From 25bca36c4cc765bc4ac77c4c6e0b0e6cf8265767 Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Sun, 12 Oct 2025 20:34:12 +0200 Subject: [PATCH] Update On Sun Oct 12 20:34:11 CEST 2025 --- .github/update.log | 1 + clash-nyanpasu/backend/Cargo.lock | 44 +- clash-nyanpasu/frontend/nyanpasu/package.json | 2 +- clash-nyanpasu/pnpm-lock.yaml | 65 +- clash-verge-rev/.github/workflows/alpha.yml | 1 + .../.github/workflows/autobuild.yml | 1 + .../.github/workflows/cross_check.yaml | 3 + clash-verge-rev/.github/workflows/dev.yml | 1 + clash-verge-rev/.github/workflows/fmt.yml | 3 + .../.github/workflows/lint-clippy.yml | 2 + clash-verge-rev/.github/workflows/release.yml | 1 + clash-verge-rev/.github/workflows/updater.yml | 3 + clash-verge-rev/.husky/pre-commit | 32 +- clash-verge-rev/.husky/pre-push | 23 +- clash-verge-rev/.prettierignore | 2 + clash-verge-rev/.prettierrc | 2 +- clash-verge-rev/UPDATELOG.md | 19 +- clash-verge-rev/package.json | 43 +- clash-verge-rev/pnpm-lock.yaml | 792 ++++++++++------ clash-verge-rev/scripts/prebuild.mjs | 30 +- clash-verge-rev/src-tauri/Cargo.lock | 854 +++++++++++------- clash-verge-rev/src-tauri/Cargo.toml | 26 +- .../src-tauri/capabilities/desktop.json | 3 +- .../src-tauri/packages/linux/post-install.sh | 4 +- .../src-tauri/packages/linux/pre-remove.sh | 2 +- clash-verge-rev/src-tauri/src/cache/mod.rs | 111 --- clash-verge-rev/src-tauri/src/cmd/app.rs | 1 - clash-verge-rev/src-tauri/src/cmd/clash.rs | 350 +------ clash-verge-rev/src-tauri/src/cmd/profile.rs | 114 +-- clash-verge-rev/src-tauri/src/cmd/proxy.rs | 107 +-- .../src-tauri/src/cmd/save_profile.rs | 37 +- clash-verge-rev/src-tauri/src/cmd/service.rs | 9 +- clash-verge-rev/src-tauri/src/cmd/system.rs | 4 +- clash-verge-rev/src-tauri/src/cmd/validate.rs | 23 +- clash-verge-rev/src-tauri/src/config/clash.rs | 3 + .../src-tauri/src/config/config.rs | 75 +- .../src-tauri/src/config/profiles.rs | 6 - clash-verge-rev/src-tauri/src/config/verge.rs | 13 +- clash-verge-rev/src-tauri/src/core/backup.rs | 15 +- clash-verge-rev/src-tauri/src/core/core.rs | 241 ++--- .../src-tauri/src/core/event_driven_proxy.rs | 86 +- clash-verge-rev/src-tauri/src/core/handle.rs | 59 +- clash-verge-rev/src-tauri/src/core/hotkey.rs | 30 +- clash-verge-rev/src-tauri/src/core/logger.rs | 37 + clash-verge-rev/src-tauri/src/core/mod.rs | 2 +- clash-verge-rev/src-tauri/src/core/service.rs | 250 ++--- .../src-tauri/src/core/service_ipc.rs | 349 ------- clash-verge-rev/src-tauri/src/core/sysopt.rs | 119 +-- clash-verge-rev/src-tauri/src/core/timer.rs | 25 +- .../src-tauri/src/core/tray/mod.rs | 218 +++-- clash-verge-rev/src-tauri/src/enhance/tun.rs | 20 + clash-verge-rev/src-tauri/src/feat/backup.rs | 3 +- clash-verge-rev/src-tauri/src/feat/clash.rs | 52 +- clash-verge-rev/src-tauri/src/feat/config.rs | 10 +- clash-verge-rev/src-tauri/src/feat/profile.rs | 28 +- clash-verge-rev/src-tauri/src/feat/proxy.rs | 14 +- clash-verge-rev/src-tauri/src/feat/window.rs | 200 ++-- clash-verge-rev/src-tauri/src/ipc/general.rs | 376 -------- clash-verge-rev/src-tauri/src/ipc/logs.rs | 330 ------- clash-verge-rev/src-tauri/src/ipc/memory.rs | 119 --- clash-verge-rev/src-tauri/src/ipc/mod.rs | 15 - clash-verge-rev/src-tauri/src/ipc/monitor.rs | 120 --- clash-verge-rev/src-tauri/src/ipc/traffic.rs | 153 ---- clash-verge-rev/src-tauri/src/lib.rs | 296 +++--- clash-verge-rev/src-tauri/src/main.rs | 9 + .../src-tauri/src/module/lightweight.rs | 43 +- .../src-tauri/src/module/sysinfo.rs | 12 +- .../src-tauri/src/process/guard.rs | 30 + clash-verge-rev/src-tauri/src/process/mod.rs | 2 + clash-verge-rev/src-tauri/src/utils/dirs.rs | 83 +- clash-verge-rev/src-tauri/src/utils/format.rs | 1 + clash-verge-rev/src-tauri/src/utils/help.rs | 2 +- clash-verge-rev/src-tauri/src/utils/init.rs | 154 ++-- .../src-tauri/src/utils/logging.rs | 147 +-- .../src-tauri/src/utils/resolve/dns.rs | 16 +- .../src-tauri/src/utils/resolve/mod.rs | 179 ++-- .../src-tauri/src/utils/resolve/scheme.rs | 2 +- .../src-tauri/src/utils/resolve/ui.rs | 2 +- .../src-tauri/src/utils/resolve/window.rs | 19 +- clash-verge-rev/src-tauri/src/utils/server.rs | 31 +- .../src-tauri/src/utils/singleton.rs | 2 - .../src-tauri/src/utils/window_manager.rs | 100 +- clash-verge-rev/src-tauri/tauri.conf.json | 7 +- .../src-tauri/tauri.linux.conf.json | 8 +- clash-verge-rev/src/assets/styles/layout.scss | 205 ++--- .../connection/connection-detail.tsx | 4 +- .../components/connection/connection-item.tsx | 4 +- .../controller/window-controller.tsx | 114 +++ .../src/components/home/clash-info-card.tsx | 2 +- .../src/components/home/clash-mode-card.tsx | 3 +- .../components/home/current-proxy-card.tsx | 150 ++- .../home/enhanced-canvas-traffic-graph.tsx | 8 +- .../home/enhanced-traffic-stats.tsx | 113 +-- .../src/components/layout/layout-traffic.tsx | 81 +- .../src/components/layout/use-custom-theme.ts | 45 + .../src/components/profile/profile-item.tsx | 62 +- .../src/components/proxy/provider-button.tsx | 317 ++++--- .../src/components/proxy/proxy-chain.tsx | 15 +- .../src/components/proxy/proxy-groups.tsx | 27 +- .../src/components/proxy/proxy-head.tsx | 8 +- .../src/components/proxy/proxy-item-mini.tsx | 8 +- .../src/components/rule/provider-button.tsx | 219 +++-- .../setting/mods/clash-core-viewer.tsx | 11 +- .../setting/mods/clash-port-viewer.tsx | 33 +- .../components/setting/mods/layout-viewer.tsx | 29 +- .../setting/mods/sysproxy-viewer.tsx | 21 +- .../src/components/setting/setting-clash.tsx | 11 +- .../setting/setting-verge-advanced.tsx | 6 +- .../setting/setting-verge-basic.tsx | 4 +- .../src/components/test/test-item.tsx | 6 +- clash-verge-rev/src/hooks/use-clash.ts | 14 +- .../src/hooks/use-connection-data.ts | 108 +++ clash-verge-rev/src/hooks/use-log-data-new.ts | 151 ++++ clash-verge-rev/src/hooks/use-log-data.ts | 4 +- clash-verge-rev/src/hooks/use-memory-data.ts | 84 ++ clash-verge-rev/src/hooks/use-profiles.ts | 13 +- .../src/hooks/use-proxy-selection.ts | 24 +- .../src/hooks/use-system-proxy-state.ts | 3 +- clash-verge-rev/src/hooks/use-traffic-data.ts | 84 ++ .../src/hooks/use-traffic-monitor.ts | 224 ++--- clash-verge-rev/src/hooks/use-window.tsx | 114 +++ .../src/hooks/useServiceInstaller.ts | 5 +- clash-verge-rev/src/locales/de.json | 1 - clash-verge-rev/src/locales/en.json | 12 +- clash-verge-rev/src/locales/es.json | 1 - clash-verge-rev/src/locales/jp.json | 11 +- clash-verge-rev/src/locales/ru.json | 10 +- clash-verge-rev/src/locales/tr.json | 10 +- clash-verge-rev/src/locales/zh.json | 12 +- clash-verge-rev/src/locales/zhtw.json | 1 - clash-verge-rev/src/main.tsx | 28 +- clash-verge-rev/src/pages/_layout.tsx | 194 ++-- clash-verge-rev/src/pages/connections.tsx | 33 +- clash-verge-rev/src/pages/logs.tsx | 54 +- clash-verge-rev/src/pages/profiles.tsx | 240 ++++- clash-verge-rev/src/pages/proxies.tsx | 5 +- clash-verge-rev/src/pages/rules.tsx | 2 +- .../src/providers/app-data-context.ts | 30 +- .../src/providers/app-data-provider.tsx | 379 ++++---- clash-verge-rev/src/services/cmds.ts | 301 +----- clash-verge-rev/src/services/delay.ts | 9 +- .../src/services/global-log-service.ts | 9 +- .../src/services/ipc-log-service.ts | 30 +- clash-verge-rev/src/services/states.ts | 21 +- lede/target/linux/mediatek/image/filogic.mk | 6 +- mieru/apis/client/client.go | 171 +--- mieru/apis/client/interface.go | 1 + mieru/apis/constant/socks5.go | 13 + mieru/apis/internal/handshake.go | 60 ++ mieru/apis/model/addr.go | 14 + mieru/apis/model/addr_test.go | 8 + mieru/apis/model/socks.go | 126 +++ mieru/apis/model/socks_test.go | 161 ++++ mieru/pkg/appctl/appctlcommon/client.go | 123 +++ mieru/pkg/cli/client.go | 78 +- .../root/usr/share/passwall/app.sh | 32 +- shadowsocks-rust/Cargo.lock | 488 +++++----- .../src/local/net/tcp/auto_proxy_stream.rs | 38 +- small/.github/workflows/T2 build.yml | 2 +- small/.github/workflows/T9 build.yml | 2 +- small/gn/Makefile | 6 +- small/gn/src/out/last_commit_position.h | 4 +- .../root/usr/share/passwall/app.sh | 32 +- .../root/usr/share/shadowsocksr/subscribe.lua | 21 +- small/pdnsd-alt/Makefile | 3 +- udp2raw/README.md | 16 +- udp2raw/doc/README.zh-cn.md | 15 +- .../ServiceLib/Sample/singbox_fakeip_filter | 3 +- yt-dlp/yt_dlp/__init__.py | 2 +- yt-dlp/yt_dlp/extractor/musescore.py | 32 +- yt-dlp/yt_dlp/extractor/youtube/_base.py | 4 +- yt-dlp/yt_dlp/extractor/youtube/_tab.py | 6 +- yt-dlp/yt_dlp/extractor/youtube/_video.py | 4 +- 173 files changed, 5800 insertions(+), 6226 deletions(-) delete mode 100644 clash-verge-rev/src-tauri/src/cache/mod.rs create mode 100644 clash-verge-rev/src-tauri/src/core/logger.rs delete mode 100644 clash-verge-rev/src-tauri/src/core/service_ipc.rs delete mode 100644 clash-verge-rev/src-tauri/src/ipc/general.rs delete mode 100644 clash-verge-rev/src-tauri/src/ipc/logs.rs delete mode 100644 clash-verge-rev/src-tauri/src/ipc/memory.rs delete mode 100644 clash-verge-rev/src-tauri/src/ipc/mod.rs delete mode 100644 clash-verge-rev/src-tauri/src/ipc/monitor.rs delete mode 100644 clash-verge-rev/src-tauri/src/ipc/traffic.rs create mode 100644 clash-verge-rev/src-tauri/src/process/guard.rs create mode 100644 clash-verge-rev/src/components/controller/window-controller.tsx create mode 100644 clash-verge-rev/src/hooks/use-connection-data.ts create mode 100644 clash-verge-rev/src/hooks/use-log-data-new.ts create mode 100644 clash-verge-rev/src/hooks/use-memory-data.ts create mode 100644 clash-verge-rev/src/hooks/use-traffic-data.ts create mode 100644 clash-verge-rev/src/hooks/use-window.tsx create mode 100644 mieru/apis/internal/handshake.go create mode 100644 mieru/apis/model/socks.go create mode 100644 mieru/apis/model/socks_test.go diff --git a/.github/update.log b/.github/update.log index ff6d45d0c6..4ae2cdea29 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1148,3 +1148,4 @@ Update On Wed Oct 8 20:42:18 CEST 2025 Update On Thu Oct 9 20:40:34 CEST 2025 Update On Fri Oct 10 20:41:18 CEST 2025 Update On Sat Oct 11 20:34:43 CEST 2025 +Update On Sun Oct 12 20:34:03 CEST 2025 diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index cd057a29c2..32358b6075 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -346,7 +346,7 @@ dependencies = [ "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -456,9 +456,9 @@ dependencies = [ [[package]] name = "async-fs" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ "async-lock", "blocking", @@ -1292,11 +1292,11 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.12" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0b03af37dad7a14518b7691d81acb0f8222604ad3d1b02f6b4bed5188c0cd5" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1688,7 +1688,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -2294,7 +2294,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.0", + "windows-sys 0.59.0", ] [[package]] @@ -2803,7 +2803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -4923,7 +4923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-targets 0.48.5", ] [[package]] @@ -6369,12 +6369,12 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.45.0", ] [[package]] @@ -7328,7 +7328,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -7546,9 +7546,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redb" -version = "3.0.2" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb1bfd2a09cb3c362dd10ea63427315cf3c79a84feb279394509981c4a3a91c" +checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06" dependencies = [ "libc", ] @@ -7882,7 +7882,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7895,7 +7895,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -9565,15 +9565,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.61.0", + "windows-sys 0.52.0", ] [[package]] @@ -11310,7 +11310,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 7f980d19cd..8c9ee74417 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -19,7 +19,7 @@ "@mui/icons-material": "7.3.4", "@mui/lab": "7.0.0-beta.17", "@mui/material": "7.3.4", - "@mui/x-date-pickers": "8.12.0", + "@mui/x-date-pickers": "8.14.0", "@nyanpasu/interface": "workspace:^", "@nyanpasu/ui": "workspace:^", "@tailwindcss/postcss": "4.1.14", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index c0617462f5..95f00115c9 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -240,8 +240,8 @@ importers: specifier: 7.3.4 version: 7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/x-date-pickers': - specifier: 8.12.0 - version: 8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 8.14.0 + version: 8.14.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@nyanpasu/interface': specifier: workspace:^ version: link:../interface @@ -289,7 +289,7 @@ importers: version: 0.4.0 material-react-table: specifier: npm:@greenhat616/material-react-table@4.0.0 - version: '@greenhat616/material-react-table@4.0.0(db1adb8c2b3e72d3c5c81efe3e7a3e75)' + version: '@greenhat616/material-react-table@4.0.0(fafcd4476b8f739264ca8e438395b4cd)' monaco-editor: specifier: 0.54.0 version: 0.54.0 @@ -310,7 +310,7 @@ importers: version: 1.6.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-hook-form-mui: specifier: 8.0.0 - version: 8.0.0(507fe0d2a0205cfe15d8e5dc9d4d832e) + version: 8.0.0(8428d39c3121399e2a450601900b76c4) react-i18next: specifier: 15.7.4 version: 15.7.4(i18next@25.5.3(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2) @@ -2061,8 +2061,8 @@ packages: '@types/react': optional: true - '@mui/x-date-pickers@8.12.0': - resolution: {integrity: sha512-CDcjdBNwMcTy3flZTCKZqSUS6deBFGKLqy3Vl6bgr5KTo8Vky2v+S+zNi56fv23Qs+P47GwpILcm3QZt/0BP0A==} + '@mui/x-date-pickers@8.14.0': + resolution: {integrity: sha512-fz8z1hoi1tbG1QUcZAkQdiO3GsCOpUeRfyANXDDzDN88L4VqwNEyrv0wmzmCfIX2sgur4gWwWJzMuCvauMgXRw==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -2098,8 +2098,8 @@ packages: moment-jalaali: optional: true - '@mui/x-internals@8.12.0': - resolution: {integrity: sha512-KCZgFHwuPg0v8I2gpjeC6k3eDRXPPX8RIGSNDXe8zSZ8dAw+p6Q2pzT9kKvctqCXSFK8ct/5YQwqx8Quhs8Ndg==} + '@mui/x-internals@8.14.0': + resolution: {integrity: sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==} engines: {node: '>=14.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -8404,6 +8404,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} @@ -10220,13 +10225,13 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@greenhat616/material-react-table@4.0.0(db1adb8c2b3e72d3c5c81efe3e7a3e75)': + '@greenhat616/material-react-table@4.0.0(fafcd4476b8f739264ca8e438395b4cd)': dependencies: '@emotion/react': 11.14.0(@types/react@19.1.14)(react@19.1.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1) '@mui/icons-material': 7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1) '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/x-date-pickers': 8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/x-date-pickers': 8.14.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/match-sorter-utils': 8.19.4 '@tanstack/react-table': 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-virtual': 3.13.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -10415,8 +10420,8 @@ snapshots: '@mui/private-theming@7.3.2(@types/react@19.1.14)(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.3 - '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1) + '@babel/runtime': 7.28.4 + '@mui/utils': 7.3.3(@types/react@19.1.14)(react@19.1.1) prop-types: 15.8.1 react: 19.1.1 optionalDependencies: @@ -10433,7 +10438,7 @@ snapshots: '@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 @@ -10459,11 +10464,11 @@ snapshots: '@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 '@mui/private-theming': 7.3.2(@types/react@19.1.14)(react@19.1.1) '@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(react@19.1.1) '@mui/types': 7.4.6(@types/react@19.1.14) - '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1) + '@mui/utils': 7.3.3(@types/react@19.1.14)(react@19.1.1) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 @@ -10491,7 +10496,7 @@ snapshots: '@mui/types@7.4.6(@types/react@19.1.14)': dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 optionalDependencies: '@types/react': 19.1.14 @@ -10503,7 +10508,7 @@ snapshots: '@mui/utils@7.3.2(@types/react@19.1.14)(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 '@mui/types': 7.4.6(@types/react@19.1.14) '@types/prop-types': 15.7.15 clsx: 2.1.1 @@ -10525,13 +10530,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.14 - '@mui/x-date-pickers@8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/x-date-pickers@8.14.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1) - '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1) - '@mui/x-internals': 8.12.0(@types/react@19.1.14)(react@19.1.1) + '@mui/utils': 7.3.3(@types/react@19.1.14)(react@19.1.1) + '@mui/x-internals': 8.14.0(@types/react@19.1.14)(react@19.1.1) '@types/react-transition-group': 4.4.12(@types/react@19.1.14) clsx: 2.1.1 prop-types: 15.8.1 @@ -10545,13 +10550,13 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.12.0(@types/react@19.1.14)(react@19.1.1)': + '@mui/x-internals@8.14.0(@types/react@19.1.14)(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.3 - '@mui/utils': 7.3.2(@types/react@19.1.14)(react@19.1.1) + '@babel/runtime': 7.28.4 + '@mui/utils': 7.3.3(@types/react@19.1.14)(react@19.1.1) react: 19.1.1 reselect: 5.1.1 - use-sync-external-store: 1.5.0(react@19.1.1) + use-sync-external-store: 1.6.0(react@19.1.1) transitivePeerDependencies: - '@types/react' @@ -16100,14 +16105,14 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-hook-form-mui@8.0.0(507fe0d2a0205cfe15d8e5dc9d4d832e): + react-hook-form-mui@8.0.0(8428d39c3121399e2a450601900b76c4): dependencies: '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-hook-form: 7.52.1(react@19.1.1) optionalDependencies: '@mui/icons-material': 7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.14)(react@19.1.1) - '@mui/x-date-pickers': 8.12.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/x-date-pickers': 8.14.0(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(react@19.1.1))(@types/react@19.1.14)(dayjs@1.11.18)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-hook-form@7.52.1(react@19.1.1): dependencies: @@ -16408,7 +16413,7 @@ snapshots: rtl-css-js@1.16.1: dependencies: - '@babel/runtime': 7.28.3 + '@babel/runtime': 7.28.4 run-parallel@1.2.0: dependencies: @@ -17496,6 +17501,10 @@ snapshots: dependencies: react: 19.1.1 + use-sync-external-store@1.6.0(react@19.1.1): + dependencies: + react: 19.1.1 + utf-8-validate@5.0.10: dependencies: node-gyp-build: 4.8.1 diff --git a/clash-verge-rev/.github/workflows/alpha.yml b/clash-verge-rev/.github/workflows/alpha.yml index 41dcec4d84..8f9836989c 100644 --- a/clash-verge-rev/.github/workflows/alpha.yml +++ b/clash-verge-rev/.github/workflows/alpha.yml @@ -25,6 +25,7 @@ env: TAG_CHANNEL: Alpha CARGO_INCREMENTAL: 0 RUST_BACKTRACE: short + HUSKY: 0 concurrency: group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}" diff --git a/clash-verge-rev/.github/workflows/autobuild.yml b/clash-verge-rev/.github/workflows/autobuild.yml index f35eda2f09..f48788c5b5 100644 --- a/clash-verge-rev/.github/workflows/autobuild.yml +++ b/clash-verge-rev/.github/workflows/autobuild.yml @@ -11,6 +11,7 @@ env: TAG_CHANNEL: AutoBuild CARGO_INCREMENTAL: 0 RUST_BACKTRACE: short + HUSKY: 0 concurrency: group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}" cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} diff --git a/clash-verge-rev/.github/workflows/cross_check.yaml b/clash-verge-rev/.github/workflows/cross_check.yaml index e00099c410..03a65bf147 100644 --- a/clash-verge-rev/.github/workflows/cross_check.yaml +++ b/clash-verge-rev/.github/workflows/cross_check.yaml @@ -9,6 +9,9 @@ on: permissions: contents: read +env: + HUSKY: 0 + jobs: cargo-check: # Treat all Rust compiler warnings as errors diff --git a/clash-verge-rev/.github/workflows/dev.yml b/clash-verge-rev/.github/workflows/dev.yml index b078aa0d52..336c5e4227 100644 --- a/clash-verge-rev/.github/workflows/dev.yml +++ b/clash-verge-rev/.github/workflows/dev.yml @@ -30,6 +30,7 @@ env: TAG_CHANNEL: DeployTest CARGO_INCREMENTAL: 0 RUST_BACKTRACE: short + HUSKY: 0 concurrency: group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}" cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} diff --git a/clash-verge-rev/.github/workflows/fmt.yml b/clash-verge-rev/.github/workflows/fmt.yml index 5ec2ca9ad4..58efc98b1c 100644 --- a/clash-verge-rev/.github/workflows/fmt.yml +++ b/clash-verge-rev/.github/workflows/fmt.yml @@ -7,6 +7,9 @@ name: Check Formatting on: pull_request: +env: + HUSKY: 0 + jobs: rustfmt: runs-on: ubuntu-latest diff --git a/clash-verge-rev/.github/workflows/lint-clippy.yml b/clash-verge-rev/.github/workflows/lint-clippy.yml index 6bc0bf338d..e987a37efd 100644 --- a/clash-verge-rev/.github/workflows/lint-clippy.yml +++ b/clash-verge-rev/.github/workflows/lint-clippy.yml @@ -3,6 +3,8 @@ name: Clippy Lint on: pull_request: workflow_dispatch: +env: + HUSKY: 0 jobs: clippy: diff --git a/clash-verge-rev/.github/workflows/release.yml b/clash-verge-rev/.github/workflows/release.yml index da80b04de2..7f74de0d54 100644 --- a/clash-verge-rev/.github/workflows/release.yml +++ b/clash-verge-rev/.github/workflows/release.yml @@ -11,6 +11,7 @@ permissions: write-all env: CARGO_INCREMENTAL: 0 RUST_BACKTRACE: short + HUSKY: 0 concurrency: # only allow per workflow per commit (and not pr) to run at a time group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}" diff --git a/clash-verge-rev/.github/workflows/updater.yml b/clash-verge-rev/.github/workflows/updater.yml index 813b8fa64a..d482bc608b 100644 --- a/clash-verge-rev/.github/workflows/updater.yml +++ b/clash-verge-rev/.github/workflows/updater.yml @@ -2,6 +2,9 @@ name: Updater CI on: workflow_dispatch permissions: write-all +env: + HUSKY: 0 + jobs: release-update: runs-on: ubuntu-latest diff --git a/clash-verge-rev/.husky/pre-commit b/clash-verge-rev/.husky/pre-commit index a4ab405f19..4e2adc2774 100755 --- a/clash-verge-rev/.husky/pre-commit +++ b/clash-verge-rev/.husky/pre-commit @@ -1,26 +1,24 @@ #!/bin/bash +set -euo pipefail -#pnpm pretty-quick --staged +echo "[pre-commit] Running lint-staged for JS/TS files..." +# Auto-fix staged JS/TS files, print warnings but don't fail commit +npx lint-staged || true -if git diff --cached --name-only | grep -q '^src/'; then - pnpm format:check - if [ $? -ne 0 ]; then - echo "Code format check failed in src/. Please fix formatting issues." - exit 1 - fi -fi +# Check staged Rust files +RUST_FILES=$(git diff --cached --name-only | grep -E '^src-tauri/.*\.rs$' || true) +if [ -n "$RUST_FILES" ]; then + echo "[pre-commit] Running rustfmt and clippy on staged Rust files..." + cd src-tauri || exit -if git diff --cached --name-only | grep -q '^src-tauri/'; then - cd src-tauri + # Auto-format Rust code cargo fmt - if [ $? -ne 0 ]; then - echo "rustfmt failed to format the code. Please fix the issues and try again." - exit 1 - fi + + # Lint with clippy, print warnings but don't fail commit + cargo clippy || echo "⚠️ clippy found issues, but commit will continue." + cd .. fi -#git add . - -# 允许提交 +echo "[pre-commit] Checks completed. Some warnings may exist, please review." exit 0 diff --git a/clash-verge-rev/.husky/pre-push b/clash-verge-rev/.husky/pre-push index 32181a5523..0cc5195d66 100644 --- a/clash-verge-rev/.husky/pre-push +++ b/clash-verge-rev/.husky/pre-push @@ -1,26 +1,24 @@ #!/bin/bash +set -euo pipefail -# $1: remote name (e.g., origin) -# $2: remote url (e.g., git@github.com:clash-verge-rev/clash-verge-rev.git) +remote_name="$1" +# --- Rust clippy for staged files in src-tauri --- if git diff --cached --name-only | grep -q '^src-tauri/'; then - cargo clippy --manifest-path ./src-tauri/Cargo.toml - if [ $? -ne 0 ]; then - echo "Clippy found issues in src-tauri. Please fix them before pushing." + echo "[pre-push] Running clippy on src-tauri..." + cargo clippy --manifest-path ./src-tauri/Cargo.toml -- -D warnings || { + echo "❌ Clippy found issues in src-tauri. Please fix them before pushing." exit 1 - fi + } fi - -# Only run format check if the remote exists and is the main repo -remote_name="$1" +# --- JS/TS format check only for main repo --- if git remote get-url "$remote_name" >/dev/null 2>&1; then remote_url=$(git remote get-url "$remote_name") if [[ "$remote_url" =~ github\.com[:/]+clash-verge-rev/clash-verge-rev(\.git)?$ ]]; then echo "[pre-push] Detected push to clash-verge-rev/clash-verge-rev ($remote_url)" echo "[pre-push] Running pnpm format:check..." - pnpm format:check - if [ $? -ne 0 ]; then + if ! pnpm format:check; then echo "❌ Code format check failed. Please fix formatting before pushing." exit 1 fi @@ -28,7 +26,8 @@ if git remote get-url "$remote_name" >/dev/null 2>&1; then echo "[pre-push] Not pushing to target repo. Skipping format check." fi else - echo "[pre-push] Remote $remote_name does not exist. Skipping format check." + echo "[pre-push] Remote '$remote_name' does not exist. Skipping format check." fi +echo "[pre-push] All checks passed." exit 0 diff --git a/clash-verge-rev/.prettierignore b/clash-verge-rev/.prettierignore index 6b1f26e7c5..0a3b69148a 100644 --- a/clash-verge-rev/.prettierignore +++ b/clash-verge-rev/.prettierignore @@ -6,3 +6,5 @@ pnpm-lock.yaml src-tauri/target/ src-tauri/gen/ + +target diff --git a/clash-verge-rev/.prettierrc b/clash-verge-rev/.prettierrc index a96ed65846..909174425c 100644 --- a/clash-verge-rev/.prettierrc +++ b/clash-verge-rev/.prettierrc @@ -11,6 +11,6 @@ "arrowParens": "always", "proseWrap": "preserve", "htmlWhitespaceSensitivity": "css", - "endOfLine": "lf", + "endOfLine": "auto", "embeddedLanguageFormatting": "auto" } diff --git a/clash-verge-rev/UPDATELOG.md b/clash-verge-rev/UPDATELOG.md index b0a28fda57..bde8494c29 100644 --- a/clash-verge-rev/UPDATELOG.md +++ b/clash-verge-rev/UPDATELOG.md @@ -3,9 +3,12 @@ ### ✨ 新增功能 - **Mihomo(Meta) 内核升级至 v1.19.14** -- Linux 打包为 `.deb` `.rpm` 提供 pkexec 依赖项 - 支持前端修改日志(最大文件大小、最大保留数量) - 新增链式代理图形化设置功能 +- 新增系统标题栏与程序标题栏切换 (设置-页面设置-倾向系统标题栏) +- 监听关机事件,自动关闭系统代理 +- 主界面“当前节点”卡片新增“延迟测试”按钮 +- 新增批量选择配置文件功能 ### 🚀 优化改进 @@ -17,6 +20,11 @@ - 优化 TUN 模式可用性的判断 - 移除流媒体检测的系统级提示(使用软件内通知) - 优化后端 i18n 资源占用 +- 改进 Linux 托盘支持并添加 `--no-tray` 选项 +- Linux 现在在新生成的配置中默认将 TUN 栈恢复为 mixed 模式 +- 为代理延迟测试的 URL 设置增加了保护以及添加了安全的备用 URL +- 更新了 Wayland 合成器检测逻辑,从而在 Hyprland 会话中保留原生 Wayland 后端 +- 改进 Windows 和 Unix 的 服务连接方式以及权限,避免无法连接服务或内核 ### 🐞 修复问题 @@ -29,6 +37,15 @@ - 修复前端 IP 检测无法使用 ipapi, ipsb 提供商 - 修复MacOS 下 Tun开启后 系统代理无法打开的问题 - 修复服务模式启动时,修改、生成配置文件或重启内核可能导致页面卡死的问题 +- 修复 Webdav 恢复备份不重启 +- 修复 Linux 开机后无法正常代理需要手动设置 +- 修复增加订阅或导入订阅文件时订阅页面无更新 +- 修复系统代理守卫功能不工作 +- 修复 KDE + Wayland 下多屏显示 UI 异常 +- 修复 Windows 深色模式下首次启动客户端标题栏颜色异常 +- 修复静默启动不加载完整 WebView 的问题 +- 修复 Linux WebKit 网络进程的崩溃 +- 修复 Linux GNOME/KDE 桌面下,应用主题颜色选择“系统”后,不随操作系统主题(Dark/Light)切换 ## v2.4.2 diff --git a/clash-verge-rev/package.json b/clash-verge-rev/package.json index 52e7ac62c8..812bf889c2 100644 --- a/clash-verge-rev/package.json +++ b/clash-verge-rev/package.json @@ -3,6 +3,7 @@ "version": "2.4.3", "license": "GPL-3.0-only", "scripts": { + "prepare": "husky || true", "dev": "cross-env RUST_BACKTRACE=full tauri dev -f verge-dev", "dev:diff": "cross-env RUST_BACKTRACE=full tauri dev -f verge-dev", "dev:trace": "cross-env RUST_BACKTRACE=full RUSTFLAGS=\"--cfg tokio_unstable\" tauri dev -f verge-dev tokio-trace", @@ -24,10 +25,12 @@ "release:deploytest": "pnpm release-version deploytest", "publish-version": "node scripts/publish-version.mjs", "fmt": "cargo fmt --manifest-path ./src-tauri/Cargo.toml", - "clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml", - "lint": "eslint -c eslint.config.ts --cache src", + "clippy": "cargo clippy --all-features --all-targets --manifest-path ./src-tauri/Cargo.toml", + "lint": "eslint -c eslint.config.ts --cache --cache-location .eslintcache src", + "lint:fix": "eslint -c eslint.config.ts --cache --cache-location .eslintcache --fix src", "format": "prettier --write .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "typecheck": "tsc --noEmit" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -39,7 +42,7 @@ "@mui/icons-material": "^7.3.4", "@mui/lab": "7.0.0-beta.17", "@mui/material": "^7.3.4", - "@mui/x-data-grid": "^8.13.1", + "@mui/x-data-grid": "^8.14.0", "@tauri-apps/api": "2.8.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.0", "@tauri-apps/plugin-dialog": "^2.4.0", @@ -53,25 +56,26 @@ "axios": "^1.12.2", "dayjs": "1.11.18", "foxact": "^0.2.49", - "i18next": "^25.5.3", + "i18next": "^25.6.0", "js-yaml": "^4.1.0", "json-schema": "^0.4.0", "lodash-es": "^4.17.21", - "monaco-editor": "^0.53.0", + "monaco-editor": "^0.54.0", "monaco-yaml": "^5.4.0", "nanoid": "^5.1.6", "react": "19.2.0", "react-dom": "19.2.0", "react-error-boundary": "6.0.0", - "react-hook-form": "^7.64.0", + "react-hook-form": "^7.65.0", "react-i18next": "16.0.0", "react-markdown": "10.1.0", "react-monaco-editor": "0.59.0", - "react-router-dom": "7.9.3", + "react-router-dom": "7.9.4", "react-virtuoso": "^4.14.1", "swr": "^2.3.6", "types-pac": "^1.0.3", - "zustand": "^5.0.8" + "zustand": "^5.0.8", + "tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo" }, "devDependencies": { "@actions/github": "^6.0.1", @@ -80,8 +84,8 @@ "@tauri-apps/cli": "2.8.4", "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", - "@types/react": "19.2.0", - "@types/react-dom": "19.2.0", + "@types/react": "19.2.2", + "@types/react-dom": "19.2.1", "@vitejs/plugin-legacy": "^7.2.1", "@vitejs/plugin-react": "5.0.4", "adm-zip": "^0.5.16", @@ -93,13 +97,15 @@ "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-prettier": "^5.5.4", - "eslint-plugin-react-hooks": "^6.1.1", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-react-refresh": "^0.4.23", "eslint-plugin-unused-imports": "^4.2.0", "glob": "^11.0.3", "globals": "^16.4.0", "https-proxy-agent": "^7.0.6", + "husky": "^9.1.7", "jiti": "^2.6.1", + "lint-staged": "^16.2.4", "meta-json-schema": "^1.19.14", "node-fetch": "^3.3.2", "prettier": "^3.6.2", @@ -107,11 +113,22 @@ "tar": "^7.5.1", "terser": "^5.44.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.45.0", + "typescript-eslint": "^8.46.0", "vite": "^7.1.9", "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-svgr": "^4.5.0" }, + "lint-staged": { + "*.{ts,tsx,js,jsx}": [ + "eslint --fix", + "prettier --write", + "git add" + ], + "*.{css,scss,json,md}": [ + "prettier --write", + "git add" + ] + }, "type": "module", "packageManager": "pnpm@9.13.2" } diff --git a/clash-verge-rev/pnpm-lock.yaml b/clash-verge-rev/pnpm-lock.yaml index 38ad0822e7..91737e7822 100644 --- a/clash-verge-rev/pnpm-lock.yaml +++ b/clash-verge-rev/pnpm-lock.yaml @@ -19,25 +19,25 @@ importers: version: 3.2.2(react@19.2.0) '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@19.2.0)(react@19.2.0) + version: 11.14.0(@types/react@19.2.2)(react@19.2.0) '@emotion/styled': specifier: ^11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@juggle/resize-observer': specifier: ^3.4.0 version: 3.4.0 '@mui/icons-material': specifier: ^7.3.4 - version: 7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) + version: 7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@mui/lab': specifier: 7.0.0-beta.17 - version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@mui/material': specifier: ^7.3.4 - version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@mui/x-data-grid': - specifier: ^8.13.1 - version: 8.13.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^8.14.0 + version: 8.14.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tauri-apps/api': specifier: 2.8.0 version: 2.8.0 @@ -78,8 +78,8 @@ importers: specifier: ^0.2.49 version: 0.2.49(react@19.2.0) i18next: - specifier: ^25.5.3 - version: 25.5.3(typescript@5.9.3) + specifier: ^25.6.0 + version: 25.6.0(typescript@5.9.3) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -90,11 +90,11 @@ importers: specifier: ^4.17.21 version: 4.17.21 monaco-editor: - specifier: ^0.53.0 - version: 0.53.0 + specifier: ^0.54.0 + version: 0.54.0 monaco-yaml: specifier: ^5.4.0 - version: 5.4.0(monaco-editor@0.53.0) + version: 5.4.0(monaco-editor@0.54.0) nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -108,32 +108,35 @@ importers: specifier: 6.0.0 version: 6.0.0(react@19.2.0) react-hook-form: - specifier: ^7.64.0 - version: 7.64.0(react@19.2.0) + specifier: ^7.65.0 + version: 7.65.0(react@19.2.0) react-i18next: specifier: 16.0.0 - version: 16.0.0(i18next@25.5.3(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + version: 16.0.0(i18next@25.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react-markdown: specifier: 10.1.0 - version: 10.1.0(@types/react@19.2.0)(react@19.2.0) + version: 10.1.0(@types/react@19.2.2)(react@19.2.0) react-monaco-editor: specifier: 0.59.0 - version: 0.59.0(monaco-editor@0.53.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 0.59.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-router-dom: - specifier: 7.9.3 - version: 7.9.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: 7.9.4 + version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-virtuoso: specifier: ^4.14.1 version: 4.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) swr: specifier: ^2.3.6 version: 2.3.6(react@19.2.0) + tauri-plugin-mihomo-api: + specifier: git+https://github.com/clash-verge-rev/tauri-plugin-mihomo + version: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/7818a0a8f55c03ee7eba3fd7433faef29adf38ac types-pac: specifier: ^1.0.3 version: 1.0.3 zustand: specifier: ^5.0.8 - version: 5.0.8(@types/react@19.2.0)(react@19.2.0)(use-sync-external-store@1.5.0(react@19.2.0)) + version: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@actions/github': specifier: ^6.0.1 @@ -154,17 +157,17 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/react': - specifier: 19.2.0 - version: 19.2.0 + specifier: 19.2.2 + version: 19.2.2 '@types/react-dom': - specifier: 19.2.0 - version: 19.2.0(@types/react@19.2.0) + specifier: 19.2.1 + version: 19.2.1(@types/react@19.2.2) '@vitejs/plugin-legacy': specifier: ^7.2.1 - version: 7.2.1(terser@5.44.0)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)) + version: 7.2.1(terser@5.44.0)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) '@vitejs/plugin-react': specifier: 5.0.4 - version: 5.0.4(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)) + version: 5.0.4(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -185,22 +188,22 @@ importers: version: 10.1.8(eslint@9.37.0(jiti@2.6.1)) eslint-import-resolver-typescript: specifier: ^4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) + version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-import-x: specifier: ^4.16.1 - version: 4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)) + version: 4.16.1(@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(prettier@3.6.2) eslint-plugin-react-hooks: - specifier: ^6.1.1 - version: 6.1.1(eslint@9.37.0(jiti@2.6.1)) + specifier: ^7.0.0 + version: 7.0.0(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-react-refresh: specifier: ^0.4.23 version: 0.4.23(eslint@9.37.0(jiti@2.6.1)) eslint-plugin-unused-imports: specifier: ^4.2.0 - version: 4.2.0(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)) + version: 4.2.0(@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)) glob: specifier: ^11.0.3 version: 11.0.3 @@ -210,9 +213,15 @@ importers: https-proxy-agent: specifier: ^7.0.6 version: 7.0.6 + husky: + specifier: ^9.1.7 + version: 9.1.7 jiti: specifier: ^2.6.1 version: 2.6.1 + lint-staged: + specifier: ^16.2.4 + version: 16.2.4 meta-json-schema: specifier: ^1.19.14 version: 1.19.14 @@ -235,17 +244,17 @@ importers: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.45.0 - version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.46.0 + version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.1.9 - version: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1) + version: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) vite-plugin-monaco-editor: specifier: ^1.1.0 - version: 1.1.0(monaco-editor@0.53.0) + version: 1.1.0(monaco-editor@0.54.0) vite-plugin-svgr: specifier: ^4.5.0 - version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)) + version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)) packages: @@ -1243,8 +1252,8 @@ packages: '@types/react': optional: true - '@mui/x-data-grid@8.13.1': - resolution: {integrity: sha512-64MlyukMoGEDLT3kqdm6tw+rocgMayChj+h+fdAwqD4+2NMQoD5wZElQE+xTNmU0/DPv710X4ENceBRt2hMuGw==} + '@mui/x-data-grid@8.14.0': + resolution: {integrity: sha512-bzUpD83Wx4mawkgquDQUUbLLnpF+JP7Pe7YQx1ixS6W/AlUwXAVagPTOijwchHvlx0Ky11dJvOQAfrnWu6an/Q==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -1259,14 +1268,14 @@ packages: '@emotion/styled': optional: true - '@mui/x-internals@8.13.1': - resolution: {integrity: sha512-OKQyCJ9uxtMpjBZCOEQGOR5MhgL1f9HjI4qZHuaLxxtDATK5rcBbVjBF67hI8FzXeF1wrcZP2wsjc4AgGpAo9g==} + '@mui/x-internals@8.14.0': + resolution: {integrity: sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==} engines: {node: '>=14.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@mui/x-virtualizer@0.2.2': - resolution: {integrity: sha512-+ZcGYh/9ykoEofzcAWcJ3n6TBXzCc2ETvytho30wRkYv1ez+8yps0ezns/QvC4JqXBge/3y+e+QatIYjkTltdw==} + '@mui/x-virtualizer@0.2.3': + resolution: {integrity: sha512-CZ+VxFmeJaTduAOlSyo5cVek0PV5Y8gm4coyaHEpCvms207J9AoMUKqWIcdwsVGlTH1Y71j35xT/MwHKutZiNw==} engines: {node: '>=14.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1759,8 +1768,8 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/react-dom@19.2.0': - resolution: {integrity: sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==} + '@types/react-dom@19.2.1': + resolution: {integrity: sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==} peerDependencies: '@types/react': ^19.2.0 @@ -1769,11 +1778,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.2.0': - resolution: {integrity: sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==} - - '@types/trusted-types@1.0.6': - resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1781,63 +1787,63 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.45.0': - resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==} + '@typescript-eslint/eslint-plugin@8.46.0': + resolution: {integrity: sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.45.0 + '@typescript-eslint/parser': ^8.46.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.45.0': - resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==} + '@typescript-eslint/parser@8.46.0': + resolution: {integrity: sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.45.0': - resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==} + '@typescript-eslint/project-service@8.46.0': + resolution: {integrity: sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.45.0': - resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==} + '@typescript-eslint/scope-manager@8.46.0': + resolution: {integrity: sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.45.0': - resolution: {integrity: sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==} + '@typescript-eslint/tsconfig-utils@8.46.0': + resolution: {integrity: sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.45.0': - resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==} + '@typescript-eslint/type-utils@8.46.0': + resolution: {integrity: sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.45.0': - resolution: {integrity: sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==} + '@typescript-eslint/types@8.46.0': + resolution: {integrity: sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.45.0': - resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==} + '@typescript-eslint/typescript-estree@8.46.0': + resolution: {integrity: sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.45.0': - resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==} + '@typescript-eslint/utils@8.46.0': + resolution: {integrity: sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.45.0': - resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} + '@typescript-eslint/visitor-keys@8.46.0': + resolution: {integrity: sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -1979,6 +1985,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@7.1.1: + resolution: {integrity: sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2146,6 +2156,14 @@ packages: resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} engines: {node: '>=0.10'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.1.0: + resolution: {integrity: sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==} + engines: {node: '>=20'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -2160,6 +2178,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2305,6 +2326,9 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dompurify@3.1.7: + resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -2318,6 +2342,9 @@ packages: electron-to-chromium@1.5.173: resolution: {integrity: sha512-2bFhXP2zqSfQHugjqJIDFVwa+qIxyNApenmXTp9EjaKtdPrES5Qcn9/aSFy/NaP2E+fWG/zxKu/LBvY36p5VNQ==} + emoji-regex@10.5.0: + resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2328,6 +2355,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2496,8 +2527,8 @@ packages: eslint: ^9.36.0 typescript: ^5.9.3 - eslint-plugin-react-hooks@6.1.1: - resolution: {integrity: sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ==} + eslint-plugin-react-hooks@7.0.0: + resolution: {integrity: sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==} engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 @@ -2592,6 +2623,9 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} @@ -2704,6 +2738,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2784,6 +2822,12 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -2797,8 +2841,13 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - i18next@25.5.3: - resolution: {integrity: sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + i18next@25.6.0: + resolution: {integrity: sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -2893,6 +2942,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -3041,6 +3094,15 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lint-staged@16.2.4: + resolution: {integrity: sha512-Pkyr/wd90oAyXk98i/2KwfkIhoYQUMtss769FIT9hFM5ogYZwrk+GRE46yKXSg2ZGhcJ1p38Gf5gmI5Ohjg2yg==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.4: + resolution: {integrity: sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==} + engines: {node: '>=20.0.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3057,6 +3119,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -3080,6 +3146,11 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -3199,6 +3270,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + minimatch@10.0.3: resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} engines: {node: 20 || >=22} @@ -3221,8 +3296,8 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - monaco-editor@0.53.0: - resolution: {integrity: sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==} + monaco-editor@0.54.0: + resolution: {integrity: sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==} monaco-languageserver-types@0.4.0: resolution: {integrity: sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==} @@ -3246,6 +3321,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nano-spawn@2.0.0: + resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} + engines: {node: '>=20.17'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3316,6 +3395,10 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3379,6 +3462,11 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3429,8 +3517,8 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-hook-form@7.64.0: - resolution: {integrity: sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==} + react-hook-form@7.65.0: + resolution: {integrity: sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -3474,15 +3562,15 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react-router-dom@7.9.3: - resolution: {integrity: sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==} + react-router-dom@7.9.4: + resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.9.3: - resolution: {integrity: sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==} + react-router@7.9.4: + resolution: {integrity: sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -3564,10 +3652,17 @@ packages: engines: {node: '>= 0.4'} hasBin: true + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.46.2: resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3655,6 +3750,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -3684,6 +3783,10 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + string-ts@2.2.1: resolution: {integrity: sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw==} @@ -3695,6 +3798,14 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.1.0: + resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} + engines: {node: '>=20'} + string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -3762,6 +3873,10 @@ packages: resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} + tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/7818a0a8f55c03ee7eba3fd7433faef29adf38ac: + resolution: {tarball: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/7818a0a8f55c03ee7eba3fd7433faef29adf38ac} + version: 0.1.0 + terser@5.44.0: resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} engines: {node: '>=10'} @@ -3835,8 +3950,8 @@ packages: types-pac@1.0.3: resolution: {integrity: sha512-MF2UAZGvGMOM+vHi9Zj/LvQqdNN1m1xSB+PjAW9B/GvFqaB4GwR18YaIbGIGDRTW/J8iqFXQHLZd5eJVtho46w==} - typescript-eslint@8.45.0: - resolution: {integrity: sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==} + typescript-eslint@8.46.0: + resolution: {integrity: sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3904,8 +4019,8 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -4022,6 +4137,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4036,9 +4155,9 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.7.1: - resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} - engines: {node: '>= 14'} + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} hasBin: true yocto-queue@0.1.0: @@ -4829,7 +4948,7 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0)': + '@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 @@ -4841,7 +4960,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 transitivePeerDependencies: - supports-color @@ -4855,18 +4974,18 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) '@emotion/utils': 1.4.2 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 transitivePeerDependencies: - supports-color @@ -4967,9 +5086,9 @@ snapshots: '@eslint-react/ast@2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-react/eff': 2.0.6 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) string-ts: 2.2.1 transitivePeerDependencies: - eslint @@ -4983,9 +5102,9 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) birecord: 0.1.1 ts-pattern: 5.8.0 transitivePeerDependencies: @@ -5000,10 +5119,10 @@ snapshots: '@eslint-react/eff': 2.0.6 '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) eslint-plugin-react-debug: 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-dom: 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) @@ -5019,7 +5138,7 @@ snapshots: '@eslint-react/kit@2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-react/eff': 2.0.6 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint - supports-color @@ -5029,7 +5148,7 @@ snapshots: dependencies: '@eslint-react/eff': 2.0.6 '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) ts-pattern: 5.8.0 zod: 4.1.11 transitivePeerDependencies: @@ -5041,9 +5160,9 @@ snapshots: dependencies: '@eslint-react/ast': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/eff': 2.0.6 - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) ts-pattern: 5.8.0 transitivePeerDependencies: - eslint @@ -5151,39 +5270,39 @@ snapshots: '@mui/core-downloads-tracker@7.3.4': {} - '@mui/icons-material@7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.0)(react@19.2.0)': + '@mui/icons-material@7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 - '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) - '@mui/types': 7.4.7(@types/react@19.2.0) - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) + '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@mui/types': 7.4.7(@types/react@19.2.2) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) clsx: 2.1.1 prop-types: 15.8.1 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) - '@types/react': 19.2.0 + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@types/react': 19.2.2 - '@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@mui/core-downloads-tracker': 7.3.4 - '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) - '@mui/types': 7.4.7(@types/react@19.2.0) - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) + '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@mui/types': 7.4.7(@types/react@19.2.2) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.2.0) + '@types/react-transition-group': 4.4.12(@types/react@19.2.2) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 @@ -5192,20 +5311,20 @@ snapshots: react-is: 19.1.1 react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) - '@types/react': 19.2.0 + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@types/react': 19.2.2 - '@mui/private-theming@7.3.3(@types/react@19.2.0)(react@19.2.0)': + '@mui/private-theming@7.3.3(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) prop-types: 15.8.1 react: 19.2.0 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 - '@mui/styled-engine@7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(react@19.2.0)': + '@mui/styled-engine@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 '@emotion/cache': 11.14.0 @@ -5215,77 +5334,77 @@ snapshots: prop-types: 15.8.1 react: 19.2.0 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) - '@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0)': + '@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/private-theming': 7.3.3(@types/react@19.2.0)(react@19.2.0) - '@mui/styled-engine': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(react@19.2.0) - '@mui/types': 7.4.7(@types/react@19.2.0) - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) + '@mui/private-theming': 7.3.3(@types/react@19.2.2)(react@19.2.0) + '@mui/styled-engine': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) + '@mui/types': 7.4.7(@types/react@19.2.2) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 react: 19.2.0 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) - '@types/react': 19.2.0 + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@types/react': 19.2.2 - '@mui/types@7.4.7(@types/react@19.2.0)': + '@mui/types@7.4.7(@types/react@19.2.2)': dependencies: '@babel/runtime': 7.28.4 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 - '@mui/utils@7.3.3(@types/react@19.2.0)(react@19.2.0)': + '@mui/utils@7.3.3(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/types': 7.4.7(@types/react@19.2.0) + '@mui/types': 7.4.7(@types/react@19.2.2) '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 react: 19.2.0 react-is: 19.1.1 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 - '@mui/x-data-grid@8.13.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@mui/x-data-grid@8.14.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) - '@mui/x-internals': 8.13.1(@types/react@19.2.0)(react@19.2.0) - '@mui/x-virtualizer': 0.2.2(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) + '@mui/x-internals': 8.14.0(@types/react@19.2.2)(react@19.2.0) + '@mui/x-virtualizer': 0.2.3(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) clsx: 2.1.1 prop-types: 15.8.1 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - use-sync-external-store: 1.5.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0) + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.13.1(@types/react@19.2.0)(react@19.2.0)': + '@mui/x-internals@8.14.0(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) react: 19.2.0 reselect: 5.1.1 - use-sync-external-store: 1.5.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) transitivePeerDependencies: - '@types/react' - '@mui/x-virtualizer@0.2.2(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@mui/x-virtualizer@0.2.3(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.28.4 - '@mui/utils': 7.3.3(@types/react@19.2.0)(react@19.2.0) - '@mui/x-internals': 8.13.1(@types/react@19.2.0)(react@19.2.0) + '@mui/utils': 7.3.3(@types/react@19.2.2)(react@19.2.0) + '@mui/x-internals': 8.14.0(@types/react@19.2.2)(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: @@ -5718,32 +5837,30 @@ snapshots: '@types/prop-types@15.7.15': {} - '@types/react-dom@19.2.0(@types/react@19.2.0)': + '@types/react-dom@19.2.1(@types/react@19.2.2)': dependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 - '@types/react-transition-group@4.4.12(@types/react@19.2.0)': + '@types/react-transition-group@4.4.12(@types/react@19.2.2)': dependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 - '@types/react@19.2.0': + '@types/react@19.2.2': dependencies: csstype: 3.1.3 - '@types/trusted-types@1.0.6': {} - '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.45.0 + '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.0 eslint: 9.37.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -5753,41 +5870,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.45.0 + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.0 debug: 4.4.1 eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.45.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.46.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/tsconfig-utils': 8.46.0(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 debug: 4.4.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.45.0': + '@typescript-eslint/scope-manager@8.46.0': dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/visitor-keys': 8.46.0 - '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.46.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.1 eslint: 9.37.0(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -5795,14 +5912,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.45.0': {} + '@typescript-eslint/types@8.46.0': {} - '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.46.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.45.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 + '@typescript-eslint/project-service': 8.46.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.0(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/visitor-keys': 8.46.0 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -5813,20 +5930,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.8.0(eslint@9.37.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.45.0': + '@typescript-eslint/visitor-keys@8.46.0': dependencies: - '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/types': 8.46.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} @@ -5890,7 +6007,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-legacy@7.2.1(terser@5.44.0)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))': + '@vitejs/plugin-legacy@7.2.1(terser@5.44.0)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4) @@ -5905,11 +6022,11 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.44.0 - vite: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1) + vite: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@5.0.4(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1))': + '@vitejs/plugin-react@5.0.4(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -5917,7 +6034,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1) + vite: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -5953,6 +6070,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@7.1.1: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -6158,6 +6279,15 @@ snapshots: memoizee: 0.4.17 timers-ext: 0.1.8 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.1.0: + dependencies: + slice-ansi: 7.1.2 + string-width: 8.1.0 + client-only@0.0.1: {} clsx@2.1.1: {} @@ -6168,6 +6298,8 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -6308,6 +6440,8 @@ snapshots: '@babel/runtime': 7.28.4 csstype: 3.1.3 + dompurify@3.1.7: {} + dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -6323,12 +6457,16 @@ snapshots: electron-to-chromium@1.5.173: {} + emoji-regex@10.5.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} entities@4.5.0: {} + environment@1.1.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -6495,7 +6633,7 @@ snapshots: - supports-color optional: true - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)): dependencies: debug: 4.4.1 eslint: 9.37.0(jiti@2.6.1) @@ -6506,26 +6644,26 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color optional: true - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.37.0(jiti@2.6.1)): dependencies: - '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/types': 8.46.0 comment-parser: 1.4.1 debug: 4.4.1 eslint: 9.37.0(jiti@2.6.1) @@ -6536,12 +6674,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 optionalDependencies: - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6552,7 +6690,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.37.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6564,7 +6702,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -6588,10 +6726,10 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) string-ts: 2.2.1 ts-pattern: 5.8.0 @@ -6607,9 +6745,9 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) compare-versions: 6.1.1 eslint: 9.37.0(jiti@2.6.1) string-ts: 2.2.1 @@ -6626,10 +6764,10 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) string-ts: 2.2.1 ts-pattern: 5.8.0 @@ -6637,11 +6775,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks@6.1.1(eslint@9.37.0(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.0(eslint@9.37.0(jiti@2.6.1)): dependencies: '@babel/core': 7.28.4 '@babel/parser': 7.28.4 eslint: 9.37.0(jiti@2.6.1) + hermes-parser: 0.25.1 zod: 4.1.11 zod-validation-error: 4.0.2(zod@4.1.11) transitivePeerDependencies: @@ -6655,10 +6794,10 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) string-ts: 2.2.1 ts-pattern: 5.8.0 @@ -6678,9 +6817,9 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) string-ts: 2.2.1 ts-pattern: 5.8.0 @@ -6696,10 +6835,10 @@ snapshots: '@eslint-react/kit': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.0.6(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.0 + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.46.0 + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) compare-versions: 6.1.1 eslint: 9.37.0(jiti@2.6.1) is-immutable-type: 5.0.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) @@ -6710,11 +6849,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)): + eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)): dependencies: eslint: 9.37.0(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint-scope@8.4.0: dependencies: @@ -6801,6 +6940,8 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + eventemitter3@5.0.1: {} + ext@1.7.0: dependencies: type: 2.7.3 @@ -6909,6 +7050,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -7018,6 +7161,12 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -7035,7 +7184,9 @@ snapshots: transitivePeerDependencies: - supports-color - i18next@25.5.3(typescript@5.9.3): + husky@9.1.7: {} + + i18next@25.6.0(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 optionalDependencies: @@ -7136,6 +7287,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -7152,7 +7307,7 @@ snapshots: is-immutable-type@5.0.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) ts-declaration-location: 1.0.7(typescript@5.9.3) @@ -7279,6 +7434,25 @@ snapshots: lines-and-columns@1.2.4: {} + lint-staged@16.2.4: + dependencies: + commander: 14.0.1 + listr2: 9.0.4 + micromatch: 4.0.8 + nano-spawn: 2.0.0 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.1 + + listr2@9.0.4: + dependencies: + cli-truncate: 5.1.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -7291,6 +7465,14 @@ snapshots: lodash@4.17.21: {} + log-update@6.1.0: + dependencies: + ansi-escapes: 7.1.1 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.2 + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -7315,6 +7497,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + marked@14.0.0: {} + math-intrinsics@1.1.0: {} mdast-util-from-markdown@2.0.2: @@ -7567,6 +7751,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-function@5.0.1: {} + minimatch@10.0.3: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -7588,9 +7774,10 @@ snapshots: dependencies: minipass: 7.1.2 - monaco-editor@0.53.0: + monaco-editor@0.54.0: dependencies: - '@types/trusted-types': 1.0.6 + dompurify: 3.1.7 + marked: 14.0.0 monaco-languageserver-types@0.4.0: dependencies: @@ -7604,27 +7791,29 @@ snapshots: monaco-types@0.1.0: {} - monaco-worker-manager@2.0.1(monaco-editor@0.53.0): + monaco-worker-manager@2.0.1(monaco-editor@0.54.0): dependencies: - monaco-editor: 0.53.0 + monaco-editor: 0.54.0 - monaco-yaml@5.4.0(monaco-editor@0.53.0): + monaco-yaml@5.4.0(monaco-editor@0.54.0): dependencies: jsonc-parser: 3.3.1 - monaco-editor: 0.53.0 + monaco-editor: 0.54.0 monaco-languageserver-types: 0.4.0 monaco-marker-data-provider: 1.2.4 monaco-types: 0.1.0 - monaco-worker-manager: 2.0.1(monaco-editor@0.53.0) + monaco-worker-manager: 2.0.1(monaco-editor@0.54.0) path-browserify: 1.0.1 prettier: 3.6.2 vscode-languageserver-textdocument: 1.0.12 vscode-languageserver-types: 3.17.5 vscode-uri: 3.1.0 - yaml: 2.7.1 + yaml: 2.8.1 ms@2.1.3: {} + nano-spawn@2.0.0: {} + nanoid@3.3.11: {} nanoid@5.1.6: {} @@ -7698,6 +7887,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -7766,6 +7959,8 @@ snapshots: picomatch@4.0.3: {} + pidtree@0.6.0: {} + possible-typed-array-names@1.1.0: optional: true @@ -7809,15 +8004,15 @@ snapshots: react-fast-compare@3.2.2: {} - react-hook-form@7.64.0(react@19.2.0): + react-hook-form@7.65.0(react@19.2.0): dependencies: react: 19.2.0 - react-i18next@16.0.0(i18next@25.5.3(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + react-i18next@16.0.0(i18next@25.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 html-parse-stringify: 3.0.1 - i18next: 25.5.3(typescript@5.9.3) + i18next: 25.6.0(typescript@5.9.3) react: 19.2.0 optionalDependencies: react-dom: 19.2.0(react@19.2.0) @@ -7827,11 +8022,11 @@ snapshots: react-is@19.1.1: {} - react-markdown@10.1.0(@types/react@19.2.0)(react@19.2.0): + react-markdown@10.1.0(@types/react@19.2.2)(react@19.2.0): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.2.0 + '@types/react': 19.2.2 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 @@ -7845,21 +8040,21 @@ snapshots: transitivePeerDependencies: - supports-color - react-monaco-editor@0.59.0(monaco-editor@0.53.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-monaco-editor@0.59.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - monaco-editor: 0.53.0 + monaco-editor: 0.54.0 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) react-refresh@0.17.0: {} - react-router-dom@7.9.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-router-dom@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - react-router: 7.9.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react-router: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react-router@7.9.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-router@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: cookie: 1.0.2 react: 19.2.0 @@ -7961,8 +8156,15 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + reusify@1.1.0: {} + rfdc@1.4.1: {} + rollup@4.46.2: dependencies: '@types/estree': 1.0.8 @@ -8100,6 +8302,11 @@ snapshots: signal-exit@4.1.0: {} + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.1.0 + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -8126,6 +8333,8 @@ snapshots: internal-slot: 1.1.0 optional: true + string-argv@0.3.2: {} + string-ts@2.2.1: {} string-width@4.2.3: @@ -8140,6 +8349,17 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.5.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.0 + + string-width@8.1.0: + dependencies: + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.0 + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -8206,7 +8426,7 @@ snapshots: dependencies: dequal: 2.0.3 react: 19.2.0 - use-sync-external-store: 1.5.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) synckit@0.11.11: dependencies: @@ -8222,6 +8442,10 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + tauri-plugin-mihomo-api@https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/7818a0a8f55c03ee7eba3fd7433faef29adf38ac: + dependencies: + '@tauri-apps/api': 2.8.0 + terser@5.44.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -8315,12 +8539,12 @@ snapshots: types-pac@1.0.3: {} - typescript-eslint@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -8420,7 +8644,7 @@ snapshots: dependencies: punycode: 2.3.1 - use-sync-external-store@1.5.0(react@19.2.0): + use-sync-external-store@1.6.0(react@19.2.0): dependencies: react: 19.2.0 @@ -8434,22 +8658,22 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-monaco-editor@1.1.0(monaco-editor@0.53.0): + vite-plugin-monaco-editor@1.1.0(monaco-editor@0.54.0): dependencies: - monaco-editor: 0.53.0 + monaco-editor: 0.54.0 - vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1)): + vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)): dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.46.2) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - vite: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1) + vite: 7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.7.1): + vite@7.1.9(jiti@2.6.1)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1): dependencies: esbuild: 0.25.4 fdir: 6.5.0(picomatch@4.0.3) @@ -8462,7 +8686,7 @@ snapshots: jiti: 2.6.1 sass: 1.93.2 terser: 5.44.0 - yaml: 2.7.1 + yaml: 2.8.1 void-elements@3.1.0: {} @@ -8544,6 +8768,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} yallist@3.1.1: {} @@ -8552,7 +8782,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.7.1: {} + yaml@2.8.1: {} yocto-queue@0.1.0: {} @@ -8562,10 +8792,10 @@ snapshots: zod@4.1.11: {} - zustand@5.0.8(@types/react@19.2.0)(react@19.2.0)(use-sync-external-store@1.5.0(react@19.2.0)): + zustand@5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.2 react: 19.2.0 - use-sync-external-store: 1.5.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) zwitch@2.0.4: {} diff --git a/clash-verge-rev/scripts/prebuild.mjs b/clash-verge-rev/scripts/prebuild.mjs index 514b2df910..2e3431369f 100644 --- a/clash-verge-rev/scripts/prebuild.mjs +++ b/clash-verge-rev/scripts/prebuild.mjs @@ -1,14 +1,14 @@ +import AdmZip from "adm-zip"; +import { execSync } from "child_process"; import fs from "fs"; import fsp from "fs/promises"; -import zlib from "zlib"; -import { extract } from "tar"; -import path from "path"; -import AdmZip from "adm-zip"; -import fetch from "node-fetch"; -import { HttpsProxyAgent } from "https-proxy-agent"; -import { execSync } from "child_process"; -import { log_info, log_debug, log_error, log_success } from "./utils.mjs"; import { glob } from "glob"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import fetch from "node-fetch"; +import path from "path"; +import { extract } from "tar"; +import zlib from "zlib"; +import { log_debug, log_error, log_info, log_success } from "./utils.mjs"; const cwd = process.cwd(); const TEMP_DIR = path.join(cwd, "node_modules/.verge"); @@ -383,8 +383,8 @@ const resolvePlugin = async () => { const resolveServicePermission = async () => { const serviceExecutables = [ "clash-verge-service*", - "install-service*", - "uninstall-service*", + "clash-verge-service-install*", + "clash-verge-service-uninstall*", ]; const resDir = path.join(cwd, "src-tauri/resources"); for (let f of serviceExecutables) { @@ -430,7 +430,7 @@ async function resolveLocales() { /** * main */ -const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`; +const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service-ipc/releases/download/${SIDECAR_HOST}`; const resolveService = () => { let ext = platform === "win32" ? ".exe" : ""; @@ -445,8 +445,8 @@ const resolveInstall = () => { let ext = platform === "win32" ? ".exe" : ""; let suffix = platform === "linux" ? "-" + SIDECAR_HOST : ""; resolveResource({ - file: "install-service" + suffix + ext, - downloadURL: `${SERVICE_URL}/install-service${ext}`, + file: "clash-verge-service-install" + suffix + ext, + downloadURL: `${SERVICE_URL}/clash-verge-service-install${ext}`, }); }; @@ -455,8 +455,8 @@ const resolveUninstall = () => { let suffix = platform === "linux" ? "-" + SIDECAR_HOST : ""; resolveResource({ - file: "uninstall-service" + suffix + ext, - downloadURL: `${SERVICE_URL}/uninstall-service${ext}`, + file: "clash-verge-service-uninstall" + suffix + ext, + downloadURL: `${SERVICE_URL}/clash-verge-service-uninstall${ext}`, }); }; diff --git a/clash-verge-rev/src-tauri/Cargo.lock b/clash-verge-rev/src-tauri/Cargo.lock index ca49513df4..ff2ba03f4d 100644 --- a/clash-verge-rev/src-tauri/Cargo.lock +++ b/clash-verge-rev/src-tauri/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -112,9 +112,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" @@ -140,12 +140,12 @@ dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", - "parking_lot 0.12.4", + "objc2-foundation 0.3.2", + "parking_lot 0.12.5", "percent-encoding", "windows-sys 0.60.2", "wl-clipboard-rs", @@ -275,7 +275,7 @@ dependencies = [ "polling 3.11.0", "rustix 1.1.2", "slab", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -370,7 +370,7 @@ dependencies = [ "rustix 1.1.2", "signal-hook-registry", "slab", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -566,9 +566,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -576,7 +576,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -647,11 +647,11 @@ dependencies = [ [[package]] name = "block2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.2", + "objc2 0.6.3", ] [[package]] @@ -723,7 +723,7 @@ dependencies = [ "static_assertions", "tap", "thin-vec", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", ] @@ -835,18 +835,18 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", @@ -910,9 +910,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ "serde_core", ] @@ -937,7 +937,7 @@ dependencies = [ "semver 1.0.27", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -964,9 +964,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.38" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", @@ -1024,7 +1024,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -1098,8 +1098,9 @@ dependencies = [ "backoff", "base64 0.22.1", "boa_engine", - "cfg-if", "chrono", + "clash_verge_logger", + "clash_verge_service_ipc", "console-subscriber", "criterion", "dashmap 6.1.0", @@ -1114,15 +1115,13 @@ dependencies = [ "hex", "hmac", "isahc", - "kode-bridge", "libc", "log", "nanoid", "network-interface", - "nu-ansi-term", "once_cell", "open", - "parking_lot 0.12.4", + "parking_lot 0.12.5", "percent-encoding", "port_scanner", "regex", @@ -1147,6 +1146,7 @@ dependencies = [ "tauri-plugin-fs", "tauri-plugin-global-shortcut", "tauri-plugin-http", + "tauri-plugin-mihomo", "tauri-plugin-notification", "tauri-plugin-process", "tauri-plugin-shell", @@ -1158,7 +1158,33 @@ dependencies = [ "warp", "winapi", "winreg 0.55.0", - "zip 5.1.1", + "zip 6.0.0", +] + +[[package]] +name = "clash_verge_logger" +version = "0.1.0" +source = "git+https://github.com/clash-verge-rev/clash-verge-logger#d3033b152cbf45fd04d9dee48d7aa9371dbfe99c" +dependencies = [ + "flexi_logger", + "log", + "nu-ansi-term", +] + +[[package]] +name = "clash_verge_service_ipc" +version = "2.0.14" +source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#0b78603344302de33712d2f7554329bdb0091e1e" +dependencies = [ + "anyhow", + "kode-bridge", + "log", + "once_cell", + "serde", + "serde_json", + "strum", + "strum_macros", + "tokio", ] [[package]] @@ -1206,7 +1232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -1637,7 +1663,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.11", + "parking_lot_core 0.9.12", ] [[package]] @@ -1651,9 +1677,15 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.11", + "parking_lot_core 0.9.12", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "data-url" version = "0.3.2" @@ -1676,9 +1708,9 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" [[package]] name = "delay_timer" @@ -1707,12 +1739,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -1852,7 +1884,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1868,9 +1900,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "libc", - "objc2 0.6.2", + "objc2 0.6.3", ] [[package]] @@ -1890,7 +1922,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.8", + "libloading 0.8.9", ] [[package]] @@ -2068,7 +2100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2200,9 +2232,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "fixedbitset" @@ -2212,9 +2244,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "libz-rs-sys", @@ -2223,15 +2255,15 @@ dependencies = [ [[package]] name = "flexi_logger" -version = "0.31.4" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff38b61724dd492b5171d5dbb0921dfc8e859022c5993b22f80f74e9afe6d573" +checksum = "31e5335674a3a259527f97e9176a3767dcc9b220b8e29d643daeb2d6c72caf8b" dependencies = [ "chrono", "log", "nu-ansi-term", "regex", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -2602,9 +2634,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gio" @@ -2699,11 +2731,11 @@ checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" dependencies = [ "crossbeam-channel", "keyboard-types", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "once_cell", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "windows-sys 0.59.0", "x11rb", "xkeysym", @@ -2812,12 +2844,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -2857,12 +2890,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - [[package]] name = "hdrhistogram" version = "7.5.4" @@ -3169,7 +3196,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3189,7 +3216,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.0", + "windows-core 0.62.2", ] [[package]] @@ -3474,7 +3501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.15.5", "serde", "serde_core", ] @@ -3732,9 +3759,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -3775,9 +3802,9 @@ dependencies = [ [[package]] name = "kode-bridge" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368479099245d8ecd5b74e6b2b6279a69b38556a442aefbbaadd3ecf8246ffc3" +checksum = "b158d8b9ab9f07c892a3f1b5d44a79e6c74e97541bf66488025796d429cf7aac" dependencies = [ "bytes", "futures", @@ -3785,17 +3812,19 @@ dependencies = [ "httparse", "interprocess", "libc", - "parking_lot 0.12.4", + "parking_lot 0.12.5", + "path-tree", "pin-project-lite", "rand 0.9.2", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", "toml 0.9.7", "tracing", + "url", "widestring", ] @@ -3849,9 +3878,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" @@ -3865,12 +3894,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link 0.2.1", ] [[package]] @@ -3881,7 +3910,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", ] [[package]] @@ -3955,11 +3984,10 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -4007,8 +4035,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "119c8490084af61b44c9eda9d626475847a186737c0378c85e32d77c33a01cd4" dependencies = [ "cc", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", "time", ] @@ -4079,9 +4107,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmem" @@ -4158,9 +4186,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "1cc7d85f3d741164e8972ad355e26ac6e51b20fcae5f911c7da8f2d8bbbb3f33" dependencies = [ "num-traits", "pxfm", @@ -4176,14 +4204,14 @@ dependencies = [ "dpi", "gtk", "keyboard-types", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "png 0.17.16", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "windows-sys 0.60.2", ] @@ -4277,7 +4305,7 @@ dependencies = [ "cc", "libc", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "winapi", ] @@ -4475,9 +4503,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", "objc2-exception-helper", @@ -4485,77 +4513,104 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "libc", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-cloud-kit", "objc2-core-data", "objc2-core-foundation", "objc2-core-graphics", "objc2-core-image", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-core-data" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.9.4", "dispatch2", - "objc2 0.6.2", + "objc2 0.6.3", ] [[package]] name = "objc2-core-graphics" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.9.4", "dispatch2", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", "objc2-io-surface", ] [[package]] name = "objc2-core-image" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", ] [[package]] @@ -4587,22 +4642,22 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "libc", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", @@ -4610,22 +4665,22 @@ dependencies = [ [[package]] name = "objc2-io-surface" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] [[package]] name = "objc2-javascript-core" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" dependencies = [ - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -4643,14 +4698,14 @@ dependencies = [ [[package]] name = "objc2-osa-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", ] [[package]] @@ -4668,59 +4723,59 @@ dependencies = [ [[package]] name = "objc2-quartz-core" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-security" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] [[package]] name = "objc2-ui-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-web-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", - "objc2 0.6.2", + "block2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "objc2-javascript-core", "objc2-security", ] [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -4730,6 +4785,9 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "parking_lot_core 0.9.12", +] [[package]] name = "oorandom" @@ -4850,12 +4908,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", "objc2-osa-kit", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -4902,12 +4960,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core 0.9.11", + "parking_lot_core 0.9.12", ] [[package]] @@ -4926,15 +4984,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -4943,6 +5001,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "path-tree" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a97453bc21a968f722df730bfe11bd08745cb50d1300b0df2bda131dece136" +dependencies = [ + "smallvec", +] + [[package]] name = "pathdiff" version = "0.2.3" @@ -4977,20 +5044,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", - "thiserror 2.0.16", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" dependencies = [ "pest", "pest_generator", @@ -4998,9 +5064,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" dependencies = [ "pest", "pest_meta", @@ -5011,9 +5077,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" dependencies = [ "pest", "sha2 0.10.9", @@ -5306,7 +5372,7 @@ dependencies = [ "hermit-abi 0.5.2", "pin-project-lite", "rustix 1.1.2", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -5578,8 +5644,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.0", - "thiserror 2.0.16", + "socket2 0.5.10", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -5600,7 +5666,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.16", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -5615,16 +5681,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.0", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -5788,9 +5854,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -5814,23 +5880,23 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -5839,9 +5905,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" dependencies = [ "aho-corasick", "memchr", @@ -5851,9 +5917,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" dependencies = [ "aho-corasick", "memchr", @@ -5952,17 +6018,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" dependencies = [ "ashpd", - "block2 0.6.1", + "block2 0.6.2", "dispatch2", "glib-sys", "gobject-sys", "gtk-sys", "js-sys", "log", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "raw-window-handle", "wasm-bindgen", "wasm-bindgen-futures", @@ -6090,7 +6156,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -6119,9 +6185,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -6161,7 +6227,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -6432,9 +6498,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" dependencies = [ "base64 0.22.1", "chrono", @@ -6443,8 +6509,7 @@ dependencies = [ "indexmap 2.11.4", "schemars 0.9.0", "schemars 1.0.4", - "serde", - "serde_derive", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -6452,9 +6517,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" dependencies = [ "darling", "proc-macro2", @@ -6722,7 +6787,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", "raw-window-handle", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", "wasm-bindgen", "web-sys", "windows-sys 0.59.0", @@ -6762,9 +6827,9 @@ checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -6779,7 +6844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "parking_lot 0.12.4", + "parking_lot 0.12.5", "phf_shared 0.11.3", "precomputed-hash", "serde", @@ -6803,6 +6868,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "subtle" version = "2.6.1" @@ -6879,9 +6962,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.37.1" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bddd368fda2f82ead69c03d46d351987cfa0c2a57abfa37a017f3aa3e9bf69a" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ "libc", "memchr", @@ -6947,7 +7030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", @@ -6964,11 +7047,11 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", - "parking_lot 0.12.4", + "parking_lot 0.12.5", "raw-window-handle", "scopeguard", "tao-macros", @@ -7038,9 +7121,9 @@ dependencies = [ "log", "mime", "muda", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", "percent-encoding", @@ -7057,7 +7140,7 @@ dependencies = [ "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", "tray-icon", @@ -7111,7 +7194,7 @@ dependencies = [ "sha2 0.10.9", "syn 2.0.106", "tauri-utils", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", "url", "uuid", @@ -7160,7 +7243,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -7175,7 +7258,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -7192,7 +7275,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-utils", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "url", "windows-registry", @@ -7240,7 +7323,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-plugin-fs", - "thiserror 2.0.16", + "thiserror 2.0.17", "url", ] @@ -7261,7 +7344,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-utils", - "thiserror 2.0.16", + "thiserror 2.0.17", "toml 0.9.7", "url", ] @@ -7278,7 +7361,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -7299,12 +7382,37 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-plugin-fs", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "url", "urlpattern", ] +[[package]] +name = "tauri-plugin-mihomo" +version = "0.1.1" +source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#719a31f364e23268c5b8c85169de5168d4843c15" +dependencies = [ + "base64 0.22.1", + "futures-util", + "http 1.3.1", + "httparse", + "log", + "pin-project", + "rand 0.9.2", + "reqwest", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", + "tokio-tungstenite", + "ts-rs", + "urlencoding", + "windows-sys 0.61.2", +] + [[package]] name = "tauri-plugin-notification" version = "2.3.1" @@ -7319,7 +7427,7 @@ dependencies = [ "serde_repr", "tauri", "tauri-plugin", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", "url", ] @@ -7351,7 +7459,7 @@ dependencies = [ "shared_child", "tauri", "tauri-plugin", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", ] @@ -7379,7 +7487,7 @@ dependencies = [ "tauri", "tauri-plugin", "tempfile", - "thiserror 2.0.16", + "thiserror 2.0.17", "time", "tokio", "url", @@ -7399,7 +7507,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -7413,14 +7521,14 @@ dependencies = [ "gtk", "http 1.3.1", "jni", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-ui-kit", "objc2-web-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", - "thiserror 2.0.16", + "thiserror 2.0.17", "url", "webkit2gtk", "webview2-com", @@ -7437,9 +7545,9 @@ dependencies = [ "http 1.3.1", "jni", "log", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "percent-encoding", "raw-window-handle", @@ -7485,7 +7593,7 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.16", + "thiserror 2.0.17", "toml 0.9.7", "url", "urlpattern", @@ -7510,22 +7618,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml 0.37.5", - "thiserror 2.0.16", + "thiserror 2.0.17", "windows 0.61.3", "windows-version", ] [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand 2.3.0", "getrandom 0.3.3", "once_cell", "rustix 1.1.2", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -7539,6 +7647,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminfo" version = "0.7.5" @@ -7610,11 +7727,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -7630,9 +7747,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -7761,7 +7878,7 @@ dependencies = [ "io-uring", "libc", "mio", - "parking_lot 0.12.4", + "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", "slab", @@ -7804,9 +7921,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -7823,6 +7940,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -8187,15 +8316,15 @@ dependencies = [ "dirs 6.0.0", "libappindicator", "muda", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "png 0.17.16", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "windows-sys 0.59.0", ] @@ -8217,6 +8346,45 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ts-rs" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" +dependencies = [ + "thiserror 2.0.17", + "ts-rs-macros", +] + +[[package]] +name = "ts-rs-macros" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "termcolor", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.17", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" @@ -8225,9 +8393,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -8339,6 +8507,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" @@ -8536,9 +8710,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -8549,9 +8723,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -8563,9 +8737,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.53" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -8576,9 +8750,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8586,9 +8760,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -8599,9 +8773,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -8694,9 +8868,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -8758,9 +8932,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" dependencies = [ "rustls-pki-types", ] @@ -8775,8 +8949,8 @@ dependencies = [ "webview2-com-sys", "windows 0.61.3", "windows-core 0.61.2", - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement 0.60.2", + "windows-interface 0.59.3", ] [[package]] @@ -8796,7 +8970,7 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ - "thiserror 2.0.16", + "thiserror 2.0.17", "windows 0.61.3", "windows-core 0.61.2", ] @@ -8821,9 +8995,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -8847,7 +9021,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -8862,10 +9036,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "raw-window-handle", "windows-sys 0.59.0", "windows-version", @@ -8922,8 +9096,8 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -8931,15 +9105,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -8966,9 +9140,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -8988,9 +9162,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -9005,9 +9179,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" @@ -9050,11 +9224,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -9078,11 +9252,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -9127,16 +9301,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -9187,19 +9361,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -9213,11 +9387,11 @@ dependencies = [ [[package]] name = "windows-version" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e061eb0a22b4a1d778ad70f7575ec7845490abb35b08fa320df7895882cacb" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -9240,9 +9414,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -9264,9 +9438,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -9288,9 +9462,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -9300,9 +9474,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -9324,9 +9498,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -9348,9 +9522,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -9372,9 +9546,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -9396,9 +9570,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -9464,7 +9638,7 @@ dependencies = [ "os_pipe", "rustix 0.38.44", "tempfile", - "thiserror 2.0.16", + "thiserror 2.0.17", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -9492,12 +9666,12 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wry" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90" +checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6" dependencies = [ "base64 0.22.1", - "block2 0.6.1", + "block2 0.6.2", "cookie", "crossbeam-channel", "dirs 6.0.0", @@ -9512,10 +9686,10 @@ dependencies = [ "kuchikiki", "libc", "ndk", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", "once_cell", @@ -9524,7 +9698,7 @@ dependencies = [ "sha2 0.10.9", "soup3", "tao-macros", - "thiserror 2.0.16", + "thiserror 2.0.17", "tracing", "url", "webkit2gtk", @@ -9576,9 +9750,9 @@ checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", "rustix 1.1.2", @@ -9754,9 +9928,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -9841,9 +10015,9 @@ dependencies = [ [[package]] name = "zip" -version = "5.1.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f852905151ac8d4d06fdca66520a661c09730a74c6d4e2b0f27b436b382e532" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" dependencies = [ "aes", "arbitrary", diff --git a/clash-verge-rev/src-tauri/Cargo.toml b/clash-verge-rev/src-tauri/Cargo.toml index 0dfd1929dc..7f0e7936a0 100755 --- a/clash-verge-rev/src-tauri/Cargo.toml +++ b/clash-verge-rev/src-tauri/Cargo.toml @@ -24,14 +24,14 @@ log = "0.4.28" dunce = "1.0.5" nanoid = "0.4" chrono = "0.4.42" -sysinfo = { version = "0.37.1", features = ["network", "system"] } +sysinfo = { version = "0.37.2", features = ["network", "system"] } boa_engine = "0.20.0" serde_json = "1.0.145" serde_yaml_ng = "0.10.0" once_cell = "1.21.3" port_scanner = "0.1.5" delay_timer = "0.11.6" -parking_lot = "0.12.4" +parking_lot = "0.12.5" percent-encoding = "2.3.2" tokio = { version = "1.47.1", features = [ "rt-multi-thread", @@ -41,7 +41,7 @@ tokio = { version = "1.47.1", features = [ ] } serde = { version = "1.0.228", features = ["derive"] } reqwest = { version = "0.12.23", features = ["json", "cookies"] } -regex = "1.11.3" +regex = "1.12.1" sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" } tauri = { version = "2.8.5", features = [ "protocol-asset", @@ -58,20 +58,19 @@ tauri-plugin-process = "2.3.0" tauri-plugin-clipboard-manager = "2.3.0" tauri-plugin-deep-link = "2.4.3" tauri-plugin-window-state = "2.4.0" -zip = "5.1.1" +zip = "6.0.0" reqwest_dav = "0.2.2" aes-gcm = { version = "0.10.3", features = ["std"] } base64 = "0.22.1" getrandom = "0.3.3" futures = "0.3.31" sys-locale = "0.3.2" -libc = "0.2.176" +libc = "0.2.177" gethostname = "1.0.2" hmac = "0.12.1" sha2 = "0.10.9" hex = "0.4.3" scopeguard = "1.2.0" -kode-bridge = "0.3.0" dashmap = "6.1.0" tauri-plugin-notification = "2.3.1" tokio-stream = "0.1.17" @@ -81,12 +80,17 @@ isahc = { version = "1.7.2", default-features = false, features = [ ] } backoff = { version = "0.4.0", features = ["tokio"] } tauri-plugin-http = "2.5.2" -flexi_logger = "0.31.4" -cfg-if = "1.0.3" -nu-ansi-term = { version = "0.50.1", optional = true } +flexi_logger = "0.31.7" console-subscriber = { version = "0.4.1", optional = true } tauri-plugin-devtools = { version = "2.0.1" } - +tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" } +clash_verge_logger = { version = "0.1.0", git = "https://github.com/clash-verge-rev/clash-verge-logger" } +clash_verge_service_ipc = { version = "2.0.14", features = [ + "client", +], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" } +# clash_verge_service_ipc = { version = "2.0.14", features = [ +# "client", +# ], path = "../../clash-verge-service-ipc" } [target.'cfg(windows)'.dependencies] runas = "=1.2.0" @@ -117,7 +121,7 @@ tauri-plugin-updater = "2.9.0" [features] default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] -verge-dev = ["nu-ansi-term"] +verge-dev = ["clash_verge_logger/color"] tauri-dev = [] tokio-trace = ["console-subscriber"] diff --git a/clash-verge-rev/src-tauri/capabilities/desktop.json b/clash-verge-rev/src-tauri/capabilities/desktop.json index 7a09c952f8..180d5cefa0 100755 --- a/clash-verge-rev/src-tauri/capabilities/desktop.json +++ b/clash-verge-rev/src-tauri/capabilities/desktop.json @@ -24,6 +24,7 @@ { "identifier": "http:default", "allow": [{ "url": "https://*/*" }, { "url": "http://*/*" }] - } + }, + "mihomo:default" ] } diff --git a/clash-verge-rev/src-tauri/packages/linux/post-install.sh b/clash-verge-rev/src-tauri/packages/linux/post-install.sh index f398791cea..e3e75bafa1 100644 --- a/clash-verge-rev/src-tauri/packages/linux/post-install.sh +++ b/clash-verge-rev/src-tauri/packages/linux/post-install.sh @@ -1,4 +1,4 @@ #!/bin/bash -chmod +x /usr/bin/install-service -chmod +x /usr/bin/uninstall-service +chmod +x /usr/bin/clash-verge-service-install +chmod +x /usr/bin/clash-verge-service-uninstall chmod +x /usr/bin/clash-verge-service diff --git a/clash-verge-rev/src-tauri/packages/linux/pre-remove.sh b/clash-verge-rev/src-tauri/packages/linux/pre-remove.sh index 984ffec658..481a47f6e2 100644 --- a/clash-verge-rev/src-tauri/packages/linux/pre-remove.sh +++ b/clash-verge-rev/src-tauri/packages/linux/pre-remove.sh @@ -1,2 +1,2 @@ #!/bin/bash -/usr/bin/uninstall-service +/usr/bin/clash-verge-service-uninstall diff --git a/clash-verge-rev/src-tauri/src/cache/mod.rs b/clash-verge-rev/src-tauri/src/cache/mod.rs deleted file mode 100644 index cea404c141..0000000000 --- a/clash-verge-rev/src-tauri/src/cache/mod.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::singleton; -use anyhow::Result; -use dashmap::DashMap; -use serde_json::Value; -use std::sync::Arc; -use std::time::{Duration, Instant}; -use tokio::sync::OnceCell; - -pub const SHORT_TERM_TTL: Duration = Duration::from_millis(4_250); - -pub struct CacheEntry { - pub value: Arc, - pub expires_at: Instant, -} - -pub struct Cache { - pub map: DashMap>>>>, -} - -impl Cache { - fn new() -> Self { - Cache { - map: DashMap::new(), - } - } - - pub fn make_key(prefix: &str, id: &str) -> String { - format!("{prefix}:{id}") - } - - pub async fn get_or_fetch(&self, key: String, ttl: Duration, fetch_fn: F) -> Arc - where - F: Fn() -> Fut + Send + Sync + 'static, - Fut: std::future::Future + Send + 'static, - T: Send + Sync + 'static, - { - loop { - let now = Instant::now(); - let key_cloned = key.clone(); - - // Get or create the cell - let cell = self - .map - .entry(key_cloned.clone()) - .or_insert_with(|| Arc::new(OnceCell::new())) - .clone(); - - // Check if we have a valid cached entry - if let Some(entry) = cell.get() { - if entry.expires_at > now { - return Arc::clone(&entry.value); - } - // Entry is expired, remove it - self.map - .remove_if(&key_cloned, |_, v| Arc::ptr_eq(v, &cell)); - continue; // Retry with fresh cell - } - - // Try to set a new value - let value = fetch_fn().await; - let entry = Box::new(CacheEntry { - value: Arc::new(value), - expires_at: Instant::now() + ttl, - }); - - match cell.set(entry) { - Ok(_) => { - // Successfully set the value, it must exist now - if let Some(set_entry) = cell.get() { - return Arc::clone(&set_entry.value); - } - } - Err(_) => { - if let Some(existing_entry) = cell.get() { - if existing_entry.expires_at > Instant::now() { - return Arc::clone(&existing_entry.value); - } - self.map - .remove_if(&key_cloned, |_, v| Arc::ptr_eq(v, &cell)); - } - } - } - } - } - - // pub fn clean_key(&self, key: &str) { - // self.map.remove(key); - // } - - // TODO - pub fn clean_default_keys(&self) { - // logging!(info, Type::Cache, "Cleaning proxies keys"); - // let proxies_key = Self::make_key("proxies", "default"); - // self.map.remove(&proxies_key); - - // logging!(info, Type::Cache, "Cleaning providers keys"); - // let providers_key = Self::make_key("providers", "default"); - // self.map.remove(&providers_key); - - // !The frontend goes crash if we clean the clash_config cache - // logging!(info, Type::Cache, "Cleaning clash config keys"); - // let clash_config_key = Self::make_key("clash_config", "default"); - // self.map.remove(&clash_config_key); - } -} - -pub type CacheService = Cache>; -pub type CacheProxy = Cache; - -singleton!(Cache, PROXY_INSTANCE); -singleton!(Cache>, SERVICE_INSTANCE); diff --git a/clash-verge-rev/src-tauri/src/cmd/app.rs b/clash-verge-rev/src-tauri/src/cmd/app.rs index 68de9ec5f9..6a3a3dd872 100644 --- a/clash-verge-rev/src-tauri/src/cmd/app.rs +++ b/clash-verge-rev/src-tauri/src/cmd/app.rs @@ -191,7 +191,6 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult { logging!( info, Type::Cmd, - true, "Copying icon file path: {:?} -> file dist: {:?}", path, dest_path diff --git a/clash-verge-rev/src-tauri/src/cmd/clash.rs b/clash-verge-rev/src-tauri/src/cmd/clash.rs index 0081f91716..9fdb4c4475 100644 --- a/clash-verge-rev/src-tauri/src/cmd/clash.rs +++ b/clash-verge-rev/src-tauri/src/cmd/clash.rs @@ -1,21 +1,15 @@ +use std::collections::VecDeque; + use super::CmdResult; use crate::{ - cache::CacheProxy, config::Config, - core::{CoreManager, handle}, -}; -use crate::{ - config::*, - feat, - ipc::{self, IpcManager}, - logging, - utils::logging::Type, - wrap_err, + core::{self, CoreManager, RunningMode, handle, logger}, }; +use crate::{config::*, feat, logging, utils::logging::Type, wrap_err}; use serde_yaml_ng::Mapping; -use std::time::Duration; +// use std::time::Duration; -const CONFIG_REFRESH_INTERVAL: Duration = Duration::from_secs(60); +// const CONFIG_REFRESH_INTERVAL: Duration = Duration::from_secs(60); /// 复制Clash环境变量 #[tauri::command] @@ -112,20 +106,6 @@ pub async fn restart_core() -> CmdResult { result } -/// 获取代理延迟 -#[tauri::command] -pub async fn clash_api_get_proxy_delay( - name: String, - url: Option, - timeout: i32, -) -> CmdResult { - wrap_err!( - IpcManager::global() - .test_proxy_delay(&name, url, timeout) - .await - ) -} - /// 测试URL延迟 #[tauri::command] pub async fn test_delay(url: String) -> CmdResult { @@ -307,317 +287,13 @@ pub async fn validate_dns_config() -> CmdResult<(bool, String)> { } } -/// 获取Clash版本信息 #[tauri::command] -pub async fn get_clash_version() -> CmdResult { - wrap_err!(IpcManager::global().get_version().await) -} - -/// 获取Clash配置 -#[tauri::command] -pub async fn get_clash_config() -> CmdResult { - let manager = IpcManager::global(); - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("clash_config", "default"); - let value = cache - .get_or_fetch(key, CONFIG_REFRESH_INTERVAL, || async { - manager.get_config().await.unwrap_or_else(|e| { - logging!(error, Type::Cmd, "Failed to fetch clash config: {e}"); - serde_json::Value::Object(serde_json::Map::new()) - }) - }) - .await; - Ok((*value).clone()) -} - -/// 强制刷新Clash配置缓存 -#[tauri::command] -pub async fn force_refresh_clash_config() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("clash_config", "default"); - cache.map.remove(&key); - get_clash_config().await -} - -/// 更新地理数据 -#[tauri::command] -pub async fn update_geo_data() -> CmdResult { - wrap_err!(IpcManager::global().update_geo_data().await) -} - -/// 升级Clash核心 -#[tauri::command] -pub async fn upgrade_clash_core() -> CmdResult { - wrap_err!(IpcManager::global().upgrade_core().await) -} - -/// 获取规则 -#[tauri::command] -pub async fn get_clash_rules() -> CmdResult { - wrap_err!(IpcManager::global().get_rules().await) -} - -/// 更新代理选择 -#[tauri::command] -pub async fn update_proxy_choice(group: String, proxy: String) -> CmdResult { - wrap_err!(IpcManager::global().update_proxy(&group, &proxy).await) -} - -/// 获取代理提供者 -#[tauri::command] -pub async fn get_proxy_providers() -> CmdResult { - wrap_err!(IpcManager::global().get_providers_proxies().await) -} - -/// 获取规则提供者 -#[tauri::command] -pub async fn get_rule_providers() -> CmdResult { - wrap_err!(IpcManager::global().get_rule_providers().await) -} - -/// 代理提供者健康检查 -#[tauri::command] -pub async fn proxy_provider_health_check(name: String) -> CmdResult { - wrap_err!( - IpcManager::global() - .proxy_provider_health_check(&name) - .await - ) -} - -/// 更新代理提供者 -#[tauri::command] -pub async fn update_proxy_provider(name: String) -> CmdResult { - wrap_err!(IpcManager::global().update_proxy_provider(&name).await) -} - -/// 更新规则提供者 -#[tauri::command] -pub async fn update_rule_provider(name: String) -> CmdResult { - wrap_err!(IpcManager::global().update_rule_provider(&name).await) -} - -/// 获取连接 -#[tauri::command] -pub async fn get_clash_connections() -> CmdResult { - wrap_err!(IpcManager::global().get_connections().await) -} - -/// 删除连接 -#[tauri::command] -pub async fn delete_clash_connection(id: String) -> CmdResult { - wrap_err!(IpcManager::global().delete_connection(&id).await) -} - -/// 关闭所有连接 -#[tauri::command] -pub async fn close_all_clash_connections() -> CmdResult { - wrap_err!(IpcManager::global().close_all_connections().await) -} - -/// 获取流量数据 (使用新的IPC流式监控) -#[tauri::command] -pub async fn get_traffic_data() -> CmdResult { - let traffic = crate::ipc::get_current_traffic().await; - let result = serde_json::json!({ - "up": traffic.total_up, - "down": traffic.total_down, - "up_rate": traffic.up_rate, - "down_rate": traffic.down_rate, - "last_updated": traffic.last_updated.elapsed().as_secs() - }); - Ok(result) -} - -/// 获取内存数据 (使用新的IPC流式监控) -#[tauri::command] -pub async fn get_memory_data() -> CmdResult { - let memory = crate::ipc::get_current_memory().await; - let usage_percent = if memory.oslimit > 0 { - (memory.inuse as f64 / memory.oslimit as f64) * 100.0 - } else { - 0.0 +pub async fn get_clash_logs() -> CmdResult> { + let logs = match core::CoreManager::global().get_running_mode() { + // TODO: 服务模式下日志获取接口 + RunningMode::Service => VecDeque::new(), + RunningMode::Sidecar => logger::Logger::global().get_logs().clone(), + _ => VecDeque::new(), }; - let result = serde_json::json!({ - "inuse": memory.inuse, - "oslimit": memory.oslimit, - "usage_percent": usage_percent, - "last_updated": memory.last_updated.elapsed().as_secs() - }); - Ok(result) -} - -/// 启动流量监控服务 (IPC流式监控自动启动,此函数为兼容性保留) -#[tauri::command] -pub async fn start_traffic_service() -> CmdResult { - logging!(trace, Type::Ipc, "启动流量监控服务 (IPC流式监控)"); - // 新的IPC监控在首次访问时自动启动 - // 触发一次访问以确保监控器已初始化 - let _ = crate::ipc::get_current_traffic().await; - let _ = crate::ipc::get_current_memory().await; - logging!(info, Type::Ipc, "IPC流式监控已激活"); - Ok(()) -} - -/// 停止流量监控服务 (IPC流式监控无需显式停止,此函数为兼容性保留) -#[tauri::command] -pub async fn stop_traffic_service() -> CmdResult { - logging!(trace, Type::Ipc, "停止流量监控服务请求 (IPC流式监控)"); - // 新的IPC监控是持久的,无需显式停止 - logging!(info, Type::Ipc, "IPC流式监控继续运行"); - Ok(()) -} - -/// 获取格式化的流量数据 (包含单位,便于前端显示) -#[tauri::command] -pub async fn get_formatted_traffic_data() -> CmdResult { - logging!(trace, Type::Ipc, "获取格式化流量数据"); - let (up_rate, down_rate, total_up, total_down, is_fresh) = - crate::ipc::get_formatted_traffic().await; - let result = serde_json::json!({ - "up_rate_formatted": up_rate, - "down_rate_formatted": down_rate, - "total_up_formatted": total_up, - "total_down_formatted": total_down, - "is_fresh": is_fresh - }); - logging!( - debug, - Type::Ipc, - "格式化流量数据: ↑{up_rate}/s ↓{down_rate}/s (总计: ↑{total_up} ↓{total_down})" - ); - Ok(result) -} - -/// 获取格式化的内存数据 (包含单位,便于前端显示) -#[tauri::command] -pub async fn get_formatted_memory_data() -> CmdResult { - logging!(info, Type::Ipc, "获取格式化内存数据"); - let (inuse, oslimit, usage_percent, is_fresh) = crate::ipc::get_formatted_memory().await; - let result = serde_json::json!({ - "inuse_formatted": inuse, - "oslimit_formatted": oslimit, - "usage_percent": usage_percent, - "is_fresh": is_fresh - }); - logging!( - debug, - Type::Ipc, - "格式化内存数据: {inuse} / {oslimit} ({usage_percent:.1}%)" - ); - Ok(result) -} - -/// 获取系统监控概览 (流量+内存,便于前端一次性获取所有状态) -#[tauri::command] -pub async fn get_system_monitor_overview() -> CmdResult { - logging!(debug, Type::Ipc, "获取系统监控概览"); - - // 并发获取流量和内存数据 - let (traffic, memory) = tokio::join!( - crate::ipc::get_current_traffic(), - crate::ipc::get_current_memory() - ); - - let (traffic_formatted, memory_formatted) = tokio::join!( - crate::ipc::get_formatted_traffic(), - crate::ipc::get_formatted_memory() - ); - - let traffic_is_fresh = traffic.last_updated.elapsed().as_secs() < 5; - let memory_is_fresh = memory.last_updated.elapsed().as_secs() < 10; - - let result = serde_json::json!({ - "traffic": { - "raw": { - "up": traffic.total_up, - "down": traffic.total_down, - "up_rate": traffic.up_rate, - "down_rate": traffic.down_rate - }, - "formatted": { - "up_rate": traffic_formatted.0, - "down_rate": traffic_formatted.1, - "total_up": traffic_formatted.2, - "total_down": traffic_formatted.3 - }, - "is_fresh": traffic_is_fresh - }, - "memory": { - "raw": { - "inuse": memory.inuse, - "oslimit": memory.oslimit, - "usage_percent": if memory.oslimit > 0 { - (memory.inuse as f64 / memory.oslimit as f64) * 100.0 - } else { - 0.0 - } - }, - "formatted": { - "inuse": memory_formatted.0, - "oslimit": memory_formatted.1, - "usage_percent": memory_formatted.2 - }, - "is_fresh": memory_is_fresh - }, - "overall_status": if traffic_is_fresh && memory_is_fresh { "healthy" } else { "stale" } - }); - - Ok(result) -} - -/// 获取代理组延迟 -#[tauri::command] -pub async fn get_group_proxy_delays( - group_name: String, - url: Option, - timeout: Option, -) -> CmdResult { - wrap_err!( - IpcManager::global() - .get_group_proxy_delays(&group_name, url, timeout.unwrap_or(10000)) - .await - ) -} - -/// 检查调试是否启用 -#[tauri::command] -pub async fn is_clash_debug_enabled() -> CmdResult { - match IpcManager::global().is_debug_enabled().await { - Ok(enabled) => Ok(enabled), - Err(_) => Ok(false), - } -} - -/// 垃圾回收 -#[tauri::command] -pub async fn clash_gc() -> CmdResult { - wrap_err!(IpcManager::global().gc().await) -} - -/// 获取日志 (使用新的流式实现) -#[tauri::command] -pub async fn get_clash_logs() -> CmdResult { - Ok(ipc::get_logs_json().await) -} - -/// 启动日志监控 -#[tauri::command] -pub async fn start_logs_monitoring(level: Option) -> CmdResult { - ipc::start_logs_monitoring(level).await; - Ok(()) -} - -/// 停止日志监控 -#[tauri::command] -pub async fn stop_logs_monitoring() -> CmdResult { - ipc::stop_logs_monitoring().await; - Ok(()) -} - -/// 清除日志 -#[tauri::command] -pub async fn clear_logs() -> CmdResult { - ipc::clear_logs().await; - Ok(()) + Ok(logs) } diff --git a/clash-verge-rev/src-tauri/src/cmd/profile.rs b/clash-verge-rev/src-tauri/src/cmd/profile.rs index d76dc00b51..d1d2b55b32 100644 --- a/clash-verge-rev/src-tauri/src/cmd/profile.rs +++ b/clash-verge-rev/src-tauri/src/cmd/profile.rs @@ -38,11 +38,11 @@ pub async fn get_profiles() -> CmdResult { match latest_result { Ok(profiles) => { - logging!(info, Type::Cmd, false, "快速获取配置列表成功"); + logging!(info, Type::Cmd, "快速获取配置列表成功"); return Ok(profiles); } Err(_) => { - logging!(warn, Type::Cmd, true, "快速获取配置超时(500ms)"); + logging!(warn, Type::Cmd, "快速获取配置超时(500ms)"); } } @@ -59,14 +59,13 @@ pub async fn get_profiles() -> CmdResult { match data_result { Ok(profiles) => { - logging!(info, Type::Cmd, false, "获取draft配置列表成功"); + logging!(info, Type::Cmd, "获取draft配置列表成功"); return Ok(profiles); } Err(join_err) => { logging!( error, Type::Cmd, - true, "获取draft配置任务失败或超时: {}", join_err ); @@ -74,12 +73,7 @@ pub async fn get_profiles() -> CmdResult { } // 策略3: fallback,尝试重新创建配置 - logging!( - warn, - Type::Cmd, - true, - "所有获取配置策略都失败,尝试fallback" - ); + logging!(warn, Type::Cmd, "所有获取配置策略都失败,尝试fallback"); Ok(IProfiles::new().await) } @@ -101,11 +95,11 @@ pub async fn enhance_profiles() -> CmdResult { /// 导入配置文件 #[tauri::command] pub async fn import_profile(url: String, option: Option) -> CmdResult { - logging!(info, Type::Cmd, true, "[导入订阅] 开始导入: {}", url); + logging!(info, Type::Cmd, "[导入订阅] 开始导入: {}", url); let import_result = tokio::time::timeout(Duration::from_secs(60), async { let item = PrfItem::from_url(&url, None, None, option).await?; - logging!(info, Type::Cmd, true, "[导入订阅] 下载完成,开始保存配置"); + logging!(info, Type::Cmd, "[导入订阅] 下载完成,开始保存配置"); let profiles = Config::profiles().await; let pre_count = profiles @@ -123,19 +117,13 @@ pub async fn import_profile(url: String, option: Option) -> CmdResult .as_ref() .map_or(0, |items| items.len()); if post_count <= pre_count { - logging!( - error, - Type::Cmd, - true, - "[导入订阅] 配置未增加,导入可能失败" - ); + logging!(error, Type::Cmd, "[导入订阅] 配置未增加,导入可能失败"); return Err(anyhow::anyhow!("配置导入后数量未增加")); } logging!( info, Type::Cmd, - true, "[导入订阅] 配置保存成功,数量: {} -> {}", pre_count, post_count @@ -143,13 +131,7 @@ pub async fn import_profile(url: String, option: Option) -> CmdResult // 立即发送配置变更通知 if let Some(uid) = &item.uid { - logging!( - info, - Type::Cmd, - true, - "[导入订阅] 发送配置变更通知: {}", - uid - ); + logging!(info, Type::Cmd, "[导入订阅] 发送配置变更通知: {}", uid); handle::Handle::notify_profile_changed(uid.clone()); } @@ -158,9 +140,9 @@ pub async fn import_profile(url: String, option: Option) -> CmdResult crate::process::AsyncHandler::spawn(move || async move { // 使用Send-safe helper函数 if let Err(e) = profiles_save_file_safe().await { - logging!(error, Type::Cmd, true, "[导入订阅] 保存配置文件失败: {}", e); + logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e); } else { - logging!(info, Type::Cmd, true, "[导入订阅] 配置文件保存成功"); + logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功"); // 发送全局配置更新通知 if let Some(uid) = uid_clone { @@ -177,15 +159,15 @@ pub async fn import_profile(url: String, option: Option) -> CmdResult match import_result { Ok(Ok(())) => { - logging!(info, Type::Cmd, true, "[导入订阅] 导入完成: {}", url); + logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", url); Ok(()) } Ok(Err(e)) => { - logging!(error, Type::Cmd, true, "[导入订阅] 导入失败: {}", e); + logging!(error, Type::Cmd, "[导入订阅] 导入失败: {}", e); Err(format!("导入订阅失败: {e}")) } Err(_) => { - logging!(error, Type::Cmd, true, "[导入订阅] 导入超时(60秒): {}", url); + logging!(error, Type::Cmd, "[导入订阅] 导入超时(60秒): {}", url); Err("导入订阅超时,请检查网络连接".into()) } } @@ -210,8 +192,15 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult { /// 创建一个新的配置文件 #[tauri::command] pub async fn create_profile(item: PrfItem, file_data: Option) -> CmdResult { - match profiles_append_item_with_filedata_safe(item, file_data).await { - Ok(_) => Ok(()), + match profiles_append_item_with_filedata_safe(item.clone(), file_data).await { + Ok(_) => { + // 发送配置变更通知 + if let Some(uid) = &item.uid { + logging!(info, Type::Cmd, "[创建订阅] 发送配置变更通知: {}", uid); + handle::Handle::notify_profile_changed(uid.clone()); + } + Ok(()) + } Err(err) => match err.to_string().as_str() { "the file already exists" => Err("the file already exists".into()), _ => Err(format!("add profile error: {err}")), @@ -235,12 +224,15 @@ pub async fn update_profile(index: String, option: Option) -> CmdResu #[tauri::command] pub async fn delete_profile(index: String) -> CmdResult { // 使用Send-safe helper函数 - let should_update = wrap_err!(profiles_delete_item_safe(index).await)?; + let should_update = wrap_err!(profiles_delete_item_safe(index.clone()).await)?; if should_update { match CoreManager::global().update_config().await { Ok(_) => { handle::Handle::refresh_clash(); + // 发送配置变更通知 + logging!(info, Type::Cmd, "[删除订阅] 发送配置变更通知: {}", index); + handle::Handle::notify_profile_changed(index); } Err(e) => { log::error!(target: "app", "{}", e); @@ -255,7 +247,7 @@ pub async fn delete_profile(index: String) -> CmdResult { #[tauri::command] pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { if CURRENT_SWITCHING_PROFILE.load(Ordering::SeqCst) { - logging!(info, Type::Cmd, true, "当前正在切换配置,放弃请求"); + logging!(info, Type::Cmd, "当前正在切换配置,放弃请求"); return Ok(false); } CURRENT_SWITCHING_PROFILE.store(true, Ordering::SeqCst); @@ -267,7 +259,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "开始修改配置文件,请求序列号: {}, 目标profile: {:?}", current_sequence, target_profile @@ -278,7 +269,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "获取锁后发现更新的请求 (序列号: {} < {}),放弃当前请求", current_sequence, latest_sequence @@ -288,13 +278,13 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { // 保存当前配置,以便在验证失败时恢复 let current_profile = Config::profiles().await.latest_ref().current.clone(); - logging!(info, Type::Cmd, true, "当前配置: {:?}", current_profile); + logging!(info, Type::Cmd, "当前配置: {:?}", current_profile); // 如果要切换配置,先检查目标配置文件是否有语法错误 if let Some(new_profile) = profiles.current.as_ref() && current_profile.as_ref() != Some(new_profile) { - logging!(info, Type::Cmd, true, "正在切换到新配置: {}", new_profile); + logging!(info, Type::Cmd, "正在切换到新配置: {}", new_profile); // 获取目标配置文件路径 let config_file_result = { @@ -310,7 +300,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { } } Err(e) => { - logging!(error, Type::Cmd, true, "获取目标配置信息失败: {}", e); + logging!(error, Type::Cmd, "获取目标配置信息失败: {}", e); None } } @@ -322,7 +312,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( error, Type::Cmd, - true, "目标配置文件不存在: {}", file_path.display() ); @@ -349,14 +338,13 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { match yaml_parse_result { Ok(Ok(_)) => { - logging!(info, Type::Cmd, true, "目标配置文件语法正确"); + logging!(info, Type::Cmd, "目标配置文件语法正确"); } Ok(Err(err)) => { let error_msg = format!(" {err}"); logging!( error, Type::Cmd, - true, "目标配置文件存在YAML语法错误:{}", error_msg ); @@ -368,7 +356,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { } Err(join_err) => { let error_msg = format!("YAML解析任务失败: {join_err}"); - logging!(error, Type::Cmd, true, "{}", error_msg); + logging!(error, Type::Cmd, "{}", error_msg); handle::Handle::notice_message( "config_validate::yaml_parse_error", &error_msg, @@ -379,13 +367,13 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { } Ok(Err(err)) => { let error_msg = format!("无法读取目标配置文件: {err}"); - logging!(error, Type::Cmd, true, "{}", error_msg); + logging!(error, Type::Cmd, "{}", error_msg); handle::Handle::notice_message("config_validate::file_read_error", &error_msg); return Ok(false); } Err(_) => { let error_msg = "读取配置文件超时(5秒)".to_string(); - logging!(error, Type::Cmd, true, "{}", error_msg); + logging!(error, Type::Cmd, "{}", error_msg); handle::Handle::notice_message( "config_validate::file_read_timeout", &error_msg, @@ -402,7 +390,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "在核心操作前发现更新的请求 (序列号: {} < {}),放弃当前请求", current_sequence, latest_sequence @@ -414,7 +401,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "正在更新配置草稿,序列号: {}", current_sequence ); @@ -429,7 +415,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "在内核交互前发现更新的请求 (序列号: {} < {}),放弃当前请求", current_sequence, latest_sequence @@ -442,7 +427,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "开始内核配置更新,序列号: {}", current_sequence ); @@ -461,7 +445,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "内核操作后发现更新的请求 (序列号: {} < {}),忽略当前结果", current_sequence, latest_sequence @@ -473,7 +456,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "配置更新成功,序列号: {}", current_sequence ); @@ -481,11 +463,11 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { handle::Handle::refresh_clash(); // 强制刷新代理缓存,确保profile切换后立即获取最新节点数据 - crate::process::AsyncHandler::spawn(|| async move { - if let Err(e) = super::proxy::force_refresh_proxies().await { - log::warn!(target: "app", "强制刷新代理缓存失败: {e}"); - } - }); + // crate::process::AsyncHandler::spawn(|| async move { + // if let Err(e) = super::proxy::force_refresh_proxies().await { + // log::warn!(target: "app", "强制刷新代理缓存失败: {e}"); + // } + // }); if let Err(e) = Tray::global().update_tooltip().await { log::warn!(target: "app", "异步更新托盘提示失败: {e}"); @@ -505,7 +487,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "向前端发送配置变更事件: {}, 序列号: {}", current, current_sequence @@ -517,17 +498,11 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { Ok(true) } Ok(Ok((false, error_msg))) => { - logging!(warn, Type::Cmd, true, "配置验证失败: {}", error_msg); + logging!(warn, Type::Cmd, "配置验证失败: {}", error_msg); Config::profiles().await.discard(); // 如果验证失败,恢复到之前的配置 if let Some(prev_profile) = current_profile { - logging!( - info, - Type::Cmd, - true, - "尝试恢复到之前的配置: {}", - prev_profile - ); + logging!(info, Type::Cmd, "尝试恢复到之前的配置: {}", prev_profile); let restore_profiles = IProfiles { current: Some(prev_profile), items: None, @@ -547,7 +522,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { } }); - logging!(info, Type::Cmd, true, "成功恢复到之前的配置"); + logging!(info, Type::Cmd, "成功恢复到之前的配置"); } // 发送验证错误通知 @@ -559,7 +534,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( warn, Type::Cmd, - true, "更新过程发生错误: {}, 序列号: {}", e, current_sequence @@ -576,7 +550,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( error, Type::Cmd, - true, "{}, 序列号: {}", timeout_msg, current_sequence @@ -587,7 +560,6 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { logging!( info, Type::Cmd, - true, "超时后尝试恢复到之前的配置: {}, 序列号: {}", prev_profile, current_sequence @@ -615,7 +587,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { /// 根据profile name修改profiles #[tauri::command] pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> CmdResult { - logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index); + logging!(info, Type::Cmd, "切换配置到: {}", profile_index); let profiles = IProfiles { current: Some(profile_index), diff --git a/clash-verge-rev/src-tauri/src/cmd/proxy.rs b/clash-verge-rev/src-tauri/src/cmd/proxy.rs index 387da3ad7d..a5b34cf46c 100644 --- a/clash-verge-rev/src-tauri/src/cmd/proxy.rs +++ b/clash-verge-rev/src-tauri/src/cmd/proxy.rs @@ -1,59 +1,7 @@ -use tauri::Emitter; - use super::CmdResult; -use crate::{ - cache::CacheProxy, - core::{handle::Handle, tray::Tray}, - ipc::IpcManager, - logging, - utils::logging::Type, -}; -use std::time::Duration; - -const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(60); -const PROVIDERS_REFRESH_INTERVAL: Duration = Duration::from_secs(60); - -#[tauri::command] -pub async fn get_proxies() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("proxies", "default"); - let value = cache - .get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async { - let manager = IpcManager::global(); - manager.get_proxies().await.unwrap_or_else(|e| { - logging!(error, Type::Cmd, "Failed to fetch proxies: {e}"); - serde_json::Value::Object(serde_json::Map::new()) - }) - }) - .await; - Ok((*value).clone()) -} - -/// 强制刷新代理缓存用于profile切换 -#[tauri::command] -pub async fn force_refresh_proxies() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("proxies", "default"); - cache.map.remove(&key); - get_proxies().await -} - -#[tauri::command] -pub async fn get_providers_proxies() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("providers", "default"); - let value = cache - .get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async { - let manager = IpcManager::global(); - manager.get_providers_proxies().await.unwrap_or_else(|e| { - logging!(error, Type::Cmd, "Failed to fetch provider proxies: {e}"); - serde_json::Value::Object(serde_json::Map::new()) - }) - }) - .await; - Ok((*value).clone()) -} +use crate::{logging, utils::logging::Type}; +// TODO: 前端通过 emit 发送更新事件, tray 监听更新事件 /// 同步托盘和GUI的代理选择状态 #[tauri::command] pub async fn sync_tray_proxy_selection() -> CmdResult<()> { @@ -70,54 +18,3 @@ pub async fn sync_tray_proxy_selection() -> CmdResult<()> { } } } - -/// 更新代理选择并同步托盘和GUI状态 -#[tauri::command] -pub async fn update_proxy_and_sync(group: String, proxy: String) -> CmdResult<()> { - match IpcManager::global().update_proxy(&group, &proxy).await { - Ok(_) => { - // println!("Proxy updated successfully: {} -> {}", group,proxy); - logging!( - info, - Type::Cmd, - "Proxy updated successfully: {} -> {}", - group, - proxy - ); - - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("proxies", "default"); - cache.map.remove(&key); - - if let Err(e) = Tray::global().update_menu().await { - logging!(error, Type::Cmd, "Failed to sync tray menu: {}", e); - } - - if let Some(app_handle) = Handle::global().app_handle() { - let _ = app_handle.emit("verge://force-refresh-proxies", ()); - let _ = app_handle.emit("verge://refresh-proxy-config", ()); - } - - logging!( - info, - Type::Cmd, - "Proxy and sync completed successfully: {} -> {}", - group, - proxy - ); - Ok(()) - } - Err(e) => { - println!("1111111111111111"); - logging!( - error, - Type::Cmd, - "Failed to update proxy: {} -> {}, error: {}", - group, - proxy, - e - ); - Err(e.to_string()) - } - } -} diff --git a/clash-verge-rev/src-tauri/src/cmd/save_profile.rs b/clash-verge-rev/src-tauri/src/cmd/save_profile.rs index b1f75455f2..7a1b39cd72 100644 --- a/clash-verge-rev/src-tauri/src/cmd/save_profile.rs +++ b/clash-verge-rev/src-tauri/src/cmd/save_profile.rs @@ -36,7 +36,6 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR logging!( info, Type::Config, - true, "[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file @@ -47,7 +46,6 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR logging!( info, Type::Config, - true, "[cmd配置save] 检测到merge文件,只进行语法验证" ); match CoreManager::global() @@ -55,12 +53,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR .await { Ok((true, _)) => { - logging!( - info, - Type::Config, - true, - "[cmd配置save] merge文件语法验证通过" - ); + logging!(info, Type::Config, "[cmd配置save] merge文件语法验证通过"); // 成功后尝试更新整体配置 match CoreManager::global().update_config().await { Ok(_) => { @@ -71,7 +64,6 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR logging!( warn, Type::Config, - true, "[cmd配置save] 更新整体配置时发生错误: {}", e ); @@ -83,7 +75,6 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR logging!( warn, Type::Config, - true, "[cmd配置save] merge文件语法验证失败: {}", error_msg ); @@ -95,13 +86,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR return Ok(()); } Err(e) => { - logging!( - error, - Type::Config, - true, - "[cmd配置save] 验证过程发生错误: {}", - e - ); + logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e); // 恢复原始配置文件 wrap_err!(fs::write(&file_path, original_content).await)?; return Err(e.to_string()); @@ -115,17 +100,11 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR .await { Ok((true, _)) => { - logging!(info, Type::Config, true, "[cmd配置save] 验证成功"); + logging!(info, Type::Config, "[cmd配置save] 验证成功"); Ok(()) } Ok((false, error_msg)) => { - logging!( - warn, - Type::Config, - true, - "[cmd配置save] 验证失败: {}", - error_msg - ); + logging!(warn, Type::Config, "[cmd配置save] 验证失败: {}", error_msg); // 恢复原始配置文件 wrap_err!(fs::write(&file_path, original_content).await)?; @@ -169,13 +148,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR Ok(()) } Err(e) => { - logging!( - error, - Type::Config, - true, - "[cmd配置save] 验证过程发生错误: {}", - e - ); + logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e); // 恢复原始配置文件 wrap_err!(fs::write(&file_path, original_content).await)?; Err(e.to_string()) diff --git a/clash-verge-rev/src-tauri/src/cmd/service.rs b/clash-verge-rev/src-tauri/src/cmd/service.rs index efb4070de6..01abdc8141 100644 --- a/clash-verge-rev/src-tauri/src/cmd/service.rs +++ b/clash-verge-rev/src-tauri/src/cmd/service.rs @@ -1,9 +1,6 @@ use super::CmdResult; use crate::{ - core::{ - CoreManager, - service::{self, SERVICE_MANAGER, ServiceStatus}, - }, + core::service::{self, SERVICE_MANAGER, ServiceStatus}, utils::i18n::t, }; @@ -17,10 +14,6 @@ async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> let emsg = format!("{} Service failed: {}", op_type, e); return Err(t(emsg.as_str()).await); } - if CoreManager::global().restart_core().await.is_err() { - let emsg = "Restart Core failed"; - return Err(t(emsg).await); - } Ok(()) } diff --git a/clash-verge-rev/src-tauri/src/cmd/system.rs b/clash-verge-rev/src-tauri/src/cmd/system.rs index c3d2ab4125..2ef6cf723a 100644 --- a/clash-verge-rev/src-tauri/src/cmd/system.rs +++ b/clash-verge-rev/src-tauri/src/cmd/system.rs @@ -28,9 +28,7 @@ pub async fn export_diagnostic_info() -> CmdResult<()> { let sysinfo = PlatformSpecification::new_sync(); let info = format!("{sysinfo:?}"); - let app_handle = handle::Handle::global() - .app_handle() - .ok_or("Failed to get app handle")?; + let app_handle = handle::Handle::app_handle(); let cliboard = app_handle.clipboard(); if cliboard.write_text(info).is_err() { logging!(error, Type::System, "Failed to write to clipboard"); diff --git a/clash-verge-rev/src-tauri/src/cmd/validate.rs b/clash-verge-rev/src-tauri/src/cmd/validate.rs index 7275ac074a..d4ccfefae9 100644 --- a/clash-verge-rev/src-tauri/src/cmd/validate.rs +++ b/clash-verge-rev/src-tauri/src/cmd/validate.rs @@ -28,14 +28,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) "config_validate::script_error" }; - logging!( - warn, - Type::Config, - true, - "{} 验证失败: {}", - file_type, - error_msg - ); + logging!(warn, Type::Config, "{} 验证失败: {}", file_type, error_msg); handle::Handle::notice_message(status, error_msg); } } @@ -43,7 +36,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) /// 验证指定脚本文件 #[tauri::command] pub async fn validate_script_file(file_path: String) -> CmdResult { - logging!(info, Type::Config, true, "验证脚本文件: {}", file_path); + logging!(info, Type::Config, "验证脚本文件: {}", file_path); match CoreManager::global() .validate_config_file(&file_path, None) @@ -58,7 +51,6 @@ pub async fn validate_script_file(file_path: String) -> CmdResult { logging!( error, Type::Config, - true, "验证脚本文件过程发生错误: {}", error_msg ); @@ -76,7 +68,6 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) { logging!( info, Type::Config, - true, "[通知] 处理{}验证错误: {}", file_type, error_msg @@ -117,18 +108,10 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) { } }; - logging!( - warn, - Type::Config, - true, - "{} 验证失败: {}", - file_type, - error_msg - ); + logging!(warn, Type::Config, "{} 验证失败: {}", file_type, error_msg); logging!( info, Type::Config, - true, "[通知] 发送通知: status={}, msg={}", status, error_msg diff --git a/clash-verge-rev/src-tauri/src/config/clash.rs b/clash-verge-rev/src-tauri/src/config/clash.rs index a46874e067..f99fa3d8d8 100644 --- a/clash-verge-rev/src-tauri/src/config/clash.rs +++ b/clash-verge-rev/src-tauri/src/config/clash.rs @@ -51,6 +51,9 @@ impl IClashTemp { let mut tun = Mapping::new(); let mut cors_map = Mapping::new(); tun.insert("enable".into(), false.into()); + #[cfg(target_os = "linux")] + tun.insert("stack".into(), "mixed".into()); + #[cfg(not(target_os = "linux"))] tun.insert("stack".into(), "gvisor".into()); tun.insert("auto-route".into(), true.into()); tun.insert("strict-route".into(), false.into()); diff --git a/clash-verge-rev/src-tauri/src/config/config.rs b/clash-verge-rev/src-tauri/src/config/config.rs index 9b065b88ba..5416e542e7 100644 --- a/clash-verge-rev/src-tauri/src/config/config.rs +++ b/clash-verge-rev/src-tauri/src/config/config.rs @@ -6,9 +6,11 @@ use crate::{ utils::{dirs, help, logging::Type}, }; use anyhow::{Result, anyhow}; +use backoff::{Error as BackoffError, ExponentialBackoff}; use std::path::PathBuf; +use std::time::Duration; use tokio::sync::OnceCell; -use tokio::time::{Duration, sleep}; +use tokio::time::sleep; pub const RUNTIME_CONFIG: &str = "clash-verge.yaml"; pub const CHECK_CONFIG: &str = "clash-verge-check.yaml"; @@ -73,9 +75,9 @@ impl Config { } // 生成运行时配置 if let Err(err) = Self::generate().await { - logging!(error, Type::Config, true, "生成运行时配置失败: {}", err); + logging!(error, Type::Config, "生成运行时配置失败: {}", err); } else { - logging!(info, Type::Config, true, "生成运行时配置成功"); + logging!(info, Type::Config, "生成运行时配置成功"); } // 生成运行时配置文件并验证 @@ -83,7 +85,7 @@ impl Config { let validation_result = if config_result.is_ok() { // 验证配置文件 - logging!(info, Type::Config, true, "开始验证配置"); + logging!(info, Type::Config, "开始验证配置"); match CoreManager::global().validate_config().await { Ok((is_valid, error_msg)) => { @@ -91,7 +93,6 @@ impl Config { logging!( warn, Type::Config, - true, "[首次启动] 配置验证失败,使用默认最小配置启动: {}", error_msg ); @@ -100,14 +101,14 @@ impl Config { .await?; Some(("config_validate::boot_error", error_msg)) } else { - logging!(info, Type::Config, true, "配置验证成功"); + logging!(info, Type::Config, "配置验证成功"); // 前端没有必要知道验证成功的消息,也没有事件驱动 // Some(("config_validate::success", String::new())) None } } Err(err) => { - logging!(warn, Type::Config, true, "验证进程执行失败: {}", err); + logging!(warn, Type::Config, "验证过程执行失败: {}", err); CoreManager::global() .use_default_config("config_validate::process_terminated", "") .await?; @@ -115,7 +116,7 @@ impl Config { } } } else { - logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置"); + logging!(warn, Type::Config, "生成配置文件失败,使用默认配置"); CoreManager::global() .use_default_config("config_validate::error", "") .await?; @@ -163,6 +164,64 @@ impl Config { Ok(()) } + + pub async fn verify_config_initialization() { + logging!(info, Type::Setup, "Verifying config initialization..."); + + let backoff_strategy = ExponentialBackoff { + initial_interval: std::time::Duration::from_millis(100), + max_interval: std::time::Duration::from_secs(2), + max_elapsed_time: Some(std::time::Duration::from_secs(10)), + multiplier: 2.0, + ..Default::default() + }; + + let operation = || async { + if Config::runtime().await.latest_ref().config.is_some() { + logging!( + info, + Type::Setup, + "Config initialization verified successfully" + ); + return Ok::<(), BackoffError>(()); + } + + logging!( + warn, + Type::Setup, + "Runtime config not found, attempting to regenerate..." + ); + + match Config::generate().await { + Ok(_) => { + logging!(info, Type::Setup, "Config successfully regenerated"); + Ok(()) + } + Err(e) => { + logging!(warn, Type::Setup, "Failed to generate config: {}", e); + Err(BackoffError::transient(e)) + } + } + }; + + match backoff::future::retry(backoff_strategy, operation).await { + Ok(_) => { + logging!( + info, + Type::Setup, + "Config initialization verified with backoff retry" + ); + } + Err(e) => { + logging!( + error, + Type::Setup, + "Failed to verify config initialization after retries: {}", + e + ); + } + } + } } #[derive(Debug)] diff --git a/clash-verge-rev/src-tauri/src/config/profiles.rs b/clash-verge-rev/src-tauri/src/config/profiles.rs index 0c1e268dac..2faab96f99 100644 --- a/clash-verge-rev/src-tauri/src/config/profiles.rs +++ b/clash-verge-rev/src-tauri/src/config/profiles.rs @@ -297,7 +297,6 @@ impl IProfiles { if let Err(err) = result { logging_error!( Type::Config, - false, "[配置文件删除] 删除文件 {} 失败: {}", path.display(), err @@ -323,7 +322,6 @@ impl IProfiles { if let Err(err) = result { logging_error!( Type::Config, - false, "[配置文件删除] 删除文件 {} 失败: {}", path.display(), err @@ -349,7 +347,6 @@ impl IProfiles { if let Err(err) = result { logging_error!( Type::Config, - false, "[配置文件删除] 删除文件 {} 失败: {}", path.display(), err @@ -375,7 +372,6 @@ impl IProfiles { if let Err(err) = result { logging_error!( Type::Config, - false, "[配置文件删除] 删除文件 {} 失败: {}", path.display(), err @@ -401,7 +397,6 @@ impl IProfiles { if let Err(err) = result { logging_error!( Type::Config, - false, "[配置文件删除] 删除文件 {} 失败: {}", path.display(), err @@ -427,7 +422,6 @@ impl IProfiles { if let Err(err) = result { logging_error!( Type::Config, - false, "[配置文件删除] 删除文件 {} 失败: {}", path.display(), err diff --git a/clash-verge-rev/src-tauri/src/config/verge.rs b/clash-verge-rev/src-tauri/src/config/verge.rs index 4a02bc9743..7d312acdff 100644 --- a/clash-verge-rev/src-tauri/src/config/verge.rs +++ b/clash-verge-rev/src-tauri/src/config/verge.rs @@ -255,7 +255,6 @@ impl IVerge { logging!( warn, Type::Config, - true, "启动时发现无效的clash_core配置: '{}', 将自动修正为 'verge-mihomo'", core ); @@ -266,7 +265,6 @@ impl IVerge { logging!( info, Type::Config, - true, "启动时发现未配置clash_core, 将设置为默认值 'verge-mihomo'" ); config.clash_core = Some("verge-mihomo".to_string()); @@ -275,21 +273,15 @@ impl IVerge { // 修正后保存配置 if needs_fix { - logging!(info, Type::Config, true, "正在保存修正后的配置文件..."); + logging!(info, Type::Config, "正在保存修正后的配置文件..."); help::save_yaml(&config_path, &config, Some("# Clash Verge Config")).await?; - logging!( - info, - Type::Config, - true, - "配置文件修正完成,需要重新加载配置" - ); + logging!(info, Type::Config, "配置文件修正完成,需要重新加载配置"); Self::reload_config_after_fix(config).await?; } else { logging!( info, Type::Config, - true, "clash_core配置验证通过: {:?}", config.clash_core ); @@ -309,7 +301,6 @@ impl IVerge { logging!( info, Type::Config, - true, "内存配置已强制更新,新的clash_core: {:?}", updated_config.clash_core ); diff --git a/clash-verge-rev/src-tauri/src/core/backup.rs b/clash-verge-rev/src-tauri/src/core/backup.rs index 3f9cfba42a..5f5555cd01 100644 --- a/clash-verge-rev/src-tauri/src/core/backup.rs +++ b/clash-verge-rev/src-tauri/src/core/backup.rs @@ -127,13 +127,24 @@ impl WebDavClient { .set_auth(reqwest_dav::Auth::Basic(config.username, config.password)) .build()?; - // 尝试检查目录是否存在,如果不存在尝试创建,但创建失败不报错 + // 尝试检查目录是否存在,如果不存在尝试创建 if client .list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0)) .await .is_err() { - let _ = client.mkcol(dirs::BACKUP_DIR).await; + match client.mkcol(dirs::BACKUP_DIR).await { + Ok(_) => log::info!("Successfully created backup directory"), + Err(e) => { + log::warn!("Failed to create backup directory: {}", e); + // 清除缓存,强制下次重新尝试 + self.reset(); + return Err(anyhow::Error::msg(format!( + "Failed to create backup directory: {}", + e + ))); + } + } } // 缓存客户端 diff --git a/clash-verge-rev/src-tauri/src/core/core.rs b/clash-verge-rev/src-tauri/src/core/core.rs index ff1e1cc0d2..fe81e1ee1d 100644 --- a/clash-verge-rev/src-tauri/src/core/core.rs +++ b/clash-verge-rev/src-tauri/src/core/core.rs @@ -1,11 +1,14 @@ use crate::AsyncHandler; +use crate::core::logger::Logger; +use crate::process::CommandChildGuard; +use crate::utils::init::sidecar_writer; +use crate::utils::logging::SharedWriter; use crate::{ config::*, core::{ handle, service::{self, SERVICE_MANAGER, ServiceStatus}, }, - ipc::IpcManager, logging, logging_error, singleton_lazy, utils::{ dirs, @@ -14,21 +17,21 @@ use crate::{ }, }; use anyhow::Result; -use chrono::Local; +use flexi_logger::DeferredNow; +use flexi_logger::writers::LogWriter; +use log::Record; use parking_lot::Mutex; -use std::{ - fmt, - fs::{File, create_dir_all}, - io::Write, - path::PathBuf, - sync::Arc, -}; -use tauri_plugin_shell::{ShellExt, process::CommandChild}; +use std::{fmt, path::PathBuf, sync::Arc}; +use tauri_plugin_shell::ShellExt; + +// TODO: +// - 重构,提升模式切换速度 +// - 内核启动添加启动 IPC 启动参数, `-ext-ctl-unix` / `-ext-ctl-pipe`, 运行时配置需要删除相关配置项 #[derive(Debug)] pub struct CoreManager { running: Arc>, - child_sidecar: Arc>>, + child_sidecar: Arc>>, } /// 内核运行模式 @@ -54,6 +57,29 @@ impl fmt::Display for RunningMode { use crate::config::IVerge; +fn write_sidecar_log( + writer: &dyn LogWriter, + now: &mut DeferredNow, + level: log::Level, + message: String, +) -> String { + let boxed = message.into_boxed_str(); + let leaked: &'static mut str = Box::leak(boxed); + let leaked_ptr = leaked as *mut str; + { + let _ = writer.write( + now, + &Record::builder() + .args(format_args!("{}", &*leaked)) + .level(level) + .target("sidecar") + .build(), + ); + } + // SAFETY: `leaked` originated from `Box::leak` above; reboxing frees it immediately after use. + unsafe { String::from(Box::from_raw(leaked_ptr)) } +} + impl CoreManager { /// 检查文件是否为脚本文件 fn is_script_file(&self, path: &str) -> Result { @@ -71,7 +97,6 @@ impl CoreManager { logging!( warn, Type::Config, - true, "无法读取文件以检测类型: {}, 错误: {}", path, err @@ -129,7 +154,6 @@ impl CoreManager { logging!( debug, Type::Config, - true, "无法确定文件类型,默认当作YAML处理: {}", path ); @@ -153,7 +177,7 @@ impl CoreManager { } /// 验证运行时配置 pub async fn validate_config(&self) -> Result<(bool, String)> { - logging!(info, Type::Config, true, "生成临时配置文件用于验证"); + logging!(info, Type::Config, "生成临时配置文件用于验证"); let config_path = Config::generate_file(ConfigType::Check).await?; let config_path = dirs::path_to_str(&config_path)?; self.validate_config_internal(config_path).await @@ -166,7 +190,7 @@ impl CoreManager { ) -> Result<(bool, String)> { // 检查程序是否正在退出,如果是则跳过验证 if handle::Handle::global().is_exiting() { - logging!(info, Type::Core, true, "应用正在退出,跳过验证"); + logging!(info, Type::Core, "应用正在退出,跳过验证"); return Ok((true, String::new())); } @@ -182,7 +206,6 @@ impl CoreManager { logging!( info, Type::Config, - true, "检测到Merge文件,仅进行语法检查: {}", config_path ); @@ -200,7 +223,6 @@ impl CoreManager { logging!( warn, Type::Config, - true, "无法确定文件类型: {}, 错误: {}", config_path, err @@ -214,7 +236,6 @@ impl CoreManager { logging!( info, Type::Config, - true, "检测到脚本文件,使用JavaScript验证: {}", config_path ); @@ -225,7 +246,6 @@ impl CoreManager { logging!( info, Type::Config, - true, "使用Clash内核验证配置文件: {}", config_path ); @@ -235,29 +255,19 @@ impl CoreManager { async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> { // 检查程序是否正在退出,如果是则跳过验证 if handle::Handle::global().is_exiting() { - logging!(info, Type::Core, true, "应用正在退出,跳过验证"); + logging!(info, Type::Core, "应用正在退出,跳过验证"); return Ok((true, String::new())); } - logging!( - info, - Type::Config, - true, - "开始验证配置文件: {}", - config_path - ); + logging!(info, Type::Config, "开始验证配置文件: {}", config_path); let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); - logging!(info, Type::Config, true, "使用内核: {}", clash_core); + logging!(info, Type::Config, "使用内核: {}", clash_core); - let app_handle = handle::Handle::global().app_handle().ok_or_else(|| { - let msg = "Failed to get app handle"; - logging!(error, Type::Core, true, "{}", msg); - anyhow::anyhow!(msg) - })?; + let app_handle = handle::Handle::app_handle(); let app_dir = dirs::app_home_dir()?; let app_dir_str = dirs::path_to_str(&app_dir)?; - logging!(info, Type::Config, true, "验证目录: {}", app_dir_str); + logging!(info, Type::Config, "验证目录: {}", app_dir_str); // 使用子进程运行clash验证配置 let output = app_handle @@ -275,14 +285,14 @@ impl CoreManager { let has_error = !output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw)); - logging!(info, Type::Config, true, "-------- 验证结果 --------"); + logging!(info, Type::Config, "-------- 验证结果 --------"); if !stderr.is_empty() { - logging!(info, Type::Config, true, "stderr输出:\n{}", stderr); + logging!(info, Type::Config, "stderr输出:\n{}", stderr); } if has_error { - logging!(info, Type::Config, true, "发现错误,开始处理错误信息"); + logging!(info, Type::Config, "发现错误,开始处理错误信息"); let error_msg = if !stdout.is_empty() { stdout.to_string() } else if !stderr.is_empty() { @@ -293,38 +303,38 @@ impl CoreManager { "验证进程被终止".to_string() }; - logging!(info, Type::Config, true, "-------- 验证结束 --------"); + logging!(info, Type::Config, "-------- 验证结束 --------"); Ok((false, error_msg)) // 返回错误消息给调用者处理 } else { - logging!(info, Type::Config, true, "验证成功"); - logging!(info, Type::Config, true, "-------- 验证结束 --------"); + logging!(info, Type::Config, "验证成功"); + logging!(info, Type::Config, "-------- 验证结束 --------"); Ok((true, String::new())) } } /// 只进行文件语法检查,不进行完整验证 fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> { - logging!(info, Type::Config, true, "开始检查文件: {}", config_path); + logging!(info, Type::Config, "开始检查文件: {}", config_path); // 读取文件内容 let content = match std::fs::read_to_string(config_path) { Ok(content) => content, Err(err) => { let error_msg = format!("Failed to read file: {err}"); - logging!(error, Type::Config, true, "无法读取文件: {}", error_msg); + logging!(error, Type::Config, "无法读取文件: {}", error_msg); return Ok((false, error_msg)); } }; // 对YAML文件尝试解析,只检查语法正确性 - logging!(info, Type::Config, true, "进行YAML语法检查"); + logging!(info, Type::Config, "进行YAML语法检查"); match serde_yaml_ng::from_str::(&content) { Ok(_) => { - logging!(info, Type::Config, true, "YAML语法检查通过"); + logging!(info, Type::Config, "YAML语法检查通过"); Ok((true, String::new())) } Err(err) => { // 使用标准化的前缀,以便错误处理函数能正确识别 let error_msg = format!("YAML syntax error: {err}"); - logging!(error, Type::Config, true, "YAML语法错误: {}", error_msg); + logging!(error, Type::Config, "YAML语法错误: {}", error_msg); Ok((false, error_msg)) } } @@ -336,13 +346,13 @@ impl CoreManager { Ok(content) => content, Err(err) => { let error_msg = format!("Failed to read script file: {err}"); - logging!(warn, Type::Config, true, "脚本语法错误: {}", err); + logging!(warn, Type::Config, "脚本语法错误: {}", err); //handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg); return Ok((false, error_msg)); } }; - logging!(debug, Type::Config, true, "验证脚本文件: {}", path); + logging!(debug, Type::Config, "验证脚本文件: {}", path); // 使用boa引擎进行基本语法检查 use boa_engine::{Context, Source}; @@ -352,7 +362,7 @@ impl CoreManager { match result { Ok(_) => { - logging!(debug, Type::Config, true, "脚本语法验证通过: {}", path); + logging!(debug, Type::Config, "脚本语法验证通过: {}", path); // 检查脚本是否包含main函数 if !content.contains("function main") @@ -360,7 +370,7 @@ impl CoreManager { && !content.contains("let main") { let error_msg = "Script must contain a main function"; - logging!(warn, Type::Config, true, "脚本缺少main函数: {}", path); + logging!(warn, Type::Config, "脚本缺少main函数: {}", path); //handle::Handle::notice_message("config_validate::script_missing_main", error_msg); return Ok((false, error_msg.to_string())); } @@ -369,7 +379,7 @@ impl CoreManager { } Err(err) => { let error_msg = format!("Script syntax error: {err}"); - logging!(warn, Type::Config, true, "脚本语法错误: {}", err); + logging!(warn, Type::Config, "脚本语法错误: {}", err); //handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg); Ok((false, error_msg)) } @@ -379,30 +389,30 @@ impl CoreManager { pub async fn update_config(&self) -> Result<(bool, String)> { // 检查程序是否正在退出,如果是则跳过完整验证流程 if handle::Handle::global().is_exiting() { - logging!(info, Type::Config, true, "应用正在退出,跳过验证"); + logging!(info, Type::Config, "应用正在退出,跳过验证"); return Ok((true, String::new())); } // 1. 先生成新的配置内容 - logging!(info, Type::Config, true, "生成新的配置内容"); + logging!(info, Type::Config, "生成新的配置内容"); Config::generate().await?; // 2. 验证配置 match self.validate_config().await { Ok((true, _)) => { // 4. 验证通过后,生成正式的运行时配置 - logging!(info, Type::Config, true, "配置验证通过, 生成运行时配置"); + logging!(info, Type::Config, "配置验证通过, 生成运行时配置"); let run_path = Config::generate_file(ConfigType::Run).await?; - logging_error!(Type::Config, true, self.put_configs_force(run_path).await); + logging_error!(Type::Config, self.put_configs_force(run_path).await); Ok((true, "something".into())) } Ok((false, error_msg)) => { - logging!(warn, Type::Config, true, "配置验证失败: {}", error_msg); + logging!(warn, Type::Config, "配置验证失败: {}", error_msg); Config::runtime().await.discard(); Ok((false, error_msg)) } Err(e) => { - logging!(warn, Type::Config, true, "验证过程发生错误: {}", e); + logging!(warn, Type::Config, "验证过程发生错误: {}", e); Config::runtime().await.discard(); Err(e) } @@ -411,19 +421,23 @@ impl CoreManager { pub async fn put_configs_force(&self, path_buf: PathBuf) -> Result<(), String> { let run_path_str = dirs::path_to_str(&path_buf).map_err(|e| { let msg = e.to_string(); - logging_error!(Type::Core, true, "{}", msg); + logging_error!(Type::Core, "{}", msg); msg }); - match IpcManager::global().put_configs_force(run_path_str?).await { + match handle::Handle::mihomo() + .await + .reload_config(true, run_path_str?) + .await + { Ok(_) => { Config::runtime().await.apply(); - logging!(info, Type::Core, true, "Configuration updated successfully"); + logging!(info, Type::Core, "Configuration updated successfully"); Ok(()) } Err(e) => { let msg = e.to_string(); Config::runtime().await.discard(); - logging_error!(Type::Core, true, "Failed to update configuration: {}", msg); + logging_error!(Type::Core, "Failed to update configuration: {}", msg); Err(msg) } } @@ -433,7 +447,7 @@ impl CoreManager { impl CoreManager { /// 清理多余的 mihomo 进程 async fn cleanup_orphaned_mihomo_processes(&self) -> Result<()> { - logging!(info, Type::Core, true, "开始清理多余的 mihomo 进程"); + logging!(info, Type::Core, "开始清理多余的 mihomo 进程"); // 获取当前管理的进程 PID let current_pid = { @@ -464,12 +478,11 @@ impl CoreManager { for pid in pids { // 跳过当前管理的进程 if let Some(current) = current_pid - && pid == current + && Some(pid) == current { logging!( debug, Type::Core, - true, "跳过当前管理的进程: {} (PID: {})", process_name, pid @@ -480,13 +493,13 @@ impl CoreManager { } } Err(e) => { - logging!(debug, Type::Core, true, "查找进程时发生错误: {}", e); + logging!(debug, Type::Core, "查找进程时发生错误: {}", e); } } } if pids_to_kill.is_empty() { - logging!(debug, Type::Core, true, "未发现多余的 mihomo 进程"); + logging!(debug, Type::Core, "未发现多余的 mihomo 进程"); return Ok(()); } @@ -503,7 +516,6 @@ impl CoreManager { logging!( info, Type::Core, - true, "清理完成,共终止了 {} 个多余的 mihomo 进程", killed_count ); @@ -612,7 +624,6 @@ impl CoreManager { logging!( info, Type::Core, - true, "尝试终止进程: {} (PID: {})", process_name, pid @@ -659,7 +670,6 @@ impl CoreManager { logging!( warn, Type::Core, - true, "进程 {} (PID: {}) 终止命令成功但进程仍在运行", process_name, pid @@ -669,7 +679,6 @@ impl CoreManager { logging!( info, Type::Core, - true, "成功终止进程: {} (PID: {})", process_name, pid @@ -680,7 +689,6 @@ impl CoreManager { logging!( warn, Type::Core, - true, "无法终止进程: {} (PID: {})", process_name, pid @@ -730,23 +738,13 @@ impl CoreManager { } async fn start_core_by_sidecar(&self) -> Result<()> { - logging!(info, Type::Core, true, "Running core by sidecar"); + logging!(info, Type::Core, "Running core by sidecar"); let config_file = &Config::generate_file(ConfigType::Run).await?; - let app_handle = handle::Handle::global() - .app_handle() - .ok_or(anyhow::anyhow!("failed to get app handle"))?; + let app_handle = handle::Handle::app_handle(); let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); let config_dir = dirs::app_home_dir()?; - let service_log_dir = dirs::app_home_dir()?.join("logs").join("service"); - create_dir_all(&service_log_dir)?; - - let now = Local::now(); - let timestamp = now.format("%Y%m%d_%H%M%S").to_string(); - - let log_path = service_log_dir.join(format!("sidecar_{timestamp}.log")); - let (mut rx, child) = app_handle .shell() .sidecar(&clash_core)? @@ -759,30 +757,39 @@ impl CoreManager { .spawn()?; let pid = child.pid(); - logging!( - trace, - Type::Core, - true, - "Started core by sidecar pid: {}", - pid - ); - *self.child_sidecar.lock() = Some(child); + logging!(trace, Type::Core, "Started core by sidecar pid: {}", pid); + *self.child_sidecar.lock() = Some(CommandChildGuard::new(child)); self.set_running_mode(RunningMode::Sidecar); - let mut log_file = std::io::BufWriter::new(File::create(log_path)?); + let shared_writer: SharedWriter = + Arc::new(tokio::sync::Mutex::new(sidecar_writer().await?)); + AsyncHandler::spawn(|| async move { while let Some(event) = rx.recv().await { + let w = shared_writer.lock().await; match event { tauri_plugin_shell::process::CommandEvent::Stdout(line) => { - if let Err(e) = writeln!(log_file, "{}", String::from_utf8_lossy(&line)) { - eprintln!("[Sidecar] write stdout failed: {e}"); - } + let mut now = DeferredNow::default(); + let message = String::from_utf8_lossy(&line).into_owned(); + let message = write_sidecar_log(&*w, &mut now, log::Level::Error, message); + Logger::global().append_log(message); } tauri_plugin_shell::process::CommandEvent::Stderr(line) => { - let _ = writeln!(log_file, "[stderr] {}", String::from_utf8_lossy(&line)); + let mut now = DeferredNow::default(); + let message = String::from_utf8_lossy(&line).into_owned(); + let message = write_sidecar_log(&*w, &mut now, log::Level::Error, message); + Logger::global().append_log(message); } tauri_plugin_shell::process::CommandEvent::Terminated(term) => { - let _ = writeln!(log_file, "[terminated] {:?}", term); + let mut now = DeferredNow::default(); + let message = if let Some(code) = term.code { + format!("Process terminated with code: {}", code) + } else if let Some(signal) = term.signal { + format!("Process terminated by signal: {}", signal) + } else { + "Process terminated".to_string() + }; + write_sidecar_log(&*w, &mut now, log::Level::Info, message); break; } _ => {} @@ -793,18 +800,12 @@ impl CoreManager { Ok(()) } fn stop_core_by_sidecar(&self) -> Result<()> { - logging!(info, Type::Core, true, "Stopping core by sidecar"); + logging!(info, Type::Core, "Stopping core by sidecar"); if let Some(child) = self.child_sidecar.lock().take() { let pid = child.pid(); - child.kill()?; - logging!( - trace, - Type::Core, - true, - "Stopped core by sidecar pid: {}", - pid - ); + drop(child); + logging!(trace, Type::Core, "Stopped core by sidecar pid: {:?}", pid); } self.set_running_mode(RunningMode::NotRunning); Ok(()) @@ -813,14 +814,14 @@ impl CoreManager { impl CoreManager { async fn start_core_by_service(&self) -> Result<()> { - logging!(info, Type::Core, true, "Running core by service"); + logging!(info, Type::Core, "Running core by service"); let config_file = &Config::generate_file(ConfigType::Run).await?; service::run_core_by_service(config_file).await?; self.set_running_mode(RunningMode::Service); Ok(()) } async fn stop_core_by_service(&self) -> Result<()> { - logging!(info, Type::Core, true, "Stopping core by service"); + logging!(info, Type::Core, "Stopping core by service"); service::stop_core_by_service().await?; self.set_running_mode(RunningMode::NotRunning); Ok(()) @@ -845,17 +846,16 @@ impl CoreManager { logging!( warn, Type::Core, - true, "应用初始化时清理多余 mihomo 进程失败: {}", e ); } // 使用简化的启动流程 - logging!(info, Type::Core, true, "开始核心初始化"); + logging!(info, Type::Core, "开始核心初始化"); self.start_core().await?; - logging!(info, Type::Core, true, "核心初始化完成"); + logging!(info, Type::Core, "核心初始化完成"); Ok(()) } @@ -870,7 +870,6 @@ impl CoreManager { } pub async fn prestart_core(&self) -> Result<()> { - SERVICE_MANAGER.lock().await.refresh().await?; match SERVICE_MANAGER.lock().await.current() { ServiceStatus::Ready => { self.set_running_mode(RunningMode::Service); @@ -888,10 +887,10 @@ impl CoreManager { match self.get_running_mode() { RunningMode::Service => { - logging_error!(Type::Core, true, self.start_core_by_service().await); + logging_error!(Type::Core, self.start_core_by_service().await); } RunningMode::NotRunning | RunningMode::Sidecar => { - logging_error!(Type::Core, true, self.start_core_by_sidecar().await); + logging_error!(Type::Core, self.start_core_by_sidecar().await); } }; @@ -900,6 +899,7 @@ impl CoreManager { /// 停止核心运行 pub async fn stop_core(&self) -> Result<()> { + Logger::global().clear_logs(); match self.get_running_mode() { RunningMode::Service => self.stop_core_by_service().await, RunningMode::Sidecar => self.stop_core_by_sidecar(), @@ -909,8 +909,11 @@ impl CoreManager { /// 重启内核 pub async fn restart_core(&self) -> Result<()> { - logging!(info, Type::Core, true, "Restarting core"); + logging!(info, Type::Core, "Restarting core"); self.stop_core().await?; + if SERVICE_MANAGER.lock().await.init().await.is_ok() { + logging_error!(Type::Setup, SERVICE_MANAGER.lock().await.refresh().await); + } self.start_core().await?; Ok(()) } @@ -919,17 +922,17 @@ impl CoreManager { pub async fn change_core(&self, clash_core: Option) -> Result<(), String> { if clash_core.is_none() { let error_message = "Clash core should not be Null"; - logging!(error, Type::Core, true, "{}", error_message); + logging!(error, Type::Core, "{}", error_message); return Err(error_message.to_string()); } let core = clash_core.as_ref().ok_or_else(|| { let msg = "Clash core should not be None"; - logging!(error, Type::Core, true, "{}", msg); + logging!(error, Type::Core, "{}", msg); msg.to_string() })?; if !IVerge::VALID_CLASH_CORES.contains(&core.as_str()) { let error_message = format!("Clash core invalid name: {core}"); - logging!(error, Type::Core, true, "{}", error_message); + logging!(error, Type::Core, "{}", error_message); return Err(error_message); } @@ -938,11 +941,11 @@ impl CoreManager { // 分离数据获取和异步调用避免Send问题 let verge_data = Config::verge().await.latest_ref().clone(); - logging_error!(Type::Core, true, verge_data.save_file().await); + logging_error!(Type::Core, verge_data.save_file().await); let run_path = Config::generate_file(ConfigType::Run).await.map_err(|e| { let msg = e.to_string(); - logging_error!(Type::Core, true, "{}", msg); + logging_error!(Type::Core, "{}", msg); msg })?; diff --git a/clash-verge-rev/src-tauri/src/core/event_driven_proxy.rs b/clash-verge-rev/src-tauri/src/core/event_driven_proxy.rs index 71bf2ea60b..43efc3dac8 100644 --- a/clash-verge-rev/src-tauri/src/core/event_driven_proxy.rs +++ b/clash-verge-rev/src-tauri/src/core/event_driven_proxy.rs @@ -5,7 +5,7 @@ use tokio::time::{Duration, sleep, timeout}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use crate::config::{Config, IVerge}; -use crate::core::async_proxy_query::AsyncProxyQuery; +use crate::core::{async_proxy_query::AsyncProxyQuery, handle}; use crate::logging_error; use crate::process::AsyncHandler; use crate::utils::logging::Type; @@ -85,6 +85,7 @@ struct ProxyConfig { sys_enabled: bool, pac_enabled: bool, guard_enabled: bool, + guard_duration: u64, } static PROXY_MANAGER: Lazy = Lazy::new(EventDrivenProxyManager::new); @@ -184,16 +185,40 @@ impl EventDrivenProxyManager { let mut event_stream = UnboundedReceiverStream::new(event_rx); let mut query_stream = UnboundedReceiverStream::new(query_rx); + // 初始化定时器,用于周期性检查代理设置 + let config = Self::get_proxy_config().await; + let mut guard_interval = tokio::time::interval(Duration::from_secs(config.guard_duration)); + // 防止首次立即触发 + guard_interval.tick().await; + loop { tokio::select! { Some(event) = event_stream.next() => { log::debug!(target: "app", "处理代理事件: {event:?}"); + let event_clone = event.clone(); // 保存一份副本用于后续检查 Self::handle_event(&state, event).await; + + // 检查是否是配置变更事件,如果是,则可能需要更新定时器 + if matches!(event_clone, ProxyEvent::ConfigChanged | ProxyEvent::AppStarted) { + let new_config = Self::get_proxy_config().await; + // 重新设置定时器间隔 + guard_interval = tokio::time::interval(Duration::from_secs(new_config.guard_duration)); + // 防止首次立即触发 + guard_interval.tick().await; + } } Some(query) = query_stream.next() => { let result = Self::handle_query(&state).await; let _ = query.response_tx.send(result); } + _ = guard_interval.tick() => { + // 定时检查代理设置 + let config = Self::get_proxy_config().await; + if config.guard_enabled && config.sys_enabled { + log::debug!(target: "app", "定时检查代理设置"); + Self::check_and_restore_proxy(&state).await; + } + } else => { // 两个通道都关闭时退出 log::info!(target: "app", "事件或查询通道关闭,代理管理器停止"); @@ -225,6 +250,12 @@ impl EventDrivenProxyManager { } ProxyEvent::AppStopping => { log::info!(target: "app", "清理代理状态"); + Self::update_state_timestamp(state, |s| { + s.sys_enabled = false; + s.pac_enabled = false; + s.is_healthy = false; + }) + .await; } } } @@ -276,6 +307,10 @@ impl EventDrivenProxyManager { } async fn check_and_restore_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过系统代理守卫检查"); + return; + } let (sys_enabled, pac_enabled) = { let s = state.read().await; (s.sys_enabled, s.pac_enabled) @@ -295,6 +330,11 @@ impl EventDrivenProxyManager { } async fn check_and_restore_pac_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过PAC代理恢复检查"); + return; + } + let current = Self::get_auto_proxy_with_timeout().await; let expected = Self::get_expected_pac_config().await; @@ -321,6 +361,11 @@ impl EventDrivenProxyManager { } async fn check_and_restore_sys_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过系统代理恢复检查"); + return; + } + let current = Self::get_sys_proxy_with_timeout().await; let expected = Self::get_expected_sys_proxy().await; @@ -349,6 +394,11 @@ impl EventDrivenProxyManager { } async fn enable_system_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过启用系统代理"); + return; + } + log::info!(target: "app", "启用系统代理"); let pac_enabled = state.read().await.pac_enabled; @@ -376,17 +426,22 @@ impl EventDrivenProxyManager { let disabled_sys = Sysproxy::default(); let disabled_auto = Autoproxy::default(); - logging_error!(Type::System, true, disabled_auto.set_auto_proxy()); - logging_error!(Type::System, true, disabled_sys.set_system_proxy()); + logging_error!(Type::System, disabled_auto.set_auto_proxy()); + logging_error!(Type::System, disabled_sys.set_system_proxy()); } } async fn switch_proxy_mode(state: &Arc>, to_pac: bool) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过代理模式切换"); + return; + } + log::info!(target: "app", "切换到{}模式", if to_pac { "PAC" } else { "HTTP代理" }); if to_pac { let disabled_sys = Sysproxy::default(); - logging_error!(Type::System, true, disabled_sys.set_system_proxy()); + logging_error!(Type::System, disabled_sys.set_system_proxy()); let expected = Self::get_expected_pac_config().await; if let Err(e) = Self::restore_pac_proxy(&expected.url).await { @@ -394,7 +449,7 @@ impl EventDrivenProxyManager { } } else { let disabled_auto = Autoproxy::default(); - logging_error!(Type::System, true, disabled_auto.set_auto_proxy()); + logging_error!(Type::System, disabled_auto.set_auto_proxy()); let expected = Self::get_expected_sys_proxy().await; if let Err(e) = Self::restore_sys_proxy(&expected).await { @@ -439,19 +494,21 @@ impl EventDrivenProxyManager { } async fn get_proxy_config() -> ProxyConfig { - let (sys_enabled, pac_enabled, guard_enabled) = { + let (sys_enabled, pac_enabled, guard_enabled, guard_duration) = { let verge_config = Config::verge().await; let verge = verge_config.latest_ref(); ( verge.enable_system_proxy.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false), verge.enable_proxy_guard.unwrap_or(false), + verge.proxy_guard_duration.unwrap_or(30), // 默认30秒 ) }; ProxyConfig { sys_enabled, pac_enabled, guard_enabled, + guard_duration, } } @@ -518,6 +575,10 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn restore_pac_proxy(expected_url: &str) -> Result<(), anyhow::Error> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过PAC代理恢复"); + return Ok(()); + } Self::execute_sysproxy_command(&["pac", expected_url]).await } @@ -538,6 +599,10 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn restore_sys_proxy(expected: &Sysproxy) -> Result<(), anyhow::Error> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过系统代理恢复"); + return Ok(()); + } let address = format!("{}:{}", expected.host, expected.port); Self::execute_sysproxy_command(&["global", &address, &expected.bypass]).await } @@ -555,6 +620,15 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn execute_sysproxy_command(args: &[&str]) -> Result<(), anyhow::Error> { + if handle::Handle::global().is_exiting() { + log::debug!( + target: "app", + "应用正在退出,取消调用 sysproxy.exe,参数: {:?}", + args + ); + return Ok(()); + } + use crate::utils::dirs; #[allow(unused_imports)] // creation_flags必须 use std::os::windows::process::CommandExt; diff --git a/clash-verge-rev/src-tauri/src/core/handle.rs b/clash-verge-rev/src-tauri/src/core/handle.rs index c0f4eddbd9..1c36ef6c49 100644 --- a/clash-verge-rev/src-tauri/src/core/handle.rs +++ b/clash-verge-rev/src-tauri/src/core/handle.rs @@ -1,4 +1,4 @@ -use crate::singleton; +use crate::{APP_HANDLE, singleton}; use parking_lot::RwLock; use std::{ sync::{ @@ -10,6 +10,8 @@ use std::{ time::{Duration, Instant}, }; use tauri::{AppHandle, Emitter, Manager, WebviewWindow}; +use tauri_plugin_mihomo::{Mihomo, MihomoExt}; +use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use crate::{logging, utils::logging::Type}; @@ -107,7 +109,7 @@ impl NotificationSystem { continue; } - if let Some(window) = handle.get_window() { + if let Some(window) = Handle::get_window() { *system.last_emit_time.write() = Instant::now(); let (event_name_str, payload_result) = match event { @@ -249,7 +251,6 @@ impl NotificationSystem { #[derive(Debug, Clone)] pub struct Handle { - pub app_handle: Arc>>, pub is_exiting: Arc>, startup_errors: Arc>>, startup_completed: Arc>, @@ -259,7 +260,6 @@ pub struct Handle { impl Default for Handle { fn default() -> Self { Self { - app_handle: Arc::new(RwLock::new(None)), is_exiting: Arc::new(RwLock::new(false)), startup_errors: Arc::new(RwLock::new(Vec::new())), startup_completed: Arc::new(RwLock::new(false)), @@ -276,25 +276,41 @@ impl Handle { Self::default() } - pub fn init(&self, app_handle: AppHandle) { - { - let mut handle = self.app_handle.write(); - *handle = Some(app_handle); + pub fn init(&self) { + // 如果正在退出,不要重新初始化 + if self.is_exiting() { + log::debug!("Handle::init called while exiting, skipping initialization"); + return; } let mut system_opt = self.notification_system.write(); if let Some(system) = system_opt.as_mut() { - system.start(); + // 只在未运行时启动 + if !system.is_running { + system.start(); + } else { + log::debug!("NotificationSystem already running, skipping start"); + } } } /// 获取 AppHandle - pub fn app_handle(&self) -> Option { - self.app_handle.read().clone() + #[allow(clippy::expect_used)] + pub fn app_handle() -> &'static AppHandle { + APP_HANDLE.get().expect("failed to get global app handle") } - pub fn get_window(&self) -> Option { - let app_handle = self.app_handle()?; + pub async fn mihomo() -> RwLockReadGuard<'static, Mihomo> { + Self::app_handle().mihomo().read().await + } + + #[allow(unused)] + pub async fn mihomo_mut() -> RwLockWriteGuard<'static, Mihomo> { + Self::app_handle().mihomo().write().await + } + + pub fn get_window() -> Option { + let app_handle = Self::app_handle(); let window: Option = app_handle.get_webview_window("main"); if window.is_none() { log::debug!(target:"app", "main window not found"); @@ -402,7 +418,6 @@ impl Handle { logging!( info, Type::Frontend, - true, "启动过程中发现错误,加入消息队列: {} - {}", status_str, msg_str @@ -452,7 +467,6 @@ impl Handle { logging!( info, Type::Frontend, - true, "发送{}条启动时累积的错误消息: {:?}", errors.len(), errors @@ -509,14 +523,10 @@ impl Handle { #[cfg(target_os = "macos")] impl Handle { pub fn set_activation_policy(&self, policy: tauri::ActivationPolicy) -> Result<(), String> { - let app_handle = self.app_handle(); - if let Some(app_handle) = app_handle.as_ref() { - app_handle - .set_activation_policy(policy) - .map_err(|e| e.to_string()) - } else { - Err("AppHandle not initialized".to_string()) - } + let app_handle = Self::app_handle(); + app_handle + .set_activation_policy(policy) + .map_err(|e| e.to_string()) } pub fn set_activation_policy_regular(&self) { @@ -524,7 +534,6 @@ impl Handle { logging!( warn, Type::Setup, - true, "Failed to set regular activation policy: {}", e ); @@ -536,7 +545,6 @@ impl Handle { logging!( warn, Type::Setup, - true, "Failed to set accessory activation policy: {}", e ); @@ -549,7 +557,6 @@ impl Handle { logging!( warn, Type::Setup, - true, "Failed to set prohibited activation policy: {}", e ); diff --git a/clash-verge-rev/src-tauri/src/core/hotkey.rs b/clash-verge-rev/src-tauri/src/core/hotkey.rs index 67258242d3..f527d4b11a 100755 --- a/clash-verge-rev/src-tauri/src/core/hotkey.rs +++ b/clash-verge-rev/src-tauri/src/core/hotkey.rs @@ -200,9 +200,7 @@ impl Hotkey { hotkey: &str, function: HotkeyFunction, ) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; + let app_handle = handle::Handle::app_handle(); let manager = app_handle.global_shortcut(); logging!( @@ -296,7 +294,6 @@ impl Hotkey { logging!( debug, Type::Hotkey, - true, "Initializing global hotkeys: {}", enable_global_hotkey ); @@ -312,7 +309,6 @@ impl Hotkey { logging!( debug, Type::Hotkey, - true, "Has {} hotkeys need to register", hotkeys.len() ); @@ -327,7 +323,6 @@ impl Hotkey { logging!( debug, Type::Hotkey, - true, "Registering hotkey: {} -> {}", key, func @@ -336,7 +331,6 @@ impl Hotkey { logging!( error, Type::Hotkey, - true, "Failed to register hotkey {} -> {}: {:?}", key, func, @@ -358,7 +352,6 @@ impl Hotkey { logging!( error, Type::Hotkey, - true, "Invalid hotkey configuration: `{}`:`{}`", key, func @@ -375,9 +368,7 @@ impl Hotkey { } pub fn reset(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; + let app_handle = handle::Handle::app_handle(); let manager = app_handle.global_shortcut(); manager.unregister_all()?; Ok(()) @@ -390,9 +381,7 @@ impl Hotkey { } pub fn unregister(&self, hotkey: &str) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; + let app_handle = handle::Handle::app_handle(); let manager = app_handle.global_shortcut(); manager.unregister(hotkey)?; logging!(debug, Type::Hotkey, "Unregister hotkey {}", hotkey); @@ -468,22 +457,11 @@ impl Hotkey { impl Drop for Hotkey { fn drop(&mut self) { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - logging!( - error, - Type::Hotkey, - "Failed to get app handle during hotkey cleanup" - ); - return; - } - }; + let app_handle = handle::Handle::app_handle(); if let Err(e) = app_handle.global_shortcut().unregister_all() { logging!( error, Type::Hotkey, - true, "Error unregistering all hotkeys: {:?}", e ); diff --git a/clash-verge-rev/src-tauri/src/core/logger.rs b/clash-verge-rev/src-tauri/src/core/logger.rs new file mode 100644 index 0000000000..7eb30479c0 --- /dev/null +++ b/clash-verge-rev/src-tauri/src/core/logger.rs @@ -0,0 +1,37 @@ +use std::{collections::VecDeque, sync::Arc}; + +use once_cell::sync::OnceCell; +use parking_lot::{RwLock, RwLockReadGuard}; + +const LOGS_QUEUE_LEN: usize = 100; + +pub struct Logger { + logs: Arc>>, +} + +impl Logger { + pub fn global() -> &'static Logger { + static LOGGER: OnceCell = OnceCell::new(); + + LOGGER.get_or_init(|| Logger { + logs: Arc::new(RwLock::new(VecDeque::with_capacity(LOGS_QUEUE_LEN + 10))), + }) + } + + pub fn get_logs(&self) -> RwLockReadGuard<'_, VecDeque> { + self.logs.read() + } + + pub fn append_log(&self, text: String) { + let mut logs = self.logs.write(); + if logs.len() > LOGS_QUEUE_LEN { + logs.pop_front(); + } + logs.push_back(text); + } + + pub fn clear_logs(&self) { + let mut logs = self.logs.write(); + logs.clear(); + } +} diff --git a/clash-verge-rev/src-tauri/src/core/mod.rs b/clash-verge-rev/src-tauri/src/core/mod.rs index 044abaf9c5..2a01fe23a4 100644 --- a/clash-verge-rev/src-tauri/src/core/mod.rs +++ b/clash-verge-rev/src-tauri/src/core/mod.rs @@ -5,8 +5,8 @@ mod core; pub mod event_driven_proxy; pub mod handle; pub mod hotkey; +pub mod logger; pub mod service; -pub mod service_ipc; pub mod sysopt; pub mod timer; pub mod tray; diff --git a/clash-verge-rev/src-tauri/src/core/service.rs b/clash-verge-rev/src-tauri/src/core/service.rs index 3312f6c069..0a752cedc8 100644 --- a/clash-verge-rev/src-tauri/src/core/service.rs +++ b/clash-verge-rev/src-tauri/src/core/service.rs @@ -1,17 +1,19 @@ use crate::{ - cache::{CacheService, SHORT_TERM_TTL}, config::Config, - core::service_ipc::{IpcCommand, send_ipc_request}, logging, logging_error, - utils::{dirs, logging::Type}, + utils::{dirs, init::service_writer_config, logging::Type}, }; use anyhow::{Context, Result, bail}; +use clash_verge_service_ipc::CoreConfig; use once_cell::sync::Lazy; -use std::{env::current_exe, path::PathBuf, process::Command as StdCommand}; +use std::{ + env::current_exe, + path::{Path, PathBuf}, + process::Command as StdCommand, + time::Duration, +}; use tokio::sync::Mutex; -const REQUIRED_SERVICE_VERSION: &str = "1.1.2"; // 定义所需的服务版本号 - #[derive(Debug, Clone, PartialEq, Eq)] pub enum ServiceStatus { Ready, @@ -29,14 +31,14 @@ pub struct ServiceManager(ServiceStatus); #[allow(clippy::unused_async)] #[cfg(target_os = "windows")] async fn uninstall_service() -> Result<()> { - logging!(info, Type::Service, true, "uninstall service"); + logging!(info, Type::Service, "uninstall service"); use deelevate::{PrivilegeLevel, Token}; use runas::Command as RunasCommand; use std::os::windows::process::CommandExt; let binary_path = dirs::service_path()?; - let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); + let uninstall_path = binary_path.with_file_name("clash-verge-service-uninstall.exe"); if !uninstall_path.exists() { bail!(format!("uninstaller not found: {uninstall_path:?}")); @@ -64,14 +66,14 @@ async fn uninstall_service() -> Result<()> { #[allow(clippy::unused_async)] #[cfg(target_os = "windows")] async fn install_service() -> Result<()> { - logging!(info, Type::Service, true, "install service"); + logging!(info, Type::Service, "install service"); use deelevate::{PrivilegeLevel, Token}; use runas::Command as RunasCommand; use std::os::windows::process::CommandExt; let binary_path = dirs::service_path()?; - let install_path = binary_path.with_file_name("install-service.exe"); + let install_path = binary_path.with_file_name("clash-verge-service-install.exe"); if !install_path.exists() { bail!(format!("installer not found: {install_path:?}")); @@ -98,17 +100,11 @@ async fn install_service() -> Result<()> { #[cfg(target_os = "windows")] async fn reinstall_service() -> Result<()> { - logging!(info, Type::Service, true, "reinstall service"); + logging!(info, Type::Service, "reinstall service"); // 先卸载服务 if let Err(err) = uninstall_service().await { - logging!( - warn, - Type::Service, - true, - "failed to uninstall service: {}", - err - ); + logging!(warn, Type::Service, "failed to uninstall service: {}", err); } // 再安装服务 @@ -123,10 +119,11 @@ async fn reinstall_service() -> Result<()> { #[allow(clippy::unused_async)] #[cfg(target_os = "linux")] async fn uninstall_service() -> Result<()> { - logging!(info, Type::Service, true, "uninstall service"); + logging!(info, Type::Service, "uninstall service"); use users::get_effective_uid; - let uninstall_path = tauri::utils::platform::current_exe()?.with_file_name("uninstall-service"); + let uninstall_path = + tauri::utils::platform::current_exe()?.with_file_name("clash-verge-service-uninstall"); if !uninstall_path.exists() { bail!(format!("uninstaller not found: {uninstall_path:?}")); @@ -146,7 +143,6 @@ async fn uninstall_service() -> Result<()> { logging!( info, Type::Service, - true, "uninstall status code:{}", status.code().unwrap_or(-1) ); @@ -164,10 +160,11 @@ async fn uninstall_service() -> Result<()> { #[cfg(target_os = "linux")] #[allow(clippy::unused_async)] async fn install_service() -> Result<()> { - logging!(info, Type::Service, true, "install service"); + logging!(info, Type::Service, "install service"); use users::get_effective_uid; - let install_path = tauri::utils::platform::current_exe()?.with_file_name("install-service"); + let install_path = + tauri::utils::platform::current_exe()?.with_file_name("clash-verge-service-install"); if !install_path.exists() { bail!(format!("installer not found: {install_path:?}")); @@ -187,7 +184,6 @@ async fn install_service() -> Result<()> { logging!( info, Type::Service, - true, "install status code:{}", status.code().unwrap_or(-1) ); @@ -204,17 +200,11 @@ async fn install_service() -> Result<()> { #[cfg(target_os = "linux")] async fn reinstall_service() -> Result<()> { - logging!(info, Type::Service, true, "reinstall service"); + logging!(info, Type::Service, "reinstall service"); // 先卸载服务 if let Err(err) = uninstall_service().await { - logging!( - warn, - Type::Service, - true, - "failed to uninstall service: {}", - err - ); + logging!(warn, Type::Service, "failed to uninstall service: {}", err); } // 再安装服务 @@ -230,10 +220,10 @@ async fn reinstall_service() -> Result<()> { async fn uninstall_service() -> Result<()> { use crate::utils::i18n::t; - logging!(info, Type::Service, true, "uninstall service"); + logging!(info, Type::Service, "uninstall service"); let binary_path = dirs::service_path()?; - let uninstall_path = binary_path.with_file_name("uninstall-service"); + let uninstall_path = binary_path.with_file_name("clash-verge-service-uninstall"); if !uninstall_path.exists() { bail!(format!("uninstaller not found: {uninstall_path:?}")); @@ -246,7 +236,7 @@ async fn uninstall_service() -> Result<()> { r#"do shell script "sudo '{uninstall_shell}'" with administrator privileges with prompt "{prompt}""# ); - // logging!(debug, Type::Service, true, "uninstall command: {}", command); + // logging!(debug, Type::Service, "uninstall command: {}", command); let status = StdCommand::new("osascript") .args(vec!["-e", &command]) @@ -266,10 +256,10 @@ async fn uninstall_service() -> Result<()> { async fn install_service() -> Result<()> { use crate::utils::i18n::t; - logging!(info, Type::Service, true, "install service"); + logging!(info, Type::Service, "install service"); let binary_path = dirs::service_path()?; - let install_path = binary_path.with_file_name("install-service"); + let install_path = binary_path.with_file_name("clash-verge-service-install"); if !install_path.exists() { bail!(format!("installer not found: {install_path:?}")); @@ -282,7 +272,7 @@ async fn install_service() -> Result<()> { r#"do shell script "sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""# ); - // logging!(debug, Type::Service, true, "install command: {}", command); + // logging!(debug, Type::Service, "install command: {}", command); let status = StdCommand::new("osascript") .args(vec!["-e", &command]) @@ -300,17 +290,11 @@ async fn install_service() -> Result<()> { #[cfg(target_os = "macos")] async fn reinstall_service() -> Result<()> { - logging!(info, Type::Service, true, "reinstall service"); + logging!(info, Type::Service, "reinstall service"); // 先卸载服务 if let Err(err) = uninstall_service().await { - logging!( - warn, - Type::Service, - true, - "failed to uninstall service: {}", - err - ); + logging!(warn, Type::Service, "failed to uninstall service: {}", err); } // 再安装服务 @@ -324,37 +308,29 @@ async fn reinstall_service() -> Result<()> { /// 强制重装服务(UI修复按钮) pub async fn force_reinstall_service() -> Result<()> { - logging!(info, Type::Service, true, "用户请求强制重装服务"); + logging!(info, Type::Service, "用户请求强制重装服务"); reinstall_service().await.map_err(|err| { - logging!(error, Type::Service, true, "强制重装服务失败: {}", err); + logging!(error, Type::Service, "强制重装服务失败: {}", err); err }) } /// 检查服务版本 - 使用IPC通信 async fn check_service_version() -> Result { - let cache = CacheService::global(); - let key = CacheService::make_key("service", "version"); - let version_arc = cache - .get_or_fetch(key, SHORT_TERM_TTL, || async { - logging!(info, Type::Service, true, "开始检查服务版本 (IPC)"); - let payload = serde_json::json!({}); - let response = send_ipc_request(IpcCommand::GetVersion, payload).await?; + let version_arc: Result = { + logging!(info, Type::Service, "开始检查服务版本 (IPC)"); + let response = clash_verge_service_ipc::get_version() + .await + .context("无法连接到Clash Verge Service")?; + if response.code > 0 { + let err_msg = response.message; + logging!(error, Type::Service, "获取服务版本失败: {}", err_msg); + return Err(anyhow::anyhow!(err_msg)); + } - let data = response - .data - .ok_or_else(|| anyhow::anyhow!("服务版本响应中没有数据"))?; - - if let Some(nested_data) = data.get("data") - && let Some(version) = nested_data.get("version").and_then(|v| v.as_str()) - { - // logging!(info, Type::Service, true, "获取到服务版本: {}", version); - return Ok(version.to_string()); - } - - Ok("unknown".to_string()) - }) - .await; + let version = response.data.unwrap_or("unknown".to_string()); + Ok(version) + }; match version_arc.as_ref() { Ok(v) => Ok(v.clone()), @@ -365,14 +341,14 @@ async fn check_service_version() -> Result { /// 检查服务是否需要重装 pub async fn check_service_needs_reinstall() -> bool { match check_service_version().await { - Ok(version) => version != REQUIRED_SERVICE_VERSION, + Ok(version) => version != clash_verge_service_ipc::VERSION, Err(_) => false, } } /// 尝试使用服务启动core pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> { - logging!(info, Type::Service, true, "尝试使用现有服务启动核心"); + logging!(info, Type::Service, "尝试使用现有服务启动核心"); let verge_config = Config::verge().await; let clash_core = verge_config.latest_ref().get_valid_clash_core(); @@ -381,113 +357,97 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result let bin_ext = if cfg!(windows) { ".exe" } else { "" }; let bin_path = current_exe()?.with_file_name(format!("{clash_core}{bin_ext}")); - let payload = serde_json::json!({ - "core_type": clash_core, - "bin_path": dirs::path_to_str(&bin_path)?, - "config_dir": dirs::path_to_str(&dirs::app_home_dir()?)?, - "config_file": dirs::path_to_str(config_file)?, - "log_file": dirs::path_to_str(&dirs::service_log_file()?)?, - }); + let payload = clash_verge_service_ipc::ClashConfig { + core_config: CoreConfig { + config_path: dirs::path_to_str(config_file)?.to_string(), + core_path: dirs::path_to_str(&bin_path)?.to_string(), + config_dir: dirs::path_to_str(&dirs::app_home_dir()?)?.to_string(), + }, + log_config: service_writer_config().await?, + }; - let response = send_ipc_request(IpcCommand::StartClash, payload) + let response = clash_verge_service_ipc::start_clash(&payload) .await .context("无法连接到Clash Verge Service")?; - if !response.success { - let err_msg = response.error.unwrap_or_else(|| "启动核心失败".to_string()); + if response.code > 0 { + let err_msg = response.message; + logging!(error, Type::Service, "启动核心失败: {}", err_msg); bail!(err_msg); } - if let Some(data) = &response.data - && let Some(code) = data.get("code").and_then(|c| c.as_u64()) - && code != 0 - { - let msg = data - .get("msg") - .and_then(|m| m.as_str()) - .unwrap_or("未知错误"); - bail!("启动核心失败: {}", msg); - } - - logging!(info, Type::Service, true, "服务成功启动核心"); + logging!(info, Type::Service, "服务成功启动核心"); Ok(()) } // 以服务启动core pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> { - logging!(info, Type::Service, true, "正在尝试通过服务启动核心"); + logging!(info, Type::Service, "正在尝试通过服务启动核心"); if check_service_needs_reinstall().await { reinstall_service().await?; } - logging!(info, Type::Service, true, "服务已运行且版本匹配,直接使用"); + logging!(info, Type::Service, "服务已运行且版本匹配,直接使用"); start_with_existing_service(config_file).await } /// 通过服务停止core pub(super) async fn stop_core_by_service() -> Result<()> { - logging!(info, Type::Service, true, "通过服务停止核心 (IPC)"); + logging!(info, Type::Service, "通过服务停止核心 (IPC)"); - let payload = serde_json::json!({}); - let response = send_ipc_request(IpcCommand::StopClash, payload) + let response = clash_verge_service_ipc::stop_clash() .await .context("无法连接到Clash Verge Service")?; - if !response.success { - let err_msg = response.error.unwrap_or_else(|| "停止核心失败".to_string()); - logging!(error, Type::Service, true, "停止核心失败: {}", err_msg); + if response.code > 0 { + let err_msg = response.message; + logging!(error, Type::Service, "停止核心失败: {}", err_msg); bail!(err_msg); } - if let Some(data) = &response.data - && let Some(code) = data.get("code") - { - let code_value = code.as_u64().unwrap_or(1); - let msg = data - .get("msg") - .and_then(|m| m.as_str()) - .unwrap_or("未知错误"); - - if code_value != 0 { - logging!( - error, - Type::Service, - true, - "停止核心返回错误: code={}, msg={}", - code_value, - msg - ); - bail!("停止核心失败: {}", msg); - } - } - - logging!(info, Type::Service, true, "服务成功停止核心"); + logging!(info, Type::Service, "服务成功停止核心"); Ok(()) } /// 检查服务是否正在运行 pub async fn is_service_available() -> Result<()> { - check_service_version().await?; + clash_verge_service_ipc::connect().await?; Ok(()) } +pub fn is_service_ipc_path_exists() -> bool { + Path::new(clash_verge_service_ipc::IPC_PATH).exists() +} + impl ServiceManager { pub fn default() -> Self { Self(ServiceStatus::Unavailable("Need Checks".into())) } + pub fn config() -> Option { + Some(clash_verge_service_ipc::IpcConfig { + default_timeout: Duration::from_millis(30), + retry_delay: Duration::from_millis(250), + max_retries: 6, + }) + } + + pub async fn init(&mut self) -> Result<()> { + if let Err(e) = clash_verge_service_ipc::connect().await { + self.0 = ServiceStatus::Unavailable("服务连接失败: {e}".to_string()); + return Err(e); + } + Ok(()) + } + pub fn current(&self) -> ServiceStatus { self.0.clone() } pub async fn refresh(&mut self) -> Result<()> { let status = self.check_service_comprehensive().await; - logging_error!( - Type::Service, - true, - self.handle_service_status(&status).await - ); + logging_error!(Type::Service, self.handle_service_status(&status).await); self.0 = status; Ok(()) } @@ -496,16 +456,16 @@ impl ServiceManager { pub async fn check_service_comprehensive(&self) -> ServiceStatus { match is_service_available().await { Ok(_) => { - logging!(info, Type::Service, true, "服务当前可用,检查是否需要重装"); + logging!(info, Type::Service, "服务当前可用,检查是否需要重装"); if check_service_needs_reinstall().await { - logging!(info, Type::Service, true, "服务需要重装且允许重装"); + logging!(info, Type::Service, "服务需要重装且允许重装"); ServiceStatus::NeedsReinstall } else { ServiceStatus::Ready } } Err(err) => { - logging!(warn, Type::Service, true, "服务不可用,检查安装状态"); + logging!(warn, Type::Service, "服务不可用,检查安装状态"); ServiceStatus::Unavailable(err.to_string()) } } @@ -515,50 +475,40 @@ impl ServiceManager { pub async fn handle_service_status(&mut self, status: &ServiceStatus) -> Result<()> { match status { ServiceStatus::Ready => { - logging!(info, Type::Service, true, "服务就绪,直接启动"); - Ok(()) + logging!(info, Type::Service, "服务就绪,直接启动"); } ServiceStatus::NeedsReinstall | ServiceStatus::ReinstallRequired => { - logging!(info, Type::Service, true, "服务需要重装,执行重装流程"); + logging!(info, Type::Service, "服务需要重装,执行重装流程"); reinstall_service().await?; self.0 = ServiceStatus::Ready; - Ok(()) } ServiceStatus::ForceReinstallRequired => { - logging!( - info, - Type::Service, - true, - "服务需要强制重装,执行强制重装流程" - ); + logging!(info, Type::Service, "服务需要强制重装,执行强制重装流程"); force_reinstall_service().await?; self.0 = ServiceStatus::Ready; - Ok(()) } ServiceStatus::InstallRequired => { - logging!(info, Type::Service, true, "需要安装服务,执行安装流程"); + logging!(info, Type::Service, "需要安装服务,执行安装流程"); install_service().await?; self.0 = ServiceStatus::Ready; - Ok(()) } ServiceStatus::UninstallRequired => { - logging!(info, Type::Service, true, "服务需要卸载,执行卸载流程"); + logging!(info, Type::Service, "服务需要卸载,执行卸载流程"); uninstall_service().await?; self.0 = ServiceStatus::Unavailable("Service Uninstalled".into()); - Ok(()) } ServiceStatus::Unavailable(reason) => { logging!( info, Type::Service, - true, "服务不可用: {},将使用Sidecar模式", reason ); self.0 = ServiceStatus::Unavailable(reason.clone()); - Err(anyhow::anyhow!("服务不可用: {}", reason)) + return Err(anyhow::anyhow!("服务不可用: {}", reason)); } } + Ok(()) } } diff --git a/clash-verge-rev/src-tauri/src/core/service_ipc.rs b/clash-verge-rev/src-tauri/src/core/service_ipc.rs deleted file mode 100644 index c32b9de42d..0000000000 --- a/clash-verge-rev/src-tauri/src/core/service_ipc.rs +++ /dev/null @@ -1,349 +0,0 @@ -use crate::{logging, utils::logging::Type}; -use anyhow::{Context, Result, bail}; -use backoff::{Error as BackoffError, ExponentialBackoff}; -use hmac::{Hmac, Mac}; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -#[cfg(unix)] -use tokio::net::UnixStream; -#[cfg(windows)] -use tokio::net::windows::named_pipe::ClientOptions; - -const IPC_SOCKET_NAME: &str = if cfg!(windows) { - r"\\.\pipe\clash-verge-service" -} else { - "/tmp/clash-verge-service.sock" -}; - -// 定义命令类型 -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum IpcCommand { - GetClash, - GetVersion, - StartClash, - StopClash, -} - -// IPC消息格式 -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct IpcRequest { - pub id: String, - pub timestamp: u64, - pub command: IpcCommand, - pub payload: serde_json::Value, - pub signature: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct IpcResponse { - pub id: String, - pub success: bool, - pub data: Option, - pub error: Option, - pub signature: String, -} - -// 密钥派生函数 -fn derive_secret_key() -> Vec { - // to do - // 从系统安全存储中获取或从程序安装时生成的密钥文件中读取 - let unique_app_id = "clash-verge-app-secret-fuck-me-until-daylight"; - let mut hasher = Sha256::new(); - hasher.update(unique_app_id.as_bytes()); - hasher.finalize().to_vec() -} - -// 创建带签名的请求 -pub fn create_signed_request( - command: IpcCommand, - payload: serde_json::Value, -) -> Result { - let id = nanoid::nanoid!(32); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - - let unsigned_request = IpcRequest { - id: id.clone(), - timestamp, - command: command.clone(), - payload: payload.clone(), - signature: String::new(), - }; - - let unsigned_json = serde_json::to_string(&unsigned_request)?; - let signature = sign_message(&unsigned_json)?; - - Ok(IpcRequest { - id, - timestamp, - command, - payload, - signature, - }) -} - -// 签名消息 -fn sign_message(message: &str) -> Result { - type HmacSha256 = Hmac; - - let secret_key = derive_secret_key(); - let mut mac = HmacSha256::new_from_slice(&secret_key).context("HMAC初始化失败")?; - - mac.update(message.as_bytes()); - let result = mac.finalize(); - let signature = hex::encode(result.into_bytes()); - - Ok(signature) -} - -// 验证响应签名 -pub fn verify_response_signature(response: &IpcResponse) -> Result { - let verification_response = IpcResponse { - id: response.id.clone(), - success: response.success, - data: response.data.clone(), - error: response.error.clone(), - signature: String::new(), - }; - - let message = serde_json::to_string(&verification_response)?; - let expected_signature = sign_message(&message)?; - - Ok(expected_signature == response.signature) -} - -fn create_backoff_strategy() -> ExponentialBackoff { - ExponentialBackoff { - initial_interval: Duration::from_millis(50), - max_interval: Duration::from_secs(1), - max_elapsed_time: Some(Duration::from_secs(3)), - multiplier: 1.5, - ..Default::default() - } -} - -pub async fn send_ipc_request( - command: IpcCommand, - payload: serde_json::Value, -) -> Result { - let command_type = format!("{command:?}"); - - let operation = || async { - match send_ipc_request_internal(command.clone(), payload.clone()).await { - Ok(response) => Ok(response), - Err(e) => { - logging!( - warn, - Type::Service, - true, - "IPC请求失败,准备重试: 命令={}, 错误={}", - command_type, - e - ); - Err(BackoffError::transient(e)) - } - } - }; - - match backoff::future::retry(create_backoff_strategy(), operation).await { - Ok(response) => { - // logging!( - // info, - // Type::Service, - // true, - // "IPC请求成功: 命令={}, 成功={}", - // command_type, - // response.success - // ); - Ok(response) - } - Err(e) => { - logging!( - error, - Type::Service, - true, - "IPC请求最终失败,重试已耗尽: 命令={}, 错误={}", - command_type, - e - ); - Err(anyhow::anyhow!("IPC请求重试失败: {}", e)) - } - } -} - -// 内部IPC请求实现(不带重试) -async fn send_ipc_request_internal( - command: IpcCommand, - payload: serde_json::Value, -) -> Result { - #[cfg(target_os = "windows")] - { - send_ipc_request_windows(command, payload).await - } - #[cfg(target_family = "unix")] - { - send_ipc_request_unix(command, payload).await - } -} - -// IPC连接管理-win -#[cfg(target_os = "windows")] -async fn send_ipc_request_windows( - command: IpcCommand, - payload: serde_json::Value, -) -> Result { - let request = create_signed_request(command, payload)?; - let request_json = serde_json::to_string(&request)?; - let request_bytes = request_json.as_bytes(); - let len_bytes = (request_bytes.len() as u32).to_be_bytes(); - - let mut pipe = match ClientOptions::new().open(IPC_SOCKET_NAME) { - Ok(p) => p, - Err(e) => { - logging!(error, Type::Service, true, "连接到服务命名管道失败: {}", e); - return Err(anyhow::anyhow!("无法连接到服务命名管道: {}", e)); - } - }; - - logging!(info, Type::Service, true, "服务连接成功 (Windows)"); - - pipe.write_all(&len_bytes).await?; - pipe.write_all(request_bytes).await?; - pipe.flush().await?; - - let mut response_len_bytes = [0u8; 4]; - pipe.read_exact(&mut response_len_bytes).await?; - let response_len = u32::from_be_bytes(response_len_bytes) as usize; - - let mut response_bytes = vec![0u8; response_len]; - pipe.read_exact(&mut response_bytes).await?; - - let response: IpcResponse = serde_json::from_slice(&response_bytes) - .map_err(|e| anyhow::anyhow!("解析响应失败: {}", e))?; - - if !verify_response_signature(&response)? { - logging!(error, Type::Service, true, "服务响应签名验证失败"); - bail!("服务响应签名验证失败"); - } - - Ok(response) -} - -// IPC连接管理-unix -#[cfg(target_family = "unix")] -async fn send_ipc_request_unix( - command: IpcCommand, - payload: serde_json::Value, -) -> Result { - let request = create_signed_request(command, payload)?; - let request_json = serde_json::to_string(&request)?; - - let mut stream = match UnixStream::connect(IPC_SOCKET_NAME).await { - Ok(s) => s, - Err(e) => { - logging!(error, Type::Service, true, "连接到Unix套接字失败: {}", e); - return Err(anyhow::anyhow!("无法连接到服务Unix套接字: {}", e)); - } - }; - - let request_bytes = request_json.as_bytes(); - let len_bytes = (request_bytes.len() as u32).to_be_bytes(); - - stream.write_all(&len_bytes).await?; - stream.write_all(request_bytes).await?; - stream.flush().await?; - - // 读取响应长度 - let mut response_len_bytes = [0u8; 4]; - stream.read_exact(&mut response_len_bytes).await?; - let response_len = u32::from_be_bytes(response_len_bytes) as usize; - - let mut response_bytes = vec![0u8; response_len]; - stream.read_exact(&mut response_bytes).await?; - - let response: IpcResponse = serde_json::from_slice(&response_bytes) - .map_err(|e| anyhow::anyhow!("解析响应失败: {}", e))?; - - if !verify_response_signature(&response)? { - logging!(error, Type::Service, true, "服务响应签名验证失败"); - bail!("服务响应签名验证失败"); - } - - Ok(response) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_signed_request() { - let command = IpcCommand::GetVersion; - let payload = serde_json::json!({"test": "data"}); - - let result = create_signed_request(command, payload); - assert!(result.is_ok()); - - if let Ok(request) = result { - assert!(!request.id.is_empty()); - assert!(!request.signature.is_empty()); - assert_eq!(request.command, IpcCommand::GetVersion); - } - } - - #[test] - fn test_sign_and_verify_message() { - let test_message = "test message for signing"; - - let signature_result = sign_message(test_message); - assert!(signature_result.is_ok()); - - if let Ok(signature) = signature_result { - assert!(!signature.is_empty()); - - // 测试相同消息产生相同签名 - if let Ok(signature2) = sign_message(test_message) { - assert_eq!(signature, signature2); - } - } - } - - #[test] - fn test_verify_response_signature() { - let response = IpcResponse { - id: "test-id".to_string(), - success: true, - data: Some(serde_json::json!({"result": "success"})), - error: None, - signature: String::new(), - }; - - // 创建正确的签名 - let verification_response = IpcResponse { - id: response.id.clone(), - success: response.success, - data: response.data.clone(), - error: response.error.clone(), - signature: String::new(), - }; - - if let Ok(message) = serde_json::to_string(&verification_response) - && let Ok(correct_signature) = sign_message(&message) - { - let signed_response = IpcResponse { - signature: correct_signature, - ..response - }; - - let verification_result = verify_response_signature(&signed_response); - assert!(verification_result.is_ok()); - if let Ok(is_valid) = verification_result { - assert!(is_valid); - } - } - } -} diff --git a/clash-verge-rev/src-tauri/src/core/sysopt.rs b/clash-verge-rev/src-tauri/src/core/sysopt.rs index 59c886c87e..a06b348be1 100644 --- a/clash-verge-rev/src-tauri/src/core/sysopt.rs +++ b/clash-verge-rev/src-tauri/src/core/sysopt.rs @@ -51,6 +51,35 @@ async fn get_bypass() -> String { } } +// Uses tokio Command with CREATE_NO_WINDOW flag to avoid DLL initialization issues during shutdown +#[cfg(target_os = "windows")] +async fn execute_sysproxy_command(args: Vec) -> Result<()> { + use crate::utils::dirs; + use anyhow::bail; + #[allow(unused_imports)] // Required for .creation_flags() method + use std::os::windows::process::CommandExt; + use tokio::process::Command; + + let binary_path = dirs::service_path()?; + let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); + + if !sysproxy_exe.exists() { + bail!("sysproxy.exe not found"); + } + + let output = Command::new(sysproxy_exe) + .args(&args) + .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口 + .output() + .await?; + + if !output.status.success() { + bail!("sysproxy exe run failed"); + } + + Ok(()) +} + impl Default for Sysopt { fn default() -> Self { Sysopt { @@ -148,49 +177,17 @@ impl Sysopt { proxy_manager.notify_config_changed(); return result; } - use crate::{core::handle::Handle, utils::dirs}; - use anyhow::bail; - use tauri_plugin_shell::ShellExt; - let app_handle = Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; - - let binary_path = dirs::service_path()?; - let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); - if !sysproxy_exe.exists() { - bail!("sysproxy.exe not found"); - } - - let shell = app_handle.shell(); - let output = if pac_enable { + let args = if pac_enable { let address = format!("http://{proxy_host}:{pac_port}/commands/pac"); - let sysproxy_str = sysproxy_exe - .as_path() - .to_str() - .ok_or_else(|| anyhow::anyhow!("Invalid sysproxy.exe path"))?; - shell - .command(sysproxy_str) - .args(["pac", address.as_str()]) - .output() - .await? + vec!["pac".to_string(), address] } else { let address = format!("{proxy_host}:{port}"); let bypass = get_bypass().await; - let sysproxy_str = sysproxy_exe - .as_path() - .to_str() - .ok_or_else(|| anyhow::anyhow!("Invalid sysproxy.exe path"))?; - shell - .command(sysproxy_str) - .args(["global", address.as_str(), bypass.as_ref()]) - .output() - .await? + vec!["global".to_string(), address, bypass] }; - if !output.status.success() { - bail!("sysproxy exe run failed"); - } + execute_sysproxy_command(args).await?; } let proxy_manager = EventDrivenProxyManager::global(); proxy_manager.notify_config_changed(); @@ -223,35 +220,7 @@ impl Sysopt { #[cfg(target_os = "windows")] { - use crate::{core::handle::Handle, utils::dirs}; - use anyhow::bail; - use tauri_plugin_shell::ShellExt; - - let app_handle = Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; - - let binary_path = dirs::service_path()?; - let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); - - if !sysproxy_exe.exists() { - bail!("sysproxy.exe not found"); - } - - let shell = app_handle.shell(); - let sysproxy_str = sysproxy_exe - .as_path() - .to_str() - .ok_or_else(|| anyhow::anyhow!("Invalid sysproxy.exe path"))?; - let output = shell - .command(sysproxy_str) - .args(["set", "1"]) - .output() - .await?; - - if !output.status.success() { - bail!("sysproxy exe run failed"); - } + execute_sysproxy_command(vec!["set".to_string(), "1".to_string()]).await?; } Ok(()) @@ -261,7 +230,12 @@ impl Sysopt { pub async fn update_launch(&self) -> Result<()> { let enable_auto_launch = { Config::verge().await.latest_ref().enable_auto_launch }; let is_enable = enable_auto_launch.unwrap_or(false); - logging!(info, true, "Setting auto-launch state to: {:?}", is_enable); + logging!( + info, + Type::System, + "Setting auto-launch state to: {:?}", + is_enable + ); // 首先尝试使用快捷方式方法 #[cfg(target_os = "windows")] @@ -293,16 +267,13 @@ impl Sysopt { /// 尝试使用原来的自启动方法 fn try_original_autostart_method(&self, is_enable: bool) { - let Some(app_handle) = Handle::global().app_handle() else { - log::error!(target: "app", "App handle not available for autostart"); - return; - }; + let app_handle = Handle::app_handle(); let autostart_manager = app_handle.autolaunch(); if is_enable { - logging_error!(Type::System, true, "{:?}", autostart_manager.enable()); + logging_error!(Type::System, "{:?}", autostart_manager.enable()); } else { - logging_error!(Type::System, true, "{:?}", autostart_manager.disable()); + logging_error!(Type::System, "{:?}", autostart_manager.disable()); } } @@ -323,9 +294,7 @@ impl Sysopt { } // 回退到原来的方法 - let app_handle = Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; + let app_handle = Handle::app_handle(); let autostart_manager = app_handle.autolaunch(); match autostart_manager.is_enabled() { diff --git a/clash-verge-rev/src-tauri/src/core/timer.rs b/clash-verge-rev/src-tauri/src/core/timer.rs index f3f01c6b3b..5552f8432a 100644 --- a/clash-verge-rev/src-tauri/src/core/timer.rs +++ b/clash-verge-rev/src-tauri/src/core/timer.rs @@ -64,7 +64,7 @@ impl Timer { if let Err(e) = self.refresh().await { // Reset initialization flag on error self.initialized.store(false, Ordering::SeqCst); - logging_error!(Type::Timer, false, "Failed to initialize timer: {}", e); + logging_error!(Type::Timer, "Failed to initialize timer: {}", e); return Err(e); } @@ -139,6 +139,27 @@ impl Timer { Ok(()) } + /// 每 3 秒更新系统托盘菜单,总共执行 3 次 + pub fn add_update_tray_menu_task(&self) -> Result<()> { + let tid = self.timer_count.fetch_add(1, Ordering::SeqCst); + let delay_timer = self.delay_timer.write(); + let task = TaskBuilder::default() + .set_task_id(tid) + .set_maximum_parallel_runnable_num(1) + .set_frequency_count_down_by_seconds(3, 3) + .spawn_async_routine(|| async move { + logging!(info, Type::Timer, "Updating tray menu"); + crate::core::tray::Tray::global() + .update_tray_display() + .await + }) + .context("failed to create update tray menu timer task")?; + delay_timer + .add_task(task) + .context("failed to add update tray menu timer task")?; + Ok(()) + } + /// Refresh timer tasks with better error handling pub async fn refresh(&self) -> Result<()> { // Generate diff outside of lock to minimize lock contention @@ -480,7 +501,7 @@ impl Timer { } }, Err(_) => { - logging_error!(Type::Timer, false, "Timer task timed out for uid: {}", uid); + logging_error!(Type::Timer, "Timer task timed out for uid: {}", uid); } } diff --git a/clash-verge-rev/src-tauri/src/core/tray/mod.rs b/clash-verge-rev/src-tauri/src/core/tray/mod.rs index 3df6a8204b..8535ba8dd0 100644 --- a/clash-verge-rev/src-tauri/src/core/tray/mod.rs +++ b/clash-verge-rev/src-tauri/src/core/tray/mod.rs @@ -3,16 +3,13 @@ use tauri::Emitter; use tauri::tray::TrayIconBuilder; #[cfg(target_os = "macos")] pub mod speed_rate; -use crate::ipc::Rate; use crate::module::lightweight; use crate::process::AsyncHandler; use crate::utils::window_manager::WindowManager; use crate::{ Type, cmd, config::Config, - feat, - ipc::IpcManager, - logging, + feat, logging, module::lightweight::is_in_lightweight_mode, singleton_lazy, utils::{dirs::find_target_icons, i18n::t}, @@ -34,6 +31,8 @@ use tauri::{ tray::{MouseButton, MouseButtonState, TrayIconEvent}, }; +// TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑) + #[derive(Clone)] struct TrayState {} @@ -54,7 +53,7 @@ fn should_handle_tray_click() -> bool { *last_click = now; true } else { - log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms", + log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms", now.duration_since(*last_click).as_millis()); false } @@ -189,18 +188,35 @@ singleton_lazy!(Tray, TRAY, Tray::default); impl Tray { pub async fn init(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray initialization"))?; - self.create_tray_from_handle(&app_handle).await?; + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘初始化"); + return Ok(()); + } + + let app_handle = handle::Handle::app_handle(); + + match self.create_tray_from_handle(app_handle).await { + Ok(_) => { + log::info!(target: "app", "System tray created successfully"); + } + Err(e) => { + // Don't return error, let application continue running without tray + log::warn!(target: "app", "System tray creation failed: {}, Application will continue running without tray icon", e); + } + } + // TODO: 初始化时,暂时使用此方法更新系统托盘菜单,有效避免代理节点菜单空白 + crate::core::timer::Timer::global().add_update_tray_menu_task()?; Ok(()) } /// 更新托盘点击行为 pub async fn update_click_behavior(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray update"))?; + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘点击行为更新"); + return Ok(()); + } + + let app_handle = handle::Handle::app_handle(); let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); let tray = app_handle @@ -215,6 +231,10 @@ impl Tray { /// 更新托盘菜单 pub async fn update_menu(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘菜单更新"); + return Ok(()); + } // 调整最小更新间隔,确保状态及时刷新 const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(100); @@ -240,18 +260,12 @@ impl Tray { return Ok(()); } - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘菜单失败: app_handle不存在"); - return Ok(()); - } - }; + let app_handle = handle::Handle::app_handle(); // 设置更新状态 self.menu_updating.store(true, Ordering::Release); - let result = self.update_menu_internal(&app_handle).await; + let result = self.update_menu_internal(app_handle).await; { let mut last_update = self.last_menu_update.lock(); @@ -308,14 +322,13 @@ impl Tray { /// 更新托盘图标 #[cfg(target_os = "macos")] - pub async fn update_icon(&self, _rate: Option) -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘图标失败: app_handle不存在"); - return Ok(()); - } - }; + pub async fn update_icon(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘图标更新"); + return Ok(()); + } + + let app_handle = handle::Handle::app_handle(); let tray = match app_handle.tray_by_id("main") { Some(tray) => tray, @@ -345,14 +358,13 @@ impl Tray { } #[cfg(not(target_os = "macos"))] - pub async fn update_icon(&self, _rate: Option) -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘图标失败: app_handle不存在"); - return Ok(()); - } - }; + pub async fn update_icon(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘图标更新"); + return Ok(()); + } + + let app_handle = handle::Handle::app_handle(); let tray = match app_handle.tray_by_id("main") { Some(tray) => tray, @@ -379,9 +391,12 @@ impl Tray { /// 更新托盘显示状态的函数 pub async fn update_tray_display(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray update"))?; + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘显示状态更新"); + return Ok(()); + } + + let app_handle = handle::Handle::app_handle(); let _tray = app_handle .tray_by_id("main") .ok_or_else(|| anyhow::anyhow!("Failed to get main tray"))?; @@ -394,13 +409,12 @@ impl Tray { /// 更新托盘提示 pub async fn update_tooltip(&self) -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘提示失败: app_handle不存在"); - return Ok(()); - } - }; + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘提示更新"); + return Ok(()); + } + + let app_handle = handle::Handle::app_handle(); let verge = Config::verge().await.latest_ref().clone(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); @@ -432,17 +446,24 @@ impl Tray { let tun_text = t("TUN").await; let profile_text = t("Profile").await; - let version = env!("CARGO_PKG_VERSION"); + let v = env!("CARGO_PKG_VERSION"); + let reassembled_version = v.split_once('+').map_or(v.to_string(), |(main, rest)| { + format!("{main}+{}", rest.split('.').next().unwrap_or("")) + }); + + let tooltip = format!( + "Clash Verge {}\n{}: {}\n{}: {}\n{}: {}", + reassembled_version, + sys_proxy_text, + switch_map[system_proxy], + tun_text, + switch_map[tun_mode], + profile_text, + current_profile_name + ); + if let Some(tray) = app_handle.tray_by_id("main") { - let _ = tray.set_tooltip(Some(&format!( - "Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}", - sys_proxy_text, - switch_map[system_proxy], - tun_text, - switch_map[tun_mode], - profile_text, - current_profile_name - ))); + let _ = tray.set_tooltip(Some(&tooltip)); } else { log::warn!(target: "app", "更新托盘提示失败: 托盘不存在"); } @@ -451,15 +472,24 @@ impl Tray { } pub async fn update_part(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘局部更新"); + return Ok(()); + } // self.update_menu().await?; // 更新轻量模式显示状态 self.update_tray_display().await?; - self.update_icon(None).await?; + self.update_icon().await?; self.update_tooltip().await?; Ok(()) } pub async fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘创建"); + return Ok(()); + } + log::info!(target: "app", "正在从AppHandle创建系统托盘"); // 获取图标 @@ -537,10 +567,15 @@ impl Tray { // 托盘统一的状态更新函数 pub async fn update_all_states(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘状态更新"); + return Ok(()); + } + // 确保所有状态更新完成 self.update_tray_display().await?; // self.update_menu().await?; - self.update_icon(None).await?; + self.update_icon().await?; self.update_tooltip().await?; Ok(()) @@ -568,14 +603,7 @@ async fn create_tray_menu( .unwrap_or_default() }; - let proxy_nodes_data = cmd::get_proxies().await.unwrap_or_else(|e| { - logging!( - error, - Type::Cmd, - "Failed to fetch proxies for tray menu: {e}" - ); - serde_json::Value::Object(serde_json::Map::new()) - }); + let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await; let version = env!("CARGO_PKG_VERSION"); @@ -628,46 +656,43 @@ async fn create_tray_menu( let mut submenus = Vec::new(); let mut group_name_submenus_hash = HashMap::new(); - if let Some(proxies) = proxy_nodes_data.get("proxies").and_then(|v| v.as_object()) { - for (group_name, group_data) in proxies.iter() { + // TODO: 应用启动时,内核还未启动完全,无法获取代理节点信息 + if let Ok(proxy_nodes_data) = proxy_nodes_data { + for (group_name, group_data) in proxy_nodes_data.proxies.iter() { // Filter groups based on mode let should_show = match mode { "global" => group_name == "GLOBAL", _ => group_name != "GLOBAL", } && // Check if the group is hidden - !group_data.get("hidden").and_then(|v| v.as_bool()).unwrap_or(false); + !group_data.hidden.unwrap_or_default(); if !should_show { continue; } - let Some(all_proxies) = group_data.get("all").and_then(|v| v.as_array()) else { + let Some(all_proxies) = group_data.all.as_ref() else { continue; }; - let now_proxy = group_data.get("now").and_then(|v| v.as_str()).unwrap_or(""); + let now_proxy = group_data.now.as_deref().unwrap_or_default(); // Create proxy items let group_items: Vec> = all_proxies .iter() - .filter_map(|proxy_name| proxy_name.as_str()) .filter_map(|proxy_str| { - let is_selected = proxy_str == now_proxy; + let is_selected = *proxy_str == now_proxy; let item_id = format!("proxy_{}_{}", group_name, proxy_str); // Get delay for display - let delay_text = proxies + let delay_text = proxy_nodes_data + .proxies .get(proxy_str) - .and_then(|p| p.get("history")) - .and_then(|h| h.as_array()) - .and_then(|h| h.last()) - .and_then(|r| r.get("delay")) - .and_then(|d| d.as_i64()) - .map(|delay| match delay { - -1 => "-ms".to_string(), + .and_then(|h| h.history.last()) + .map(|h| match h.delay { + 0 => "-ms".to_string(), delay if delay >= 10000 => "-ms".to_string(), - _ => format!("{}ms", delay), + _ => format!("{}ms", h.delay), }) .unwrap_or_else(|| "-ms".to_string()); @@ -994,13 +1019,7 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { match event.id.as_ref() { mode @ ("rule_mode" | "global_mode" | "direct_mode") => { let mode = &mode[0..mode.len() - 5]; // Removing the "_mode" suffix - logging!( - info, - Type::ProxyMode, - true, - "Switch Proxy Mode To: {}", - mode - ); + logging!(info, Type::ProxyMode, "Switch Proxy Mode To: {}", mode); feat::change_clash_mode(mode.into()).await; } "open_window" => { @@ -1056,29 +1075,30 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { let group_name = parts[1]; let proxy_name = parts[2]; - match cmd::proxy::update_proxy_and_sync( - group_name.to_string(), - proxy_name.to_string(), - ) - .await + match handle::Handle::mihomo() + .await + .select_node_for_group(group_name, proxy_name) + .await { Ok(_) => { log::info!(target: "app", "切换代理成功: {} -> {}", group_name, proxy_name); + let _ = handle::Handle::app_handle() + .emit("verge://refresh-proxy-config", ()); } Err(e) => { log::error!(target: "app", "切换代理失败: {} -> {}, 错误: {:?}", group_name, proxy_name, e); // Fallback to IPC update - if (IpcManager::global() - .update_proxy(group_name, proxy_name) + if (handle::Handle::mihomo() + .await + .select_node_for_group(group_name, proxy_name) .await) .is_ok() { log::info!(target: "app", "代理切换回退成功: {} -> {}", group_name, proxy_name); - if let Some(app_handle) = handle::Handle::global().app_handle() { - let _ = app_handle.emit("verge://force-refresh-proxies", ()); - } + let app_handle = handle::Handle::app_handle(); + let _ = app_handle.emit("verge://force-refresh-proxies", ()); } } } diff --git a/clash-verge-rev/src-tauri/src/enhance/tun.rs b/clash-verge-rev/src-tauri/src/enhance/tun.rs index 5f81a99f1f..d019960a25 100644 --- a/clash-verge-rev/src-tauri/src/enhance/tun.rs +++ b/clash-verge-rev/src-tauri/src/enhance/tun.rs @@ -29,6 +29,26 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { }); if enable { + #[cfg(target_os = "linux")] + { + let stack_key = Value::from("stack"); + let should_override = match tun_val.get(&stack_key) { + Some(value) => value + .as_str() + .map(|stack| stack.eq_ignore_ascii_case("gvisor")) + .unwrap_or(false), + None => true, + }; + + if should_override { + revise!(tun_val, "stack", "mixed"); + log::warn!( + target: "app", + "gVisor TUN stack detected on Linux; falling back to 'mixed' for compatibility" + ); + } + } + // 读取DNS配置 let dns_key = Value::from("dns"); let dns_val = config.get(&dns_key); diff --git a/clash-verge-rev/src-tauri/src/feat/backup.rs b/clash-verge-rev/src-tauri/src/feat/backup.rs index d92418c808..f5c9687e64 100644 --- a/clash-verge-rev/src-tauri/src/feat/backup.rs +++ b/clash-verge-rev/src-tauri/src/feat/backup.rs @@ -20,6 +20,8 @@ pub async fn create_backup_and_upload_webdav() -> Result<()> { .await { log::error!(target: "app", "Failed to upload to WebDAV: {err:#?}"); + // 上传失败时重置客户端缓存 + backup::WebDavClient::global().reset(); return Err(err); } @@ -73,7 +75,6 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> { zip.extract(app_home_dir()?)?; logging_error!( Type::Backup, - true, super::patch_verge( IVerge { webdav_url, diff --git a/clash-verge-rev/src-tauri/src/feat/clash.rs b/clash-verge-rev/src-tauri/src/feat/clash.rs index 74e4a961c7..4bb474fd56 100644 --- a/clash-verge-rev/src-tauri/src/feat/clash.rs +++ b/clash-verge-rev/src-tauri/src/feat/clash.rs @@ -1,10 +1,9 @@ use crate::{ config::Config, core::{CoreManager, handle, tray}, - ipc::IpcManager, logging_error, process::AsyncHandler, - utils::{logging::Type, resolve}, + utils::{self, logging::Type, resolve}, }; use serde_yaml_ng::{Mapping, Value}; @@ -24,33 +23,28 @@ pub async fn restart_clash_core() { /// Restart the application pub async fn restart_app() { - // logging_error!(Type::Core, true, CoreManager::global().stop_core().await); - resolve::resolve_reset_async().await; + utils::server::shutdown_embedded_server(); + if let Err(err) = resolve::resolve_reset_async().await { + handle::Handle::notice_message( + "restart_app::error", + format!("Failed to cleanup resources: {err}"), + ); + log::error!(target:"app", "Restart failed during cleanup: {err}"); + return; + } - handle::Handle::global() - .app_handle() - .map(|app_handle| { - app_handle.restart(); - }) - .unwrap_or_else(|| { - logging_error!( - Type::System, - false, - "{}", - "Failed to get app handle for restart" - ); - }); + let app_handle = handle::Handle::app_handle(); + app_handle.restart(); } fn after_change_clash_mode() { AsyncHandler::spawn(move || async { - match IpcManager::global().get_connections().await { + let mihomo = handle::Handle::mihomo().await; + match mihomo.get_connections().await { Ok(connections) => { - if let Some(connections_array) = connections["connections"].as_array() { + if let Some(connections_array) = connections.connections { for connection in connections_array { - if let Some(id) = connection["id"].as_str() { - let _ = IpcManager::global().delete_connection(id).await; - } + let _ = mihomo.close_connection(&connection.id).await; } } } @@ -70,7 +64,11 @@ pub async fn change_clash_mode(mode: String) { "mode": mode }); log::debug!(target: "app", "change clash mode to {mode}"); - match IpcManager::global().patch_configs(json_value).await { + match handle::Handle::mihomo() + .await + .patch_base_config(&json_value) + .await + { Ok(_) => { // 更新订阅 Config::clash().await.data_mut().patch_config(mapping); @@ -79,12 +77,8 @@ pub async fn change_clash_mode(mode: String) { let clash_data = Config::clash().await.data_mut().clone(); if clash_data.save_config().await.is_ok() { handle::Handle::refresh_clash(); - logging_error!(Type::Tray, true, tray::Tray::global().update_menu().await); - logging_error!( - Type::Tray, - true, - tray::Tray::global().update_icon(None).await - ); + logging_error!(Type::Tray, tray::Tray::global().update_menu().await); + logging_error!(Type::Tray, tray::Tray::global().update_icon().await); } let is_auto_close_connection = Config::verge() diff --git a/clash-verge-rev/src-tauri/src/feat/config.rs b/clash-verge-rev/src-tauri/src/feat/config.rs index 15f0325b93..506f5fa6dd 100644 --- a/clash-verge-rev/src-tauri/src/feat/config.rs +++ b/clash-verge-rev/src-tauri/src/feat/config.rs @@ -22,12 +22,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { CoreManager::global().restart_core().await?; } else { if patch.get("mode").is_some() { - logging_error!(Type::Tray, true, tray::Tray::global().update_menu().await); - logging_error!( - Type::Tray, - true, - tray::Tray::global().update_icon(None).await - ); + logging_error!(Type::Tray, tray::Tray::global().update_menu().await); + logging_error!(Type::Tray, tray::Tray::global().update_icon().await); } Config::runtime().await.draft_mut().patch_config(patch); CoreManager::global().update_config().await?; @@ -211,7 +207,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { tray::Tray::global().update_menu().await?; } if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 { - tray::Tray::global().update_icon(None).await?; + tray::Tray::global().update_icon().await?; } if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 { tray::Tray::global().update_tooltip().await?; diff --git a/clash-verge-rev/src-tauri/src/feat/profile.rs b/clash-verge-rev/src-tauri/src/feat/profile.rs index 2bf2249cb7..3728a067ff 100644 --- a/clash-verge-rev/src-tauri/src/feat/profile.rs +++ b/clash-verge-rev/src-tauri/src/feat/profile.rs @@ -13,7 +13,7 @@ pub async fn toggle_proxy_profile(profile_index: String) { Ok(_) => { let result = tray::Tray::global().update_menu().await; if let Err(err) = result { - logging!(error, Type::Tray, true, "更新菜单失败: {}", err); + logging!(error, Type::Tray, "更新菜单失败: {}", err); } } Err(err) => { @@ -30,7 +30,7 @@ pub async fn update_profile( option: Option, auto_refresh: Option, ) -> Result<()> { - logging!(info, Type::Config, true, "[订阅更新] 开始更新订阅 {}", uid); + logging!(info, Type::Config, "[订阅更新] 开始更新订阅 {}", uid); let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true,保持兼容性 let url_opt = { @@ -138,23 +138,23 @@ pub async fn update_profile( }; if should_update { - logging!(info, Type::Config, true, "[订阅更新] 更新内核配置"); + logging!(info, Type::Config, "[订阅更新] 更新内核配置"); match CoreManager::global().update_config().await { Ok(_) => { - logging!(info, Type::Config, true, "[订阅更新] 更新成功"); + logging!(info, Type::Config, "[订阅更新] 更新成功"); handle::Handle::refresh_clash(); - if let Err(err) = cmd::proxy::force_refresh_proxies().await { - logging!( - error, - Type::Config, - true, - "[订阅更新] 代理组刷新失败: {}", - err - ); - } + // if let Err(err) = cmd::proxy::force_refresh_proxies().await { + // logging!( + // error, + // Type::Config, + // true, + // "[订阅更新] 代理组刷新失败: {}", + // err + // ); + // } } Err(err) => { - logging!(error, Type::Config, true, "[订阅更新] 更新失败: {}", err); + logging!(error, Type::Config, "[订阅更新] 更新失败: {}", err); handle::Handle::notice_message("update_failed", format!("{err}")); log::error!(target: "app", "{err}"); } diff --git a/clash-verge-rev/src-tauri/src/feat/proxy.rs b/clash-verge-rev/src-tauri/src/feat/proxy.rs index 5934193b31..7d520a8971 100644 --- a/clash-verge-rev/src-tauri/src/feat/proxy.rs +++ b/clash-verge-rev/src-tauri/src/feat/proxy.rs @@ -1,9 +1,6 @@ use crate::{ config::{Config, IVerge}, core::handle, - ipc::IpcManager, - logging, - utils::logging::Type, }; use std::env; use tauri_plugin_clipboard_manager::ClipboardExt; @@ -26,7 +23,7 @@ pub async fn toggle_system_proxy() { // 如果当前系统代理即将关闭,且自动关闭连接设置为true,则关闭所有连接 if enable && auto_close_connection - && let Err(err) = IpcManager::global().close_all_connections().await + && let Err(err) = handle::Handle::mihomo().await.close_all_connections().await { log::error!(target: "app", "Failed to close all connections: {err}"); } @@ -78,14 +75,7 @@ pub async fn copy_clash_env() { .unwrap_or_else(|| "127.0.0.1".to_string()), }; - let Some(app_handle) = handle::Handle::global().app_handle() else { - logging!( - error, - Type::System, - "Failed to get app handle for proxy operation" - ); - return; - }; + let app_handle = handle::Handle::app_handle(); let port = { Config::verge() .await diff --git a/clash-verge-rev/src-tauri/src/feat/window.rs b/clash-verge-rev/src-tauri/src/feat/window.rs index 0188b27a9a..2717e040fb 100644 --- a/clash-verge-rev/src-tauri/src/feat/window.rs +++ b/clash-verge-rev/src-tauri/src/feat/window.rs @@ -1,14 +1,11 @@ +use crate::config::Config; +use crate::core::event_driven_proxy::EventDrivenProxyManager; +use crate::core::{CoreManager, handle, sysopt}; +use crate::utils; use crate::utils::window_manager::WindowManager; -use crate::{ - config::Config, - core::{CoreManager, handle, sysopt}, - ipc::IpcManager, - logging, - module::lightweight, - utils::logging::Type, -}; +use crate::{logging, module::lightweight, utils::logging::Type}; -/// Open or close the dashboard window +/// Public API: open or close the dashboard pub async fn open_or_close_dashboard() { open_or_close_dashboard_internal().await } @@ -20,35 +17,27 @@ async fn open_or_close_dashboard_internal() { log::info!(target: "app", "Window toggle result: {result:?}"); } -/// 异步优化的应用退出函数 pub async fn quit() { - logging!(debug, Type::System, true, "启动退出流程"); + logging!(debug, Type::System, "启动退出流程"); + utils::server::shutdown_embedded_server(); // 获取应用句柄并设置退出标志 - let Some(app_handle) = handle::Handle::global().app_handle() else { - logging!( - error, - Type::System, - "Failed to get app handle for quit operation" - ); - return; - }; + let app_handle = handle::Handle::app_handle(); handle::Handle::global().set_is_exiting(); + EventDrivenProxyManager::global().notify_app_stopping(); // 优先关闭窗口,提供立即反馈 - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let _ = window.hide(); log::info!(target: "app", "窗口已隐藏"); } - // 使用异步任务处理资源清理,避免阻塞 - logging!(info, Type::System, true, "开始异步清理资源"); + logging!(info, Type::System, "开始异步清理资源"); let cleanup_result = clean_async().await; logging!( info, Type::System, - true, "资源清理完成,退出代码: {}", if cleanup_result { 0 } else { 1 } ); @@ -58,7 +47,7 @@ pub async fn quit() { async fn clean_async() -> bool { use tokio::time::{Duration, timeout}; - logging!(info, Type::System, true, "开始执行异步清理操作..."); + logging!(info, Type::System, "开始执行异步清理操作..."); // 1. 处理TUN模式 let tun_success = if Config::verge() @@ -68,9 +57,16 @@ async fn clean_async() -> bool { .unwrap_or(false) { let disable_tun = serde_json::json!({"tun": {"enable": false}}); + #[cfg(target_os = "windows")] + let tun_timeout = Duration::from_secs(2); + #[cfg(not(target_os = "windows"))] + let tun_timeout = Duration::from_secs(2); + match timeout( - Duration::from_secs(3), - IpcManager::global().patch_configs(disable_tun), + tun_timeout, + handle::Handle::mihomo() + .await + .patch_base_config(&disable_tun), ) .await { @@ -81,11 +77,12 @@ async fn clean_async() -> bool { } Ok(Err(e)) => { log::warn!(target: "app", "禁用TUN模式失败: {e}"); - false + // 超时不阻塞退出 + true } Err(_) => { - log::warn!(target: "app", "禁用TUN模式超时"); - false + log::warn!(target: "app", "禁用TUN模式超时(可能系统正在关机),继续退出流程"); + true } } } else { @@ -94,33 +91,130 @@ async fn clean_async() -> bool { // 2. 系统代理重置 let proxy_task = async { - match timeout( - Duration::from_secs(3), - sysopt::Sysopt::global().reset_sysproxy(), - ) - .await + #[cfg(target_os = "windows")] { - Ok(_) => { - log::info!(target: "app", "系统代理已重置"); - true + use sysproxy::{Autoproxy, Sysproxy}; + use winapi::um::winuser::{GetSystemMetrics, SM_SHUTTINGDOWN}; + + // 检查系统代理是否开启 + let sys_proxy_enabled = Config::verge() + .await + .latest_ref() + .enable_system_proxy + .unwrap_or(false); + + if !sys_proxy_enabled { + log::info!(target: "app", "系统代理未启用,跳过重置"); + return true; } - Err(_) => { - log::warn!(target: "app", "重置系统代理超时"); - false + + // 检查是否正在关机 + let is_shutting_down = unsafe { GetSystemMetrics(SM_SHUTTINGDOWN) != 0 }; + + if is_shutting_down { + // sysproxy-rs 操作注册表(避免.exe的dll错误) + log::info!(target: "app", "检测到正在关机,syspro-rs操作注册表关闭系统代理"); + + match Sysproxy::get_system_proxy() { + Ok(mut sysproxy) => { + sysproxy.enable = false; + if let Err(e) = sysproxy.set_system_proxy() { + log::warn!(target: "app", "关机时关闭系统代理失败: {e}"); + } else { + log::info!(target: "app", "系统代理已关闭(通过注册表)"); + } + } + Err(e) => { + log::warn!(target: "app", "关机时获取代理设置失败: {e}"); + } + } + + // 关闭自动代理配置 + if let Ok(mut autoproxy) = Autoproxy::get_auto_proxy() { + autoproxy.enable = false; + let _ = autoproxy.set_auto_proxy(); + } + + return true; + } + + // 正常退出:使用 sysproxy.exe 重置代理 + log::info!(target: "app", "sysproxy.exe重置系统代理"); + + match timeout( + Duration::from_secs(2), + sysopt::Sysopt::global().reset_sysproxy(), + ) + .await + { + Ok(Ok(_)) => { + log::info!(target: "app", "系统代理已重置"); + true + } + Ok(Err(e)) => { + log::warn!(target: "app", "重置系统代理失败: {e}"); + true + } + Err(_) => { + log::warn!(target: "app", "重置系统代理超时,继续退出流程"); + true + } + } + } + + // 非 Windows 平台:正常重置代理 + #[cfg(not(target_os = "windows"))] + { + let sys_proxy_enabled = Config::verge() + .await + .latest_ref() + .enable_system_proxy + .unwrap_or(false); + + if !sys_proxy_enabled { + log::info!(target: "app", "系统代理未启用,跳过重置"); + return true; + } + + log::info!(target: "app", "开始重置系统代理..."); + + match timeout( + Duration::from_millis(1500), + sysopt::Sysopt::global().reset_sysproxy(), + ) + .await + { + Ok(Ok(_)) => { + log::info!(target: "app", "系统代理已重置"); + true + } + Ok(Err(e)) => { + log::warn!(target: "app", "重置系统代理失败: {e}"); + true + } + Err(_) => { + log::warn!(target: "app", "重置系统代理超时,继续退出"); + true + } } } }; // 3. 核心服务停止 let core_task = async { - match timeout(Duration::from_secs(3), CoreManager::global().stop_core()).await { + #[cfg(target_os = "windows")] + let stop_timeout = Duration::from_secs(2); + #[cfg(not(target_os = "windows"))] + let stop_timeout = Duration::from_secs(3); + + match timeout(stop_timeout, CoreManager::global().stop_core()).await { Ok(_) => { - log::info!(target: "app", "核心服务已停止"); + log::info!(target: "app", "core已停止"); true } Err(_) => { - log::warn!(target: "app", "停止核心服务超时"); - false + log::warn!(target: "app", "停止core超时(可能系统正在关机),继续退出"); + true } } }; @@ -158,7 +252,6 @@ async fn clean_async() -> bool { logging!( info, Type::System, - true, "异步关闭操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}", tun_success, proxy_success, @@ -176,26 +269,29 @@ pub fn clean() -> bool { let (tx, rx) = std::sync::mpsc::channel(); AsyncHandler::spawn(move || async move { - logging!(info, Type::System, true, "开始执行关闭操作..."); + logging!(info, Type::System, "开始执行关闭操作..."); // 使用已有的异步清理函数 let cleanup_result = clean_async().await; - // 发送结果 let _ = tx.send(cleanup_result); }); - match rx.recv_timeout(std::time::Duration::from_secs(8)) { + #[cfg(target_os = "windows")] + let total_timeout = std::time::Duration::from_secs(5); + #[cfg(not(target_os = "windows"))] + let total_timeout = std::time::Duration::from_secs(8); + + match rx.recv_timeout(total_timeout) { Ok(result) => { - logging!(info, Type::System, true, "关闭操作完成,结果: {}", result); + logging!(info, Type::System, "关闭操作完成,结果: {}", result); result } Err(_) => { logging!( warn, Type::System, - true, - "清理操作超时,返回成功状态避免阻塞" + "清理操作超时(可能正在关机),返回成功避免阻塞" ); true } @@ -216,7 +312,7 @@ pub async fn hide() { add_light_weight_timer().await; } - if let Some(window) = handle::Handle::global().get_window() + if let Some(window) = handle::Handle::get_window() && window.is_visible().unwrap_or(false) { let _ = window.hide(); diff --git a/clash-verge-rev/src-tauri/src/ipc/general.rs b/clash-verge-rev/src-tauri/src/ipc/general.rs deleted file mode 100644 index 9e90ae9052..0000000000 --- a/clash-verge-rev/src-tauri/src/ipc/general.rs +++ /dev/null @@ -1,376 +0,0 @@ -use std::time::Duration; - -use kode_bridge::{ - ClientConfig, IpcHttpClient, LegacyResponse, - errors::{AnyError, AnyResult}, -}; -use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode}; - -use crate::{ - logging, singleton_with_logging, - utils::{dirs::ipc_path, logging::Type}, -}; - -// 定义用于URL路径的编码集合,只编码真正必要的字符 -const URL_PATH_ENCODE_SET: &AsciiSet = &CONTROLS - .add(b' ') // 空格 - .add(b'/') // 斜杠 - .add(b'?') // 问号 - .add(b'#') // 井号 - .add(b'&') // 和号 - .add(b'%'); // 百分号 - -// Helper function to create AnyError from string -fn create_error(msg: impl Into) -> AnyError { - Box::new(std::io::Error::other(msg.into())) -} - -pub struct IpcManager { - client: IpcHttpClient, -} - -impl IpcManager { - pub fn new() -> Self { - logging!(info, Type::Ipc, true, "Creating new IpcManager instance"); - let ipc_path_buf = ipc_path().unwrap_or_else(|e| { - logging!(error, Type::Ipc, true, "Failed to get IPC path: {}", e); - std::path::PathBuf::from("/tmp/clash-verge-ipc") // fallback path - }); - let ipc_path = ipc_path_buf.to_str().unwrap_or_default(); - let config = ClientConfig { - default_timeout: Duration::from_secs(5), - enable_pooling: false, - max_retries: 4, - retry_delay: Duration::from_millis(125), - max_concurrent_requests: 16, - max_requests_per_second: Some(64.0), - ..Default::default() - }; - #[allow(clippy::unwrap_used)] - let client = IpcHttpClient::with_config(ipc_path, config).unwrap(); - Self { client } - } -} - -impl IpcManager { - pub async fn request( - &self, - method: &str, - path: &str, - body: Option<&serde_json::Value>, - ) -> AnyResult { - self.client.request(method, path, body).await - } -} - -impl IpcManager { - pub async fn send_request( - &self, - method: &str, - path: &str, - body: Option<&serde_json::Value>, - ) -> AnyResult { - let response = IpcManager::global().request(method, path, body).await?; - match method { - "GET" => Ok(response.json()?), - "PATCH" => { - if response.status == 204 { - Ok(serde_json::json!({"code": 204})) - } else { - Ok(response.json()?) - } - } - "PUT" | "DELETE" => { - if response.status == 204 { - Ok(serde_json::json!({"code": 204})) - } else { - match response.json() { - Ok(json) => Ok(json), - Err(_) => Ok(serde_json::json!({ - "code": response.status, - "message": response.body, - "error": "failed to parse response as JSON" - })), - } - } - } - _ => match response.json() { - Ok(json) => Ok(json), - Err(_) => Ok(serde_json::json!({ - "code": response.status, - "message": response.body, - "error": "failed to parse response as JSON" - })), - }, - } - } - - // 基础代理信息获取 - pub async fn get_proxies(&self) -> AnyResult { - let url = "/proxies"; - self.send_request("GET", url, None).await - } - - // 代理提供者信息获取 - pub async fn get_providers_proxies(&self) -> AnyResult { - let url = "/providers/proxies"; - self.send_request("GET", url, None).await - } - - // 连接管理 - pub async fn get_connections(&self) -> AnyResult { - let url = "/connections"; - self.send_request("GET", url, None).await - } - - pub async fn delete_connection(&self, id: &str) -> AnyResult<()> { - let encoded_id = utf8_percent_encode(id, URL_PATH_ENCODE_SET).to_string(); - let url = format!("/connections/{encoded_id}"); - let response = self.send_request("DELETE", &url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"].as_str().unwrap_or("unknown error"), - )) - } - } - - pub async fn close_all_connections(&self) -> AnyResult<()> { - let url = "/connections"; - let response = self.send_request("DELETE", url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_owned(), - )) - } - } -} - -impl IpcManager { - #[allow(dead_code)] - pub async fn is_mihomo_running(&self) -> AnyResult<()> { - let url = "/version"; - let _response = self.send_request("GET", url, None).await?; - Ok(()) - } - - pub async fn put_configs_force(&self, clash_config_path: &str) -> AnyResult<()> { - let url = "/configs?force=true"; - let payload = serde_json::json!({ - "path": clash_config_path, - }); - let _response = self.send_request("PUT", url, Some(&payload)).await?; - Ok(()) - } - - pub async fn patch_configs(&self, config: serde_json::Value) -> AnyResult<()> { - let url = "/configs"; - let response = self.send_request("PATCH", url, Some(&config)).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_owned(), - )) - } - } - - pub async fn test_proxy_delay( - &self, - name: &str, - test_url: Option, - timeout: i32, - ) -> AnyResult { - let test_url = - test_url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string()); - - let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string(); - // 测速URL不再编码,直接传递 - let url = format!("/proxies/{encoded_name}/delay?url={test_url}&timeout={timeout}"); - - self.send_request("GET", &url, None).await - } - - // 版本和配置相关 - pub async fn get_version(&self) -> AnyResult { - let url = "/version"; - self.send_request("GET", url, None).await - } - - pub async fn get_config(&self) -> AnyResult { - let url = "/configs"; - self.send_request("GET", url, None).await - } - - pub async fn update_geo_data(&self) -> AnyResult<()> { - let url = "/configs/geo"; - let response = self.send_request("POST", url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_string(), - )) - } - } - - pub async fn upgrade_core(&self) -> AnyResult<()> { - let url = "/upgrade"; - let response = self.send_request("POST", url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_string(), - )) - } - } - - // 规则相关 - pub async fn get_rules(&self) -> AnyResult { - let url = "/rules"; - self.send_request("GET", url, None).await - } - - pub async fn get_rule_providers(&self) -> AnyResult { - let url = "/providers/rules"; - self.send_request("GET", url, None).await - } - - pub async fn update_rule_provider(&self, name: &str) -> AnyResult<()> { - let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string(); - let url = format!("/providers/rules/{encoded_name}"); - let response = self.send_request("PUT", &url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_string(), - )) - } - } - - // 代理相关 - pub async fn update_proxy(&self, group: &str, proxy: &str) -> AnyResult<()> { - // 使用 percent-encoding 进行正确的 URL 编码 - let encoded_group = utf8_percent_encode(group, URL_PATH_ENCODE_SET).to_string(); - let url = format!("/proxies/{encoded_group}"); - let payload = serde_json::json!({ - "name": proxy - }); - - // println!("group: {}, proxy: {}", group, proxy); - match self.send_request("PUT", &url, Some(&payload)).await { - Ok(_) => { - // println!("updateProxy response: {:?}", response); - Ok(()) - } - Err(e) => { - // println!("updateProxy encountered error: {}", e); - logging!( - error, - crate::utils::logging::Type::Ipc, - true, - "IPC: updateProxy encountered error: {} (ignored, always returning true)", - e - ); - Ok(()) - } - } - } - - pub async fn proxy_provider_health_check(&self, name: &str) -> AnyResult<()> { - let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string(); - let url = format!("/providers/proxies/{encoded_name}/healthcheck"); - let response = self.send_request("GET", &url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_string(), - )) - } - } - - pub async fn update_proxy_provider(&self, name: &str) -> AnyResult<()> { - let encoded_name = utf8_percent_encode(name, URL_PATH_ENCODE_SET).to_string(); - let url = format!("/providers/proxies/{encoded_name}"); - let response = self.send_request("PUT", &url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_string(), - )) - } - } - - // 延迟测试相关 - pub async fn get_group_proxy_delays( - &self, - group_name: &str, - url: Option, - timeout: i32, - ) -> AnyResult { - let test_url = url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string()); - - let encoded_group_name = utf8_percent_encode(group_name, URL_PATH_ENCODE_SET).to_string(); - // 测速URL不再编码,直接传递 - let url = format!("/group/{encoded_group_name}/delay?url={test_url}&timeout={timeout}"); - - self.send_request("GET", &url, None).await - } - - // 调试相关 - pub async fn is_debug_enabled(&self) -> AnyResult { - let url = "/debug/pprof"; - match self.send_request("GET", url, None).await { - Ok(_) => Ok(true), - Err(_) => Ok(false), - } - } - - pub async fn gc(&self) -> AnyResult<()> { - let url = "/debug/gc"; - let response = self.send_request("PUT", url, None).await?; - if response["code"] == 204 { - Ok(()) - } else { - Err(create_error( - response["message"] - .as_str() - .unwrap_or("unknown error") - .to_string(), - )) - } - } - - // 日志相关功能已迁移到 logs.rs 模块,使用流式处理 -} - -// Use singleton macro with logging -singleton_with_logging!(IpcManager, INSTANCE, "IpcManager"); diff --git a/clash-verge-rev/src-tauri/src/ipc/logs.rs b/clash-verge-rev/src-tauri/src/ipc/logs.rs deleted file mode 100644 index f0fd0ae5ff..0000000000 --- a/clash-verge-rev/src-tauri/src/ipc/logs.rs +++ /dev/null @@ -1,330 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{collections::VecDeque, sync::Arc, time::Instant}; -use tauri::async_runtime::JoinHandle; -use tokio::{sync::RwLock, time::Duration}; - -use crate::{ - ipc::monitor::MonitorData, - logging, - process::AsyncHandler, - singleton_with_logging, - utils::{dirs::ipc_path, logging::Type}, -}; - -const MAX_LOGS: usize = 1000; // Maximum number of logs to keep in memory - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct LogData { - #[serde(rename = "type")] - pub log_type: String, - pub payload: String, -} - -#[derive(Debug, Clone)] -pub struct LogItem { - pub log_type: String, - pub payload: String, - pub time: String, -} - -impl LogItem { - fn new(log_type: String, payload: String) -> Self { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_else(|_| std::time::Duration::from_secs(0)) - .as_secs(); - - // Simple time formatting (HH:MM:SS) - let hours = (now / 3600) % 24; - let minutes = (now / 60) % 60; - let seconds = now % 60; - let time_str = format!("{hours:02}:{minutes:02}:{seconds:02}"); - - Self { - log_type, - payload, - time: time_str, - } - } -} - -#[derive(Debug, Clone)] -pub struct CurrentLogs { - pub logs: VecDeque, - // pub level: String, - pub last_updated: Instant, -} - -impl Default for CurrentLogs { - fn default() -> Self { - Self { - logs: VecDeque::with_capacity(MAX_LOGS), - // level: "info".to_string(), - last_updated: Instant::now(), - } - } -} - -impl MonitorData for CurrentLogs { - fn mark_fresh(&mut self) { - self.last_updated = Instant::now(); - } - - fn is_fresh_within(&self, duration: Duration) -> bool { - self.last_updated.elapsed() < duration - } -} - -// Logs monitor with streaming support -pub struct LogsMonitor { - current: Arc>, - task_handle: Arc>>>, - current_monitoring_level: Arc>>, -} - -// Use singleton_with_logging macro -singleton_with_logging!(LogsMonitor, INSTANCE, "LogsMonitor"); - -impl LogsMonitor { - fn new() -> Self { - let current = Arc::new(RwLock::new(CurrentLogs::default())); - - Self { - current, - task_handle: Arc::new(RwLock::new(None)), - current_monitoring_level: Arc::new(RwLock::new(None)), - } - } - - pub async fn start_monitoring(&self, level: Option) { - let filter_level = level.clone().unwrap_or_else(|| "info".to_string()); - - // Check if we're already monitoring the same level - // let level_changed = { - // let current_level = self.current_monitoring_level.read().await; - // if let Some(existing_level) = current_level.as_ref() { - // if existing_level == &filter_level { - // logging!( - // info, - // Type::Ipc, - // true, - // "LogsMonitor: Already monitoring level '{}', skipping duplicate request", - // filter_level - // ); - // return; - // } - // true // Level changed - // } else { - // true // First time or was stopped - // } - // }; - - // Stop existing monitoring task if level changed or first time - { - let mut handle = self.task_handle.write().await; - if let Some(task) = handle.take() { - task.abort(); - logging!( - info, - Type::Ipc, - true, - "LogsMonitor: Stopped previous monitoring task (level changed)" - ); - } - } - - // We want to keep the logs cache even if the level changes, - // so we don't clear it here. The cache will be cleared only when the level changes - // and a new task is started. This allows us to keep logs from previous levels - // even if the level changes during monitoring. - // Clear logs cache when level changes to ensure fresh data - // if level_changed { - // let mut current = self.current.write().await; - // current.logs.clear(); - // current.level = filter_level.clone(); - // current.mark_fresh(); - // logging!( - // info, - // Type::Ipc, - // true, - // "LogsMonitor: Cleared logs cache due to level change to '{}'", - // filter_level - // ); - // } - - // Update current monitoring level - { - let mut current_level = self.current_monitoring_level.write().await; - *current_level = Some(filter_level.clone()); - } - - let monitor_current = Arc::clone(&self.current); - - let task = AsyncHandler::spawn(move || async move { - loop { - // Get fresh IPC path and client for each connection attempt - let (_ipc_path_buf, client) = match Self::create_ipc_client() { - Ok((path, client)) => (path, client), - Err(e) => { - logging!(error, Type::Ipc, true, "Failed to create IPC client: {}", e); - tokio::time::sleep(Duration::from_secs(2)).await; - continue; - } - }; - - let url = if filter_level == "all" { - "/logs".to_string() - } else { - format!("/logs?level={filter_level}") - }; - - logging!( - info, - Type::Ipc, - true, - "LogsMonitor: Starting stream for {}", - url - ); - - let _ = client - .get(&url) - .timeout(Duration::from_secs(30)) - .process_lines(|line| { - Self::process_log_line(line, Arc::clone(&monitor_current)) - }) - .await; - - // Wait before retrying - tokio::time::sleep(Duration::from_secs(2)).await; - } - }); - - // Store the task handle - { - let mut handle = self.task_handle.write().await; - *handle = Some(task); - } - - logging!( - info, - Type::Ipc, - true, - "LogsMonitor: Started new monitoring task for level: {:?}", - level - ); - } - - pub async fn stop_monitoring(&self) { - // Stop monitoring task but keep logs - { - let mut handle = self.task_handle.write().await; - if let Some(task) = handle.take() { - task.abort(); - logging!( - info, - Type::Ipc, - true, - "LogsMonitor: Stopped monitoring task" - ); - } - } - - // Reset monitoring level - { - let mut monitoring_level = self.current_monitoring_level.write().await; - *monitoring_level = None; - } - } - - fn create_ipc_client() -> Result< - (std::path::PathBuf, kode_bridge::IpcStreamClient), - Box, - > { - use kode_bridge::IpcStreamClient; - - let ipc_path_buf = ipc_path()?; - let ipc_path = ipc_path_buf.to_str().ok_or("Invalid IPC path")?; - let client = IpcStreamClient::new(ipc_path)?; - Ok((ipc_path_buf, client)) - } - - fn process_log_line( - line: &str, - current: Arc>, - ) -> Result<(), Box> { - if let Ok(log_data) = serde_json::from_str::(line.trim()) { - // Server-side filtering via query parameters handles the level filtering - // We only need to accept all logs since filtering is done at the endpoint level - let log_item = LogItem::new(log_data.log_type, log_data.payload); - - AsyncHandler::spawn(move || async move { - let mut logs = current.write().await; - - // Add new log - logs.logs.push_back(log_item); - - // Keep only the last 1000 logs - if logs.logs.len() > 1000 { - logs.logs.pop_front(); - } - - logs.mark_fresh(); - }); - } - Ok(()) - } - - pub async fn current(&self) -> CurrentLogs { - self.current.read().await.clone() - } - - pub async fn clear_logs(&self) { - let mut current = self.current.write().await; - current.logs.clear(); - current.mark_fresh(); - logging!( - info, - Type::Ipc, - true, - "LogsMonitor: Cleared frontend logs (monitoring continues)" - ); - } - - pub async fn get_logs_as_json(&self) -> serde_json::Value { - let current = self.current().await; - - // Simply return all cached logs since filtering is handled by start_monitoring - // and the cache is cleared when level changes - let logs: Vec = current - .logs - .iter() - .map(|log| { - serde_json::json!({ - "type": log.log_type, - "payload": log.payload, - "time": log.time - }) - }) - .collect(); - - serde_json::Value::Array(logs) - } -} - -pub async fn start_logs_monitoring(level: Option) { - LogsMonitor::global().start_monitoring(level).await; -} - -pub async fn stop_logs_monitoring() { - LogsMonitor::global().stop_monitoring().await; -} - -pub async fn clear_logs() { - LogsMonitor::global().clear_logs().await; -} - -pub async fn get_logs_json() -> serde_json::Value { - LogsMonitor::global().get_logs_as_json().await -} diff --git a/clash-verge-rev/src-tauri/src/ipc/memory.rs b/clash-verge-rev/src-tauri/src/ipc/memory.rs deleted file mode 100644 index ed05168935..0000000000 --- a/clash-verge-rev/src-tauri/src/ipc/memory.rs +++ /dev/null @@ -1,119 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Instant}; -use tokio::{sync::RwLock, time::Duration}; - -use crate::{ - ipc::monitor::{IpcStreamMonitor, MonitorData, StreamingParser}, - process::AsyncHandler, - singleton_lazy_with_logging, - utils::format::fmt_bytes, -}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct MemoryData { - pub inuse: u64, - pub oslimit: u64, -} - -#[derive(Debug, Clone)] -pub struct CurrentMemory { - pub inuse: u64, - pub oslimit: u64, - pub last_updated: Instant, -} - -impl Default for CurrentMemory { - fn default() -> Self { - Self { - inuse: 0, - oslimit: 0, - last_updated: Instant::now(), - } - } -} - -impl MonitorData for CurrentMemory { - fn mark_fresh(&mut self) { - self.last_updated = Instant::now(); - } - - fn is_fresh_within(&self, duration: Duration) -> bool { - self.last_updated.elapsed() < duration - } -} - -impl StreamingParser for CurrentMemory { - fn parse_and_update( - line: &str, - current: Arc>, - ) -> Result<(), Box> { - if let Ok(memory) = serde_json::from_str::(line.trim()) { - AsyncHandler::spawn(move || async move { - let mut current_guard = current.write().await; - current_guard.inuse = memory.inuse; - current_guard.oslimit = memory.oslimit; - current_guard.mark_fresh(); - }); - } - Ok(()) - } -} - -// Minimal memory monitor using the new architecture -pub struct MemoryMonitor { - monitor: IpcStreamMonitor, -} - -impl Default for MemoryMonitor { - fn default() -> Self { - MemoryMonitor { - monitor: IpcStreamMonitor::new( - "/memory".to_string(), - Duration::from_secs(10), - Duration::from_secs(2), - Duration::from_secs(10), - ), - } - } -} - -// Use simplified singleton_lazy_with_logging macro -singleton_lazy_with_logging!( - MemoryMonitor, - INSTANCE, - "MemoryMonitor", - MemoryMonitor::default -); - -impl MemoryMonitor { - pub async fn current(&self) -> CurrentMemory { - self.monitor.current().await - } - - pub async fn is_fresh(&self) -> bool { - self.monitor.is_fresh().await - } -} - -pub async fn get_current_memory() -> CurrentMemory { - MemoryMonitor::global().current().await -} - -pub async fn get_formatted_memory() -> (String, String, f64, bool) { - let monitor = MemoryMonitor::global(); - let memory = monitor.current().await; - let is_fresh = monitor.is_fresh().await; - - let usage_percent = if memory.oslimit > 0 { - (memory.inuse as f64 / memory.oslimit as f64) * 100.0 - } else { - 0.0 - }; - - ( - fmt_bytes(memory.inuse), - fmt_bytes(memory.oslimit), - usage_percent, - is_fresh, - ) -} diff --git a/clash-verge-rev/src-tauri/src/ipc/mod.rs b/clash-verge-rev/src-tauri/src/ipc/mod.rs deleted file mode 100644 index 8b2f42f877..0000000000 --- a/clash-verge-rev/src-tauri/src/ipc/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub mod general; -pub mod logs; -pub mod memory; -pub mod monitor; -pub mod traffic; - -pub use general::IpcManager; -pub use logs::{clear_logs, get_logs_json, start_logs_monitoring, stop_logs_monitoring}; -pub use memory::{get_current_memory, get_formatted_memory}; -pub use traffic::{get_current_traffic, get_formatted_traffic}; - -pub struct Rate { - // pub up: usize, - // pub down: usize, -} diff --git a/clash-verge-rev/src-tauri/src/ipc/monitor.rs b/clash-verge-rev/src-tauri/src/ipc/monitor.rs deleted file mode 100644 index f12f363358..0000000000 --- a/clash-verge-rev/src-tauri/src/ipc/monitor.rs +++ /dev/null @@ -1,120 +0,0 @@ -use kode_bridge::IpcStreamClient; -use std::sync::Arc; -use tokio::{sync::RwLock, time::Duration}; - -use crate::{ - logging, - process::AsyncHandler, - utils::{dirs::ipc_path, logging::Type}, -}; - -/// Generic base structure for IPC monitoring data with freshness tracking -pub trait MonitorData: Clone + Send + Sync + 'static { - /// Update the last_updated timestamp to now - fn mark_fresh(&mut self); - - /// Check if data is fresh based on the given duration - fn is_fresh_within(&self, duration: Duration) -> bool; -} - -/// Trait for parsing streaming data and updating monitor state -pub trait StreamingParser: MonitorData { - /// Parse a line of streaming data and update the current state - fn parse_and_update( - line: &str, - current: Arc>, - ) -> Result<(), Box>; -} - -/// Generic IPC stream monitor that handles the common streaming pattern -pub struct IpcStreamMonitor -where - T: MonitorData + StreamingParser + Default, -{ - current: Arc>, - #[allow(dead_code)] - endpoint: String, - #[allow(dead_code)] - timeout: Duration, - #[allow(dead_code)] - retry_interval: Duration, - freshness_duration: Duration, -} - -impl IpcStreamMonitor -where - T: MonitorData + StreamingParser + Default, -{ - pub fn new( - endpoint: String, - timeout: Duration, - retry_interval: Duration, - freshness_duration: Duration, - ) -> Self { - let current = Arc::new(RwLock::new(T::default())); - let monitor_current = Arc::clone(¤t); - let endpoint_clone = endpoint.clone(); - - // Start the monitoring task - AsyncHandler::spawn(move || async move { - Self::streaming_task(monitor_current, endpoint_clone, timeout, retry_interval).await; - }); - - Self { - current, - endpoint, - timeout, - retry_interval, - freshness_duration, - } - } - - pub async fn current(&self) -> T { - self.current.read().await.clone() - } - - pub async fn is_fresh(&self) -> bool { - self.current - .read() - .await - .is_fresh_within(self.freshness_duration) - } - - /// The core streaming task that can be specialized per monitor type - async fn streaming_task( - current: Arc>, - endpoint: String, - timeout: Duration, - retry_interval: Duration, - ) { - loop { - let ipc_path_buf = match ipc_path() { - Ok(path) => path, - Err(e) => { - logging!(error, Type::Ipc, true, "Failed to get IPC path: {}", e); - tokio::time::sleep(retry_interval).await; - continue; - } - }; - - let ipc_path = ipc_path_buf.to_str().unwrap_or_default(); - - let client = match IpcStreamClient::new(ipc_path) { - Ok(client) => client, - Err(e) => { - logging!(error, Type::Ipc, true, "Failed to create IPC client: {}", e); - tokio::time::sleep(retry_interval).await; - continue; - } - }; - - let _ = client - .get(&endpoint) - .timeout(timeout) - .process_lines(|line| T::parse_and_update(line, Arc::clone(¤t))) - .await; - - tokio::time::sleep(retry_interval).await; - } - } -} diff --git a/clash-verge-rev/src-tauri/src/ipc/traffic.rs b/clash-verge-rev/src-tauri/src/ipc/traffic.rs deleted file mode 100644 index ac30820ef5..0000000000 --- a/clash-verge-rev/src-tauri/src/ipc/traffic.rs +++ /dev/null @@ -1,153 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Instant}; -use tokio::{sync::RwLock, time::Duration}; - -use crate::{ - ipc::monitor::{IpcStreamMonitor, MonitorData, StreamingParser}, - process::AsyncHandler, - singleton_lazy_with_logging, - utils::format::fmt_bytes, -}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct TrafficData { - pub up: u64, - pub down: u64, -} - -#[derive(Debug, Clone)] -pub struct CurrentTraffic { - pub up_rate: u64, - pub down_rate: u64, - pub total_up: u64, - pub total_down: u64, - pub last_updated: Instant, -} - -impl Default for CurrentTraffic { - fn default() -> Self { - Self { - up_rate: 0, - down_rate: 0, - total_up: 0, - total_down: 0, - last_updated: Instant::now(), - } - } -} - -impl MonitorData for CurrentTraffic { - fn mark_fresh(&mut self) { - self.last_updated = Instant::now(); - } - - fn is_fresh_within(&self, duration: Duration) -> bool { - self.last_updated.elapsed() < duration - } -} - -// Traffic monitoring state for calculating rates -#[derive(Debug, Clone, Default)] -pub struct TrafficMonitorState { - pub current: CurrentTraffic, - pub last_traffic: Option, -} - -impl MonitorData for TrafficMonitorState { - fn mark_fresh(&mut self) { - self.current.mark_fresh(); - } - - fn is_fresh_within(&self, duration: Duration) -> bool { - self.current.is_fresh_within(duration) - } -} - -impl StreamingParser for TrafficMonitorState { - fn parse_and_update( - line: &str, - current: Arc>, - ) -> Result<(), Box> { - if let Ok(traffic) = serde_json::from_str::(line.trim()) { - AsyncHandler::spawn(move || async move { - let mut state_guard = current.write().await; - - let (up_rate, down_rate) = state_guard - .last_traffic - .as_ref() - .map(|l| { - ( - traffic.up.saturating_sub(l.up), - traffic.down.saturating_sub(l.down), - ) - }) - .unwrap_or((0, 0)); - - state_guard.current = CurrentTraffic { - up_rate, - down_rate, - total_up: traffic.up, - total_down: traffic.down, - last_updated: Instant::now(), - }; - - state_guard.last_traffic = Some(traffic); - }); - } - Ok(()) - } -} - -// Minimal traffic monitor using the new architecture -pub struct TrafficMonitor { - monitor: IpcStreamMonitor, -} - -impl Default for TrafficMonitor { - fn default() -> Self { - TrafficMonitor { - monitor: IpcStreamMonitor::new( - "/traffic".to_string(), - Duration::from_secs(10), - Duration::from_secs(1), - Duration::from_secs(5), - ), - } - } -} - -// Use simplified singleton_lazy_with_logging macro -singleton_lazy_with_logging!( - TrafficMonitor, - INSTANCE, - "TrafficMonitor", - TrafficMonitor::default -); - -impl TrafficMonitor { - pub async fn current(&self) -> CurrentTraffic { - self.monitor.current().await.current - } - - pub async fn is_fresh(&self) -> bool { - self.monitor.is_fresh().await - } -} - -pub async fn get_current_traffic() -> CurrentTraffic { - TrafficMonitor::global().current().await -} - -pub async fn get_formatted_traffic() -> (String, String, String, String, bool) { - let monitor = TrafficMonitor::global(); - let traffic = monitor.current().await; - let is_fresh = monitor.is_fresh().await; - - ( - fmt_bytes(traffic.up_rate), - fmt_bytes(traffic.down_rate), - fmt_bytes(traffic.total_up), - fmt_bytes(traffic.total_down), - is_fresh, - ) -} diff --git a/clash-verge-rev/src-tauri/src/lib.rs b/clash-verge-rev/src-tauri/src/lib.rs index 44683d27d2..f50ffe5ee7 100644 --- a/clash-verge-rev/src-tauri/src/lib.rs +++ b/clash-verge-rev/src-tauri/src/lib.rs @@ -1,34 +1,32 @@ #![allow(non_snake_case)] #![recursion_limit = "512"] -mod cache; mod cmd; pub mod config; mod core; mod enhance; mod feat; -mod ipc; mod module; mod process; mod utils; #[cfg(target_os = "macos")] use crate::utils::window_manager::WindowManager; use crate::{ - core::handle, - core::hotkey, + core::{EventDrivenProxyManager, handle, hotkey}, process::AsyncHandler, utils::{resolve, server}, }; use config::Config; -use tauri::AppHandle; -#[cfg(target_os = "macos")] -use tauri::Manager; +use once_cell::sync::OnceCell; +use tauri::{AppHandle, Manager}; #[cfg(target_os = "macos")] use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_deep_link::DeepLinkExt; use tokio::time::{Duration, timeout}; use utils::logging::Type; +pub static APP_HANDLE: OnceCell = OnceCell::new(); + /// Application initialization helper functions mod app_init { use super::*; @@ -36,27 +34,22 @@ mod app_init { /// Initialize singleton monitoring for other instances pub fn init_singleton_check() { AsyncHandler::spawn_blocking(move || async move { - logging!(info, Type::Setup, true, "开始检查单例实例..."); + logging!(info, Type::Setup, "开始检查单例实例..."); match timeout(Duration::from_millis(500), server::check_singleton()).await { Ok(result) => { if result.is_err() { - logging!(info, Type::Setup, true, "检测到已有应用实例运行"); - if let Some(app_handle) = handle::Handle::global().app_handle() { + logging!(info, Type::Setup, "检测到已有应用实例运行"); + if let Some(app_handle) = APP_HANDLE.get() { app_handle.exit(0); } else { std::process::exit(0); } } else { - logging!(info, Type::Setup, true, "未检测到其他应用实例"); + logging!(info, Type::Setup, "未检测到其他应用实例"); } } Err(_) => { - logging!( - warn, - Type::Setup, - true, - "单例检查超时,假定没有其他实例运行" - ); + logging!(warn, Type::Setup, "单例检查超时,假定没有其他实例运行"); } } }); @@ -75,7 +68,13 @@ mod app_init { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_deep_link::init()) - .plugin(tauri_plugin_http::init()); + .plugin(tauri_plugin_http::init()) + .plugin( + tauri_plugin_mihomo::Builder::new() + .protocol(tauri_plugin_mihomo::models::Protocol::LocalSocket) + .socket_path(crate::config::IClashTemp::guard_external_controller_ipc()) + .build(), + ); // Devtools plugin only in debug mode with feature tauri-dev // to avoid duplicated registering of logger since the devtools plugin also registers a logger @@ -90,7 +89,7 @@ mod app_init { pub fn setup_deep_links(app: &tauri::App) -> Result<(), Box> { #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] { - logging!(info, Type::Setup, true, "注册深层链接..."); + logging!(info, Type::Setup, "注册深层链接..."); app.deep_link().register_all()?; } @@ -99,7 +98,7 @@ mod app_init { if let Some(url) = url { AsyncHandler::spawn(|| async { if let Err(e) = resolve::resolve_scheme(url).await { - logging!(error, Type::Setup, true, "Failed to resolve scheme: {}", e); + logging!(error, Type::Setup, "Failed to resolve scheme: {}", e); } }); } @@ -127,7 +126,7 @@ mod app_init { /// Setup window state management pub fn setup_window_state(app: &tauri::App) -> Result<(), Box> { - logging!(info, Type::Setup, true, "初始化窗口状态管理..."); + logging!(info, Type::Setup, "初始化窗口状态管理..."); let window_state_plugin = tauri_plugin_window_state::Builder::new() .with_filename("window_state.json") .with_state_flags(tauri_plugin_window_state::StateFlags::default()) @@ -184,46 +183,13 @@ mod app_init { cmd::update_proxy_chain_config_in_runtime, cmd::invoke_uwp_tool, cmd::copy_clash_env, - cmd::get_proxies, - cmd::force_refresh_proxies, - cmd::get_providers_proxies, cmd::sync_tray_proxy_selection, - cmd::update_proxy_and_sync, cmd::save_dns_config, cmd::apply_dns_config, cmd::check_dns_config_exists, cmd::get_dns_config_content, cmd::validate_dns_config, - cmd::get_clash_version, - cmd::get_clash_config, - cmd::force_refresh_clash_config, - cmd::update_geo_data, - cmd::upgrade_clash_core, - cmd::get_clash_rules, - cmd::update_proxy_choice, - cmd::get_proxy_providers, - cmd::get_rule_providers, - cmd::proxy_provider_health_check, - cmd::update_proxy_provider, - cmd::update_rule_provider, - cmd::get_clash_connections, - cmd::delete_clash_connection, - cmd::close_all_clash_connections, - cmd::get_group_proxy_delays, - cmd::is_clash_debug_enabled, - cmd::clash_gc, - // Logging and monitoring cmd::get_clash_logs, - cmd::start_logs_monitoring, - cmd::stop_logs_monitoring, - cmd::clear_logs, - cmd::get_traffic_data, - cmd::get_memory_data, - cmd::get_formatted_traffic_data, - cmd::get_formatted_memory_data, - cmd::get_system_monitor_overview, - cmd::start_traffic_service, - cmd::stop_traffic_service, // Verge configuration cmd::get_verge_config, cmd::patch_verge_config, @@ -251,8 +217,6 @@ mod app_init { // Script validation cmd::script_validate_notice, cmd::validate_script_file, - // Clash API - cmd::clash_api_get_proxy_delay, // Backup and WebDAV cmd::create_webdav_backup, cmd::save_webdav_config, @@ -278,15 +242,76 @@ pub fn run() { // Set Linux environment variable #[cfg(target_os = "linux")] { - unsafe { - std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); - } - let desktop_env = std::env::var("XDG_CURRENT_DESKTOP") .unwrap_or_default() .to_uppercase(); + let session_desktop = std::env::var("XDG_SESSION_DESKTOP") + .unwrap_or_default() + .to_uppercase(); + let desktop_session = std::env::var("DESKTOP_SESSION") + .unwrap_or_default() + .to_uppercase(); let is_kde_desktop = desktop_env.contains("KDE"); let is_plasma_desktop = desktop_env.contains("PLASMA"); + let is_hyprland_desktop = desktop_env.contains("HYPR") + || session_desktop.contains("HYPR") + || desktop_session.contains("HYPR"); + + let is_wayland_session = std::env::var("XDG_SESSION_TYPE") + .map(|value| value.eq_ignore_ascii_case("wayland")) + .unwrap_or(false) + || std::env::var("WAYLAND_DISPLAY").is_ok(); + let prefer_native_wayland = + is_wayland_session && (is_kde_desktop || is_plasma_desktop || is_hyprland_desktop); + let dmabuf_override = std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER"); + + if prefer_native_wayland { + let compositor_label = if is_hyprland_desktop { + "Hyprland" + } else if is_plasma_desktop { + "KDE Plasma" + } else { + "KDE" + }; + + if matches!(dmabuf_override.as_deref(), Ok("1")) { + unsafe { + std::env::remove_var("WEBKIT_DISABLE_DMABUF_RENDERER"); + } + logging!( + info, + Type::Setup, + "Wayland + {} detected: Re-enabled WebKit DMABUF renderer to avoid Cairo surface failures.", + compositor_label + ); + } else { + logging!( + info, + Type::Setup, + "Wayland + {} detected: Using native Wayland backend for reliable rendering.", + compositor_label + ); + } + } else { + if dmabuf_override.is_err() { + unsafe { + std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + } + } + + // Force X11 backend for tray icon compatibility on Wayland + if is_wayland_session { + unsafe { + std::env::set_var("GDK_BACKEND", "x11"); + std::env::remove_var("WAYLAND_DISPLAY"); + } + logging!( + info, + Type::Setup, + "Wayland detected: Forcing X11 backend for tray icon compatibility" + ); + } + } if is_kde_desktop || is_plasma_desktop { unsafe { @@ -295,7 +320,6 @@ pub fn run() { logging!( info, Type::Setup, - true, "KDE detected: Disabled GTK CSD for better titlebar stability." ); } @@ -304,44 +328,35 @@ pub fn run() { // Create and configure the Tauri builder let builder = app_init::setup_plugins(tauri::Builder::default()) .setup(|app| { - logging!(info, Type::Setup, true, "开始应用初始化..."); + logging!(info, Type::Setup, "开始应用初始化..."); + + #[allow(clippy::expect_used)] + APP_HANDLE + .set(app.app_handle().clone()) + .expect("failed to set global app handle"); // Setup autostart plugin if let Err(e) = app_init::setup_autostart(app) { - logging!(error, Type::Setup, true, "Failed to setup autostart: {}", e); + logging!(error, Type::Setup, "Failed to setup autostart: {}", e); } // Setup deep links if let Err(e) = app_init::setup_deep_links(app) { - logging!( - error, - Type::Setup, - true, - "Failed to setup deep links: {}", - e - ); + logging!(error, Type::Setup, "Failed to setup deep links: {}", e); } // Setup window state management if let Err(e) = app_init::setup_window_state(app) { - logging!( - error, - Type::Setup, - true, - "Failed to setup window state: {}", - e - ); + logging!(error, Type::Setup, "Failed to setup window state: {}", e); } - let app_handle = app.handle().clone(); + logging!(info, Type::Setup, "执行主要设置操作..."); - logging!(info, Type::Setup, true, "执行主要设置操作..."); - - resolve::resolve_setup_handle(app_handle); + resolve::resolve_setup_handle(); resolve::resolve_setup_async(); resolve::resolve_setup_sync(); - logging!(info, Type::Setup, true, "初始化完成,继续执行"); + logging!(info, Type::Setup, "初始化完成,继续执行"); Ok(()) }) .invoke_handler(app_init::generate_handlers()); @@ -353,14 +368,24 @@ pub fn run() { use super::*; /// Handle application ready/resumed events - pub fn handle_ready_resumed(app_handle: &AppHandle) { - logging!(info, Type::System, true, "应用就绪或恢复"); - handle::Handle::global().init(app_handle.clone()); + pub fn handle_ready_resumed(_app_handle: &AppHandle) { + // 双重检查:确保不在退出状态 + if handle::Handle::global().is_exiting() { + logging!( + debug, + Type::System, + "handle_ready_resumed: 应用正在退出,跳过处理" + ); + return; + } + + logging!(info, Type::System, "应用就绪或恢复"); + handle::Handle::global().init(); #[cfg(target_os = "macos")] { - if let Some(window) = app_handle.get_webview_window("main") { - logging!(info, Type::Window, true, "设置macOS窗口标题"); + if let Some(window) = _app_handle.get_webview_window("main") { + logging!(info, Type::Window, "设置macOS窗口标题"); let _ = window.set_title("Clash Verge"); } } @@ -368,33 +393,26 @@ pub fn run() { /// Handle application reopen events (macOS) #[cfg(target_os = "macos")] - pub async fn handle_reopen(app_handle: &AppHandle, has_visible_windows: bool) { + pub async fn handle_reopen(has_visible_windows: bool) { logging!( info, Type::System, - true, "处理 macOS 应用重新打开事件: has_visible_windows={}", has_visible_windows ); - handle::Handle::global().init(app_handle.clone()); + handle::Handle::global().init(); if !has_visible_windows { // 当没有可见窗口时,设置为 regular 模式并显示主窗口 handle::Handle::global().set_activation_policy_regular(); - logging!(info, Type::System, true, "没有可见窗口,尝试显示主窗口"); + logging!(info, Type::System, "没有可见窗口,尝试显示主窗口"); let result = WindowManager::show_main_window().await; - logging!( - info, - Type::System, - true, - "窗口显示操作完成,结果: {:?}", - result - ); + logging!(info, Type::System, "窗口显示操作完成,结果: {:?}", result); } else { - logging!(info, Type::System, true, "已有可见窗口,无需额外操作"); + logging!(info, Type::System, "已有可见窗口,无需额外操作"); } } @@ -410,10 +428,10 @@ pub fn run() { log::info!(target: "app", "closing window..."); if let tauri::WindowEvent::CloseRequested { api, .. } = api { api.prevent_close(); - if let Some(window) = core::handle::Handle::global().get_window() { + if let Some(window) = core::handle::Handle::get_window() { let _ = window.hide(); } else { - logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在"); + logging!(warn, Type::Window, "尝试隐藏窗口但窗口不存在"); } } } @@ -435,20 +453,20 @@ pub fn run() { .register_system_hotkey(SystemHotkey::CmdQ) .await { - logging!(error, Type::Hotkey, true, "Failed to register CMD+Q: {}", e); + logging!(error, Type::Hotkey, "Failed to register CMD+Q: {}", e); } if let Err(e) = hotkey::Hotkey::global() .register_system_hotkey(SystemHotkey::CmdW) .await { - logging!(error, Type::Hotkey, true, "Failed to register CMD+W: {}", e); + logging!(error, Type::Hotkey, "Failed to register CMD+W: {}", e); } } if !is_enable_global_hotkey && let Err(e) = hotkey::Hotkey::global().init().await { - logging!(error, Type::Hotkey, true, "Failed to init hotkeys: {}", e); + logging!(error, Type::Hotkey, "Failed to init hotkeys: {}", e); } return; } @@ -460,29 +478,17 @@ pub fn run() { if let Err(e) = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdQ) { - logging!( - error, - Type::Hotkey, - true, - "Failed to unregister CMD+Q: {}", - e - ); + logging!(error, Type::Hotkey, "Failed to unregister CMD+Q: {}", e); } if let Err(e) = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW) { - logging!( - error, - Type::Hotkey, - true, - "Failed to unregister CMD+W: {}", - e - ); + logging!(error, Type::Hotkey, "Failed to unregister CMD+W: {}", e); } } if !is_enable_global_hotkey && let Err(e) = hotkey::Hotkey::global().reset() { - logging!(error, Type::Hotkey, true, "Failed to reset hotkeys: {}", e); + logging!(error, Type::Hotkey, "Failed to reset hotkeys: {}", e); } }); } @@ -498,7 +504,6 @@ pub fn run() { logging!( error, Type::Hotkey, - true, "Failed to unregister CMD+Q on destroy: {}", e ); @@ -509,7 +514,6 @@ pub fn run() { logging!( error, Type::Hotkey, - true, "Failed to unregister CMD+W on destroy: {}", e ); @@ -525,7 +529,6 @@ pub fn run() { logging!( error, Type::Setup, - true, "Failed to build Tauri application: {}", e ); @@ -535,6 +538,11 @@ pub fn run() { app.run(|app_handle, e| { match e { tauri::RunEvent::Ready | tauri::RunEvent::Resumed => { + // 如果正在退出,忽略 Ready/Resumed 事件 + if core::handle::Handle::global().is_exiting() { + logging!(debug, Type::System, "忽略 Ready/Resumed 事件,应用正在退出"); + return; + } event_handlers::handle_ready_resumed(app_handle); } #[cfg(target_os = "macos")] @@ -542,22 +550,54 @@ pub fn run() { has_visible_windows, .. } => { - let app_handle = app_handle.clone(); + // 如果正在退出,忽略 Reopen 事件 + if core::handle::Handle::global().is_exiting() { + logging!(debug, Type::System, "忽略 Reopen 事件,应用正在退出"); + return; + } AsyncHandler::spawn(move || async move { - event_handlers::handle_reopen(&app_handle, has_visible_windows).await; + event_handlers::handle_reopen(has_visible_windows).await; }); } tauri::RunEvent::ExitRequested { api, code, .. } => { + tauri::async_runtime::block_on(async { + let _ = handle::Handle::mihomo() + .await + .clear_all_ws_connections() + .await; + }); + // 如果已经在退出流程中,不要阻止退出 + if core::handle::Handle::global().is_exiting() { + logging!( + info, + Type::System, + "应用正在退出,允许 ExitRequested (code: {:?})", + code + ); + return; + } + + // 只阻止外部的无退出码请求(如用户取消系统关机) if code.is_none() { + logging!(debug, Type::System, "阻止外部退出请求"); api.prevent_exit(); } } tauri::RunEvent::Exit => { - // Avoid duplicate cleanup - if core::handle::Handle::global().is_exiting() { - return; + let handle = core::handle::Handle::global(); + + if handle.is_exiting() { + logging!( + debug, + Type::System, + "Exit事件触发,但退出流程已执行,跳过重复清理" + ); + } else { + logging!(debug, Type::System, "Exit事件触发,执行清理流程"); + handle.set_is_exiting(); + EventDrivenProxyManager::global().notify_app_stopping(); + feat::clean(); } - feat::clean(); } tauri::RunEvent::WindowEvent { label, event, .. } => { if label == "main" { diff --git a/clash-verge-rev/src-tauri/src/main.rs b/clash-verge-rev/src-tauri/src/main.rs index 6a088a2ef1..a2a91c7ed6 100755 --- a/clash-verge-rev/src-tauri/src/main.rs +++ b/clash-verge-rev/src-tauri/src/main.rs @@ -2,5 +2,14 @@ fn main() { #[cfg(feature = "tokio-trace")] console_subscriber::init(); + + // Check for --no-tray command line argument + let args: Vec = std::env::args().collect(); + if args.contains(&"--no-tray".to_string()) { + unsafe { + std::env::set_var("CLASH_VERGE_DISABLE_TRAY", "1"); + } + } + app_lib::run(); } diff --git a/clash-verge-rev/src-tauri/src/module/lightweight.rs b/clash-verge-rev/src-tauri/src/module/lightweight.rs index ba42963b5e..87ec61e6da 100644 --- a/clash-verge-rev/src-tauri/src/module/lightweight.rs +++ b/clash-verge-rev/src-tauri/src/module/lightweight.rs @@ -1,5 +1,4 @@ use crate::{ - cache::CacheProxy, config::Config, core::{handle, timer::Timer, tray::Tray}, log_err, logging, @@ -51,13 +50,13 @@ fn set_state(new: LightweightState) { LIGHTWEIGHT_STATE.store(new.as_u8(), Ordering::Release); match new { LightweightState::Normal => { - logging!(info, Type::Lightweight, true, "轻量模式已关闭"); + logging!(info, Type::Lightweight, "轻量模式已关闭"); } LightweightState::In => { - logging!(info, Type::Lightweight, true, "轻量模式已开启"); + logging!(info, Type::Lightweight, "轻量模式已开启"); } LightweightState::Exiting => { - logging!(info, Type::Lightweight, true, "正在退出轻量模式"); + logging!(info, Type::Lightweight, "正在退出轻量模式"); } } } @@ -101,7 +100,6 @@ pub async fn run_once_auto_lightweight() { logging!( info, Type::Lightweight, - true, "不满足静默启动且自动进入轻量模式的条件,跳过自动进入轻量模式" ); return; @@ -126,7 +124,6 @@ pub async fn auto_lightweight_mode_init() -> Result<()> { logging!( info, Type::Lightweight, - true, "非静默启动直接挂载自动进入轻量模式监听器!" ); set_state(LightweightState::Normal); @@ -141,13 +138,13 @@ pub async fn enable_auto_light_weight_mode() { logging!(error, Type::Lightweight, "Failed to initialize timer: {e}"); return; } - logging!(info, Type::Lightweight, true, "开启自动轻量模式"); + logging!(info, Type::Lightweight, "开启自动轻量模式"); setup_window_close_listener(); setup_webview_focus_listener(); } pub fn disable_auto_light_weight_mode() { - logging!(info, Type::Lightweight, true, "关闭自动轻量模式"); + logging!(info, Type::Lightweight, "关闭自动轻量模式"); let _ = cancel_light_weight_timer(); cancel_window_close_listener(); cancel_webview_focus_listener(); @@ -164,7 +161,7 @@ pub async fn entry_lightweight_mode() -> bool { ) .is_err() { - logging!(info, Type::Lightweight, true, "无需进入轻量模式,跳过调用"); + logging!(info, Type::Lightweight, "无需进入轻量模式,跳过调用"); return false; } @@ -176,7 +173,6 @@ pub async fn entry_lightweight_mode() -> bool { // 回到 In set_state(LightweightState::In); - CacheProxy::global().clean_default_keys(); true } @@ -195,7 +191,6 @@ pub async fn exit_lightweight_mode() -> bool { logging!( info, Type::Lightweight, - true, "轻量模式不在退出条件(可能已退出或正在退出),跳过调用" ); return false; @@ -209,7 +204,7 @@ pub async fn exit_lightweight_mode() -> bool { // 回到 Normal set_state(LightweightState::Normal); - logging!(info, Type::Lightweight, true, "轻量模式退出完成"); + logging!(info, Type::Lightweight, "轻量模式退出完成"); true } @@ -219,19 +214,14 @@ pub async fn add_light_weight_timer() { } fn setup_window_close_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = window.listen("tauri://close-requested", move |_event| { std::mem::drop(AsyncHandler::spawn(|| async { if let Err(e) = setup_light_weight_timer().await { log::warn!("Failed to setup light weight timer: {e}"); } })); - logging!( - info, - Type::Lightweight, - true, - "监听到关闭请求,开始轻量模式计时" - ); + logging!(info, Type::Lightweight, "监听到关闭请求,开始轻量模式计时"); }); WINDOW_CLOSE_HANDLER.store(handler, Ordering::Release); @@ -239,17 +229,17 @@ fn setup_window_close_listener() { } fn cancel_window_close_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = WINDOW_CLOSE_HANDLER.swap(0, Ordering::AcqRel); if handler != 0 { window.unlisten(handler); - logging!(info, Type::Lightweight, true, "取消了窗口关闭监听"); + logging!(info, Type::Lightweight, "取消了窗口关闭监听"); } } } fn setup_webview_focus_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = window.listen("tauri://focus", move |_event| { log_err!(cancel_light_weight_timer()); logging!( @@ -264,11 +254,11 @@ fn setup_webview_focus_listener() { } fn cancel_webview_focus_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = WEBVIEW_FOCUS_HANDLER.swap(0, Ordering::AcqRel); if handler != 0 { window.unlisten(handler); - logging!(info, Type::Lightweight, true, "取消了窗口焦点监听"); + logging!(info, Type::Lightweight, "取消了窗口焦点监听"); } } } @@ -294,7 +284,7 @@ async fn setup_light_weight_timer() -> Result<()> { .set_maximum_parallel_runnable_num(1) .set_frequency_once_by_minutes(once_by_minutes) .spawn_async_routine(move || async move { - logging!(info, Type::Timer, true, "计时器到期,开始进入轻量模式"); + logging!(info, Type::Timer, "计时器到期,开始进入轻量模式"); entry_lightweight_mode().await; }) .context("failed to create timer task")?; @@ -321,7 +311,6 @@ async fn setup_light_weight_timer() -> Result<()> { logging!( info, Type::Timer, - true, "计时器已设置,{} 分钟后将自动进入轻量模式", once_by_minutes ); @@ -337,7 +326,7 @@ fn cancel_light_weight_timer() -> Result<()> { delay_timer .remove_task(task.task_id) .context("failed to remove timer task")?; - logging!(info, Type::Timer, true, "计时器已取消"); + logging!(info, Type::Timer, "计时器已取消"); } Ok(()) diff --git a/clash-verge-rev/src-tauri/src/module/sysinfo.rs b/clash-verge-rev/src-tauri/src/module/sysinfo.rs index fc348f49af..fb2b25791c 100644 --- a/clash-verge-rev/src-tauri/src/module/sysinfo.rs +++ b/clash-verge-rev/src-tauri/src/module/sysinfo.rs @@ -38,17 +38,7 @@ impl PlatformSpecification { let system_kernel_version = System::kernel_version().unwrap_or("Null".into()); let system_arch = System::cpu_arch(); - let Some(handler) = handle::Handle::global().app_handle() else { - return Self { - system_name, - system_version, - system_kernel_version, - system_arch, - verge_version: "unknown".into(), - running_mode: "NotRunning".to_string(), - is_admin: false, - }; - }; + let handler = handle::Handle::app_handle(); let verge_version = handler.package_info().version.to_string(); // 使用默认值避免在同步上下文中执行异步操作 diff --git a/clash-verge-rev/src-tauri/src/process/guard.rs b/clash-verge-rev/src-tauri/src/process/guard.rs new file mode 100644 index 0000000000..e4a48909de --- /dev/null +++ b/clash-verge-rev/src-tauri/src/process/guard.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use tauri_plugin_shell::process::CommandChild; + +#[derive(Debug)] +pub struct CommandChildGuard(Option); + +impl Drop for CommandChildGuard { + fn drop(&mut self) { + if let Err(err) = self.kill() { + log::error!(target: "app", "Failed to kill child process: {}", err); + } + } +} + +impl CommandChildGuard { + pub fn new(child: CommandChild) -> Self { + Self(Some(child)) + } + + pub fn kill(&mut self) -> Result<()> { + if let Some(child) = self.0.take() { + let _ = child.kill(); + } + Ok(()) + } + + pub fn pid(&self) -> Option { + self.0.as_ref().map(|c| c.pid()) + } +} diff --git a/clash-verge-rev/src-tauri/src/process/mod.rs b/clash-verge-rev/src-tauri/src/process/mod.rs index ac83ecc0d1..77f99e78ed 100644 --- a/clash-verge-rev/src-tauri/src/process/mod.rs +++ b/clash-verge-rev/src-tauri/src/process/mod.rs @@ -1,2 +1,4 @@ mod async_handler; pub use async_handler::AsyncHandler; +mod guard; +pub use guard::CommandChildGuard; diff --git a/clash-verge-rev/src-tauri/src/utils/dirs.rs b/clash-verge-rev/src-tauri/src/utils/dirs.rs index 9b6c681ed5..8b39425e68 100644 --- a/clash-verge-rev/src-tauri/src/utils/dirs.rs +++ b/clash-verge-rev/src-tauri/src/utils/dirs.rs @@ -51,53 +51,7 @@ pub fn app_home_dir() -> Result { } // 避免在Handle未初始化时崩溃 - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "app_handle not initialized, using default path"); - // 使用可执行文件目录作为备用 - let exe_path = tauri::utils::platform::current_exe()?; - let exe_dir = exe_path - .parent() - .ok_or(anyhow::anyhow!("failed to get executable directory"))?; - - // 使用系统临时目录 + 应用ID - #[cfg(target_os = "windows")] - { - if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") { - let path = PathBuf::from(local_app_data).join(APP_ID); - return Ok(path); - } - } - - #[cfg(target_os = "macos")] - { - if let Some(home) = std::env::var_os("HOME") { - let path = PathBuf::from(home) - .join("Library") - .join("Application Support") - .join(APP_ID); - return Ok(path); - } - } - - #[cfg(target_os = "linux")] - { - if let Some(home) = std::env::var_os("HOME") { - let path = PathBuf::from(home) - .join(".local") - .join("share") - .join(APP_ID); - return Ok(path); - } - } - - // 如果无法获取系统目录,则回退到可执行文件目录 - let fallback_dir = PathBuf::from(exe_dir).join(".config").join(APP_ID); - log::warn!(target: "app", "Using fallback data directory: {fallback_dir:?}"); - return Ok(fallback_dir); - } - }; + let app_handle = handle::Handle::app_handle(); match app_handle.path().data_dir() { Ok(dir) => Ok(dir.join(APP_ID)), @@ -111,18 +65,7 @@ pub fn app_home_dir() -> Result { /// get the resources dir pub fn app_resources_dir() -> Result { // 避免在Handle未初始化时崩溃 - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "app_handle not initialized in app_resources_dir, using fallback"); - // 使用可执行文件目录作为备用 - let exe_dir = tauri::utils::platform::current_exe()? - .parent() - .ok_or(anyhow::anyhow!("failed to get executable directory"))? - .to_path_buf(); - return Ok(exe_dir.join("resources")); - } - }; + let app_handle = handle::Handle::app_handle(); match app_handle.path().resource_dir() { Ok(dir) => Ok(dir.join("resources")), @@ -201,18 +144,18 @@ pub fn service_path() -> Result { Ok(res_dir.join("clash-verge-service.exe")) } -pub fn service_log_file() -> Result { - use chrono::Local; - - let log_dir = app_logs_dir()?.join("service"); - - let local_time = Local::now().format("%Y-%m-%d-%H%M").to_string(); - let log_file = format!("{local_time}.log"); - let log_file = log_dir.join(log_file); - +pub fn sidecar_log_dir() -> Result { + let log_dir = app_logs_dir()?.join("sidecar"); let _ = std::fs::create_dir_all(&log_dir); - Ok(log_file) + Ok(log_dir) +} + +pub fn service_log_dir() -> Result { + let log_dir = app_logs_dir()?.join("service"); + let _ = std::fs::create_dir_all(&log_dir); + + Ok(log_dir) } pub fn path_to_str(path: &PathBuf) -> Result<&str> { @@ -249,7 +192,7 @@ pub fn get_encryption_key() -> Result> { #[cfg(unix)] pub fn ensure_mihomo_safe_dir() -> Option { - ["/var/tmp", "/tmp"] + ["/tmp"] .iter() .map(PathBuf::from) .find(|path| path.exists()) diff --git a/clash-verge-rev/src-tauri/src/utils/format.rs b/clash-verge-rev/src-tauri/src/utils/format.rs index 270206c3bc..86437a4968 100644 --- a/clash-verge-rev/src-tauri/src/utils/format.rs +++ b/clash-verge-rev/src-tauri/src/utils/format.rs @@ -1,4 +1,5 @@ /// Format bytes into human readable string (B, KB, MB, GB) +#[allow(unused)] pub fn fmt_bytes(bytes: u64) -> String { const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; let (mut val, mut unit) = (bytes as f64, 0); diff --git a/clash-verge-rev/src-tauri/src/utils/help.rs b/clash-verge-rev/src-tauri/src/utils/help.rs index 4991bdab3a..e06c7ed7cc 100644 --- a/clash-verge-rev/src-tauri/src/utils/help.rs +++ b/clash-verge-rev/src-tauri/src/utils/help.rs @@ -42,7 +42,7 @@ pub async fn read_mapping(path: &PathBuf) -> Result { } Err(err) => { let error_msg = format!("YAML syntax error in {}: {}", path.display(), err); - logging!(error, Type::Config, true, "{}", error_msg); + logging!(error, Type::Config, "{}", error_msg); crate::core::handle::Handle::notice_message( "config_validate::yaml_syntax_error", diff --git a/clash-verge-rev/src-tauri/src/utils/init.rs b/clash-verge-rev/src-tauri/src/utils/init.rs index 325fbbb7b3..1758dfb06f 100644 --- a/clash-verge-rev/src-tauri/src/utils/init.rs +++ b/clash-verge-rev/src-tauri/src/utils/init.rs @@ -1,19 +1,23 @@ -cfg_if::cfg_if! { - if #[cfg(not(feature = "tauri-dev"))] { - use crate::utils::logging::{console_colored_format, file_format, NoExternModule}; - use flexi_logger::{Cleanup, Criterion, Duplicate, FileSpec, LogSpecification, Logger}; - } -} - +#[cfg(not(feature = "tauri-dev"))] +use crate::utils::logging::NoModuleFilter; use crate::{ config::*, core::handle, logging, process::AsyncHandler, - utils::{dirs, help, logging::Type}, + utils::{ + dirs::{self, service_log_dir, sidecar_log_dir}, + help, + logging::Type, + }, }; use anyhow::Result; use chrono::{Local, TimeZone}; +use clash_verge_service_ipc::WriterConfig; +use flexi_logger::writers::FileLogWriter; +use flexi_logger::{Cleanup, Criterion, FileSpec}; +#[cfg(not(feature = "tauri-dev"))] +use flexi_logger::{Duplicate, LogSpecBuilder, Logger}; use std::{path::PathBuf, str::FromStr}; use tauri_plugin_shell::ShellExt; use tokio::fs; @@ -34,11 +38,13 @@ pub async fn init_logger() -> Result<()> { }; let log_dir = dirs::app_logs_dir()?; - let logger = Logger::with(LogSpecification::from(log_level)) + let spec = LogSpecBuilder::new().default(log_level).build(); + + let logger = Logger::with(spec) .log_to_file(FileSpec::default().directory(log_dir).basename("")) .duplicate_to_stdout(Duplicate::Debug) - .format(console_colored_format) - .format_for_files(file_format) + .format(clash_verge_logger::console_format) + .format_for_files(clash_verge_logger::file_format_with_level) .rotate( Criterion::Size(log_max_size * 1024), flexi_logger::Naming::TimestampsCustomFormat { @@ -47,7 +53,7 @@ pub async fn init_logger() -> Result<()> { }, Cleanup::KeepLogFiles(log_max_count), ) - .filter(Box::new(NoExternModule)); + .filter(Box::new(NoModuleFilter(&["wry", "tauri"]))); let _handle = logger.start()?; @@ -59,6 +65,52 @@ pub async fn init_logger() -> Result<()> { Ok(()) } +pub async fn sidecar_writer() -> Result { + let (log_max_size, log_max_count) = { + let verge_guard = Config::verge().await; + let verge = verge_guard.latest_ref(); + ( + verge.app_log_max_size.unwrap_or(128), + verge.app_log_max_count.unwrap_or(8), + ) + }; + let sidecar_log_dir = sidecar_log_dir()?; + Ok(FileLogWriter::builder( + FileSpec::default() + .directory(sidecar_log_dir) + .basename("sidecar") + .suppress_timestamp(), + ) + .format(clash_verge_logger::file_format_without_level) + .rotate( + Criterion::Size(log_max_size * 1024), + flexi_logger::Naming::TimestampsCustomFormat { + current_infix: Some("latest"), + format: "%Y-%m-%d_%H-%M-%S", + }, + Cleanup::KeepLogFiles(log_max_count), + ) + .try_build()?) +} + +pub async fn service_writer_config() -> Result { + let (log_max_size, log_max_count) = { + let verge_guard = Config::verge().await; + let verge = verge_guard.latest_ref(); + ( + verge.app_log_max_size.unwrap_or(128), + verge.app_log_max_count.unwrap_or(8), + ) + }; + let service_log_dir = dirs::path_to_str(&service_log_dir()?)?.to_string(); + + Ok(WriterConfig { + directory: service_log_dir, + max_log_size: log_max_size * 1024, + max_log_files: log_max_count, + }) +} + // TODO flexi_logger 提供了最大保留天数,或许我们应该用内置删除log文件 /// 删除log文件 pub async fn delete_log() -> Result<()> { @@ -82,13 +134,7 @@ pub async fn delete_log() -> Result<()> { _ => return Ok(()), }; - logging!( - info, - Type::Setup, - true, - "try to delete log files, day: {}", - day - ); + logging!(info, Type::Setup, "try to delete log files, day: {}", day); // %Y-%m-%d to NaiveDateTime let parse_time_str = |s: &str| { @@ -123,7 +169,7 @@ pub async fn delete_log() -> Result<()> { if duration.num_days() > day { let file_path = file.path(); let _ = fs::remove_file(file_path).await; - logging!(info, Type::Setup, true, "delete log file: {}", file_name); + logging!(info, Type::Setup, "delete log file: {}", file_name); } } Ok(()) @@ -250,7 +296,7 @@ async fn init_dns_config() -> Result<()> { let dns_path = app_dir.join("dns_config.yaml"); if !dns_path.exists() { - logging!(info, Type::Setup, true, "Creating default DNS config file"); + logging!(info, Type::Setup, "Creating default DNS config file"); help::save_yaml( &dns_path, &default_dns_config, @@ -275,14 +321,7 @@ async fn ensure_directories() -> Result<()> { fs::create_dir_all(&dir).await.map_err(|e| { anyhow::anyhow!("Failed to create {} directory {:?}: {}", name, dir, e) })?; - logging!( - info, - Type::Setup, - true, - "Created {} directory: {:?}", - name, - dir - ); + logging!(info, Type::Setup, "Created {} directory: {:?}", name, dir); } } @@ -298,13 +337,7 @@ async fn initialize_config_files() -> Result<()> { help::save_yaml(&path, &template, Some("# Clash Verge")) .await .map_err(|e| anyhow::anyhow!("Failed to create clash config: {}", e))?; - logging!( - info, - Type::Setup, - true, - "Created clash config at {:?}", - path - ); + logging!(info, Type::Setup, "Created clash config at {:?}", path); } if let Ok(path) = dirs::verge_path() @@ -314,13 +347,7 @@ async fn initialize_config_files() -> Result<()> { help::save_yaml(&path, &template, Some("# Clash Verge")) .await .map_err(|e| anyhow::anyhow!("Failed to create verge config: {}", e))?; - logging!( - info, - Type::Setup, - true, - "Created verge config at {:?}", - path - ); + logging!(info, Type::Setup, "Created verge config at {:?}", path); } if let Ok(path) = dirs::profiles_path() @@ -330,13 +357,7 @@ async fn initialize_config_files() -> Result<()> { help::save_yaml(&path, &template, Some("# Clash Verge")) .await .map_err(|e| anyhow::anyhow!("Failed to create profiles config: {}", e))?; - logging!( - info, - Type::Setup, - true, - "Created profiles config at {:?}", - path - ); + logging!(info, Type::Setup, "Created profiles config at {:?}", path); } // 验证并修正verge配置 @@ -364,19 +385,13 @@ pub async fn init_config() -> Result<()> { AsyncHandler::spawn(|| async { if let Err(e) = delete_log().await { - logging!(warn, Type::Setup, true, "Failed to clean old logs: {}", e); + logging!(warn, Type::Setup, "Failed to clean old logs: {}", e); } - logging!(info, Type::Setup, true, "后台日志清理任务完成"); + logging!(info, Type::Setup, "后台日志清理任务完成"); }); if let Err(e) = init_dns_config().await { - logging!( - warn, - Type::Setup, - true, - "DNS config initialization failed: {}", - e - ); + logging!(warn, Type::Setup, "DNS config initialization failed: {}", e); } Ok(()) @@ -406,13 +421,12 @@ pub async fn init_resources() -> Result<()> { let handle_copy = |src: PathBuf, dest: PathBuf, file: String| async move { match fs::copy(&src, &dest).await { Ok(_) => { - logging!(debug, Type::Setup, true, "resources copied '{}'", file); + logging!(debug, Type::Setup, "resources copied '{}'", file); } Err(err) => { logging!( error, Type::Setup, - true, "failed to copy resources '{}' to '{:?}', {}", file, dest, @@ -437,13 +451,7 @@ pub async fn init_resources() -> Result<()> { } } _ => { - logging!( - debug, - Type::Setup, - true, - "failed to get modified '{}'", - file - ); + logging!(debug, Type::Setup, "failed to get modified '{}'", file); handle_copy(src_path.clone(), dest_path.clone(), file.to_string()).await; } }; @@ -494,15 +502,7 @@ pub fn init_scheme() -> Result<()> { } pub async fn startup_script() -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - return Err(anyhow::anyhow!( - "app_handle not available for startup script execution" - )); - } - }; - + let app_handle = handle::Handle::app_handle(); let script_path = { let verge = Config::verge().await; let verge = verge.latest_ref(); diff --git a/clash-verge-rev/src-tauri/src/utils/logging.rs b/clash-verge-rev/src-tauri/src/utils/logging.rs index 1390ca0e77..dd8d8cf7b5 100644 --- a/clash-verge-rev/src-tauri/src/utils/logging.rs +++ b/clash-verge-rev/src-tauri/src/utils/logging.rs @@ -1,15 +1,12 @@ -cfg_if::cfg_if! { - if #[cfg(feature = "tauri-dev")] { - use std::fmt; - } else { - #[cfg(feature = "verge-dev")] - use nu_ansi_term::Color; - use std::{fmt, io::Write, thread}; - use flexi_logger::DeferredNow; - use log::{LevelFilter, Record}; - use flexi_logger::filter::LogLineFilter; - } -} +use flexi_logger::writers::FileLogWriter; +#[cfg(not(feature = "tauri-dev"))] +use flexi_logger::{DeferredNow, filter::LogLineFilter}; +#[cfg(not(feature = "tauri-dev"))] +use log::Record; +use std::{fmt, sync::Arc}; +use tokio::sync::Mutex; + +pub type SharedWriter = Arc>; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Type { @@ -28,7 +25,6 @@ pub enum Type { Lightweight, Network, ProxyMode, - Ipc, // Cache, ClashVergeRev, } @@ -51,7 +47,6 @@ impl fmt::Display for Type { Type::Lightweight => write!(f, "[Lightweight]"), Type::Network => write!(f, "[Network]"), Type::ProxyMode => write!(f, "[ProxMode]"), - Type::Ipc => write!(f, "[IPC]"), // Type::Cache => write!(f, "[Cache]"), Type::ClashVergeRev => write!(f, "[ClashVergeRev]"), } @@ -118,18 +113,6 @@ macro_rules! wrap_err { #[macro_export] macro_rules! logging { - // 带 println 的版本(支持格式化参数) - ($level:ident, $type:expr, true, $($arg:tt)*) => { - // We dont need println here anymore - // println!("{} {}", $type, format_args!($($arg)*)); - log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*)); - }; - - // 带 println 的版本(使用 false 明确不打印) - ($level:ident, $type:expr, false, $($arg:tt)*) => { - log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*)); - }; - // 不带 print 参数的版本(默认不打印) ($level:ident, $type:expr, $($arg:tt)*) => { log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*)); @@ -138,110 +121,50 @@ macro_rules! logging { #[macro_export] macro_rules! logging_error { - // 1. 处理 Result,带打印控制 - ($type:expr, $print:expr, $expr:expr) => { - match $expr { - Ok(_) => {}, - Err(err) => { - if $print { - println!("[{}] Error: {}", $type, err); - } - log::error!(target: "app", "[{}] {}", $type, err); - } - } - }; - - // 2. 处理 Result,默认不打印 + // Handle Result ($type:expr, $expr:expr) => { if let Err(err) = $expr { log::error!(target: "app", "[{}] {}", $type, err); } }; - // 3. 处理格式化字符串,带打印控制 - ($type:expr, $print:expr, $fmt:literal $(, $arg:expr)*) => { - if $print { - println!("[{}] {}", $type, format_args!($fmt $(, $arg)*)); - } - log::error!(target: "app", "[{}] {}", $type, format_args!($fmt $(, $arg)*)); - }; - - // 4. 处理格式化字符串,不带 bool 时,默认 `false` + // Handle formatted message: always print to stdout and log as error ($type:expr, $fmt:literal $(, $arg:expr)*) => { - logging_error!($type, false, $fmt $(, $arg)*); + log::error!(target: "app", "[{}] {}", $type, format_args!($fmt $(, $arg)*)); }; } #[cfg(not(feature = "tauri-dev"))] -static IGNORE_MODULES: &[&str] = &["tauri", "wry"]; +pub struct NoModuleFilter<'a>(pub &'a [&'a str]); + #[cfg(not(feature = "tauri-dev"))] -pub struct NoExternModule; +impl<'a> NoModuleFilter<'a> { + #[inline] + pub fn filter(&self, record: &Record) -> bool { + if let Some(module) = record.module_path() { + for blocked in self.0 { + if module.len() >= blocked.len() + && module.as_bytes()[..blocked.len()] == blocked.as_bytes()[..] + { + return false; + } + } + } + true + } +} + #[cfg(not(feature = "tauri-dev"))] -impl LogLineFilter for NoExternModule { +impl<'a> LogLineFilter for NoModuleFilter<'a> { fn write( &self, now: &mut DeferredNow, record: &Record, - log_line_writer: &dyn flexi_logger::filter::LogLineWriter, + writer: &dyn flexi_logger::filter::LogLineWriter, ) -> std::io::Result<()> { - let module_path = record.module_path().unwrap_or_default(); - if IGNORE_MODULES.iter().any(|m| module_path.starts_with(m)) { - Ok(()) - } else { - log_line_writer.write(now, record) + if !self.filter(record) { + return Ok(()); } + writer.write(now, record) } } - -#[cfg(not(feature = "tauri-dev"))] -pub fn get_log_level(log_level: &LevelFilter) -> String { - #[cfg(feature = "verge-dev")] - match log_level { - LevelFilter::Off => Color::Fixed(8).paint("OFF").to_string(), - LevelFilter::Error => Color::Red.paint("ERROR").to_string(), - LevelFilter::Warn => Color::Yellow.paint("WARN ").to_string(), - LevelFilter::Info => Color::Green.paint("INFO ").to_string(), - LevelFilter::Debug => Color::Blue.paint("DEBUG").to_string(), - LevelFilter::Trace => Color::Purple.paint("TRACE").to_string(), - } - #[cfg(not(feature = "verge-dev"))] - log_level.to_string() -} - -#[cfg(not(feature = "tauri-dev"))] -pub fn console_colored_format( - w: &mut dyn Write, - now: &mut DeferredNow, - record: &log::Record, -) -> std::io::Result<()> { - let current_thread = thread::current(); - let thread_name = current_thread.name().unwrap_or("unnamed"); - - let level = get_log_level(&record.level().to_level_filter()); - let line = record.line().unwrap_or(0); - write!( - w, - "[{}] {} [{}:{}] T[{}] {}", - now.format("%H:%M:%S%.3f"), - level, - record.module_path().unwrap_or(""), - line, - thread_name, - record.args(), - ) -} - -#[cfg(not(feature = "tauri-dev"))] -pub fn file_format( - w: &mut dyn Write, - now: &mut DeferredNow, - record: &Record, -) -> std::io::Result<()> { - write!( - w, - "[{}] {} {}", - now.format("%Y-%m-%d %H:%M:%S%.3f"), - record.level(), - record.args(), - ) -} diff --git a/clash-verge-rev/src-tauri/src/utils/resolve/dns.rs b/clash-verge-rev/src-tauri/src/utils/resolve/dns.rs index e3618178fb..4b710bdc79 100644 --- a/clash-verge-rev/src-tauri/src/utils/resolve/dns.rs +++ b/clash-verge-rev/src-tauri/src/utils/resolve/dns.rs @@ -2,13 +2,7 @@ pub async fn set_public_dns(dns_server: String) { use crate::{core::handle, utils::dirs}; use tauri_plugin_shell::ShellExt; - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::error!(target: "app", "app_handle not available for DNS configuration"); - return; - } - }; + let app_handle = handle::Handle::app_handle(); log::info!(target: "app", "try to set system dns"); let resource_dir = match dirs::app_resources_dir() { @@ -50,13 +44,7 @@ pub async fn set_public_dns(dns_server: String) { pub async fn restore_public_dns() { use crate::{core::handle, utils::dirs}; use tauri_plugin_shell::ShellExt; - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::error!(target: "app", "app_handle not available for DNS restoration"); - return; - } - }; + let app_handle = handle::Handle::app_handle(); log::info!(target: "app", "try to unset system dns"); let resource_dir = match dirs::app_resources_dir() { Ok(dir) => dir, diff --git a/clash-verge-rev/src-tauri/src/utils/resolve/mod.rs b/clash-verge-rev/src-tauri/src/utils/resolve/mod.rs index 1bee81fda1..7883494767 100644 --- a/clash-verge-rev/src-tauri/src/utils/resolve/mod.rs +++ b/clash-verge-rev/src-tauri/src/utils/resolve/mod.rs @@ -1,10 +1,13 @@ use anyhow::Result; -use tauri::AppHandle; use crate::{ config::Config, core::{ - CoreManager, Timer, handle, hotkey::Hotkey, service::SERVICE_MANAGER, sysopt, tray::Tray, + CoreManager, Timer, handle, + hotkey::Hotkey, + service::{SERVICE_MANAGER, ServiceManager, is_service_ipc_path_exists}, + sysopt, + tray::Tray, }, logging, logging_error, module::lightweight::{auto_lightweight_mode_init, run_once_auto_lightweight}, @@ -18,8 +21,8 @@ pub mod ui; pub mod window; pub mod window_script; -pub fn resolve_setup_handle(app_handle: AppHandle) { - init_handle(app_handle); +pub fn resolve_setup_handle() { + init_handle(); } pub fn resolve_setup_sync() { @@ -34,7 +37,6 @@ pub fn resolve_setup_async() { logging!( info, Type::Setup, - true, "开始执行异步设置任务... 线程ID: {:?}", std::thread::current().id() ); @@ -45,11 +47,10 @@ pub fn resolve_setup_async() { logging!( info, Type::ClashVergeRev, - true, "Version: {}", env!("CARGO_PKG_VERSION") ); - init_service_manager().await; + futures::join!(init_service_manager()); futures::join!( init_work_config(), @@ -62,7 +63,12 @@ pub fn resolve_setup_async() { init_once_auto_lightweight().await; init_auto_lightweight_mode().await; + // 确保配置完全初始化后再启动核心管理器 init_verge_config().await; + + // 添加配置验证,确保运行时配置已正确生成 + Config::verify_config_initialization().await; + init_core_manager().await; init_system_proxy().await; @@ -78,187 +84,154 @@ pub fn resolve_setup_async() { }); let elapsed = start_time.elapsed(); - logging!( - info, - Type::Setup, - true, - "异步设置任务完成,耗时: {:?}", - elapsed - ); + logging!(info, Type::Setup, "异步设置任务完成,耗时: {:?}", elapsed); if elapsed.as_secs() > 10 { - logging!( - warn, - Type::Setup, - true, - "异步设置任务耗时较长({:?})", - elapsed - ); + logging!(warn, Type::Setup, "异步设置任务耗时较长({:?})", elapsed); } } // 其它辅助函数不变 -pub async fn resolve_reset_async() { - logging!(info, Type::Tray, true, "Resetting system proxy"); - logging_error!( - Type::System, - true, - sysopt::Sysopt::global().reset_sysproxy().await - ); +pub async fn resolve_reset_async() -> Result<(), anyhow::Error> { + logging!(info, Type::Tray, "Resetting system proxy"); + sysopt::Sysopt::global().reset_sysproxy().await?; - logging!(info, Type::Core, true, "Stopping core service"); - logging_error!(Type::Core, true, CoreManager::global().stop_core().await); + logging!(info, Type::Core, "Stopping core service"); + CoreManager::global().stop_core().await?; #[cfg(target_os = "macos")] { use dns::restore_public_dns; - logging!(info, Type::System, true, "Restoring system DNS settings"); + logging!(info, Type::System, "Restoring system DNS settings"); restore_public_dns().await; } + + Ok(()) } -pub fn init_handle(app_handle: AppHandle) { - logging!(info, Type::Setup, true, "Initializing app handle..."); - handle::Handle::global().init(app_handle); +pub fn init_handle() { + logging!(info, Type::Setup, "Initializing app handle..."); + handle::Handle::global().init(); } pub(super) fn init_scheme() { - logging!(info, Type::Setup, true, "Initializing custom URL scheme"); - logging_error!(Type::Setup, true, init::init_scheme()); + logging!(info, Type::Setup, "Initializing custom URL scheme"); + logging_error!(Type::Setup, init::init_scheme()); } #[cfg(not(feature = "tauri-dev"))] pub(super) async fn resolve_setup_logger() { - logging!(info, Type::Setup, true, "Initializing global logger..."); - logging_error!(Type::Setup, true, init::init_logger().await); + logging!(info, Type::Setup, "Initializing global logger..."); + logging_error!(Type::Setup, init::init_logger().await); } pub async fn resolve_scheme(param: String) -> Result<()> { - logging!( - info, - Type::Setup, - true, - "Resolving scheme for param: {}", - param - ); - logging_error!(Type::Setup, true, scheme::resolve_scheme(param).await); + logging!(info, Type::Setup, "Resolving scheme for param: {}", param); + logging_error!(Type::Setup, scheme::resolve_scheme(param).await); Ok(()) } pub(super) fn init_embed_server() { - logging!(info, Type::Setup, true, "Initializing embedded server..."); + logging!(info, Type::Setup, "Initializing embedded server..."); server::embed_server(); } pub(super) async fn init_resources() { - logging!(info, Type::Setup, true, "Initializing resources..."); - logging_error!(Type::Setup, true, init::init_resources().await); + logging!(info, Type::Setup, "Initializing resources..."); + logging_error!(Type::Setup, init::init_resources().await); } pub(super) async fn init_startup_script() { - logging!(info, Type::Setup, true, "Initializing startup script"); - logging_error!(Type::Setup, true, init::startup_script().await); + logging!(info, Type::Setup, "Initializing startup script"); + logging_error!(Type::Setup, init::startup_script().await); } pub(super) async fn init_timer() { - logging!(info, Type::Setup, true, "Initializing timer..."); - logging_error!(Type::Setup, true, Timer::global().init().await); + logging!(info, Type::Setup, "Initializing timer..."); + logging_error!(Type::Setup, Timer::global().init().await); } pub(super) async fn init_hotkey() { - logging!(info, Type::Setup, true, "Initializing hotkey..."); - logging_error!(Type::Setup, true, Hotkey::global().init().await); + logging!(info, Type::Setup, "Initializing hotkey..."); + logging_error!(Type::Setup, Hotkey::global().init().await); } pub(super) async fn init_once_auto_lightweight() { logging!( info, Type::Lightweight, - true, "Running auto lightweight mode check..." ); run_once_auto_lightweight().await; } pub(super) async fn init_auto_lightweight_mode() { - logging!( - info, - Type::Setup, - true, - "Initializing auto lightweight mode..." - ); - logging_error!(Type::Setup, true, auto_lightweight_mode_init().await); + logging!(info, Type::Setup, "Initializing auto lightweight mode..."); + logging_error!(Type::Setup, auto_lightweight_mode_init().await); } pub async fn init_work_config() { - logging!( - info, - Type::Setup, - true, - "Initializing work configuration..." - ); - logging_error!(Type::Setup, true, init::init_config().await); + logging!(info, Type::Setup, "Initializing work configuration..."); + logging_error!(Type::Setup, init::init_config().await); } pub(super) async fn init_tray() { - logging!(info, Type::Setup, true, "Initializing system tray..."); - logging_error!(Type::Setup, true, Tray::global().init().await); + // Check if tray should be disabled via environment variable + if std::env::var("CLASH_VERGE_DISABLE_TRAY").unwrap_or_default() == "1" { + logging!(info, Type::Setup, "System tray disabled via --no-tray flag"); + return; + } + + logging!(info, Type::Setup, "Initializing system tray..."); + logging_error!(Type::Setup, Tray::global().init().await); } pub(super) async fn init_verge_config() { - logging!( - info, - Type::Setup, - true, - "Initializing verge configuration..." - ); - logging_error!(Type::Setup, true, Config::init_config().await); + logging!(info, Type::Setup, "Initializing verge configuration..."); + logging_error!(Type::Setup, Config::init_config().await); } pub(super) async fn init_service_manager() { - logging!(info, Type::Setup, true, "Initializing service manager..."); - logging_error!( - Type::Setup, - true, - SERVICE_MANAGER.lock().await.refresh().await - ); + logging!(info, Type::Setup, "Initializing service manager..."); + clash_verge_service_ipc::set_config(ServiceManager::config()).await; + if !is_service_ipc_path_exists() { + logging!( + warn, + Type::Setup, + "Service IPC path does not exist, service may be unavailable" + ); + return; + } + if SERVICE_MANAGER.lock().await.init().await.is_ok() { + logging_error!(Type::Setup, SERVICE_MANAGER.lock().await.refresh().await); + } } pub(super) async fn init_core_manager() { - logging!(info, Type::Setup, true, "Initializing core manager..."); - logging_error!(Type::Setup, true, CoreManager::global().init().await); + logging!(info, Type::Setup, "Initializing core manager..."); + logging_error!(Type::Setup, CoreManager::global().init().await); } pub(super) async fn init_system_proxy() { - logging!(info, Type::Setup, true, "Initializing system proxy..."); + logging!(info, Type::Setup, "Initializing system proxy..."); logging_error!( Type::Setup, - true, sysopt::Sysopt::global().update_sysproxy().await ); } pub(super) fn init_system_proxy_guard() { - logging!( - info, - Type::Setup, - true, - "Initializing system proxy guard..." - ); - logging_error!( - Type::Setup, - true, - sysopt::Sysopt::global().init_guard_sysproxy() - ); + logging!(info, Type::Setup, "Initializing system proxy guard..."); + logging_error!(Type::Setup, sysopt::Sysopt::global().init_guard_sysproxy()); } pub(super) async fn refresh_tray_menu() { - logging!(info, Type::Setup, true, "Refreshing tray menu..."); - logging_error!(Type::Setup, true, Tray::global().update_part().await); + logging!(info, Type::Setup, "Refreshing tray menu..."); + logging_error!(Type::Setup, Tray::global().update_part().await); } pub(super) async fn init_window() { - logging!(info, Type::Setup, true, "Initializing main window..."); + logging!(info, Type::Setup, "Initializing main window..."); let is_silent_start = { Config::verge().await.latest_ref().enable_silent_start }.unwrap_or(false); #[cfg(target_os = "macos")] diff --git a/clash-verge-rev/src-tauri/src/utils/resolve/scheme.rs b/clash-verge-rev/src-tauri/src/utils/resolve/scheme.rs index 99e6588ea4..c34c9503fe 100644 --- a/clash-verge-rev/src-tauri/src/utils/resolve/scheme.rs +++ b/clash-verge-rev/src-tauri/src/utils/resolve/scheme.rs @@ -49,7 +49,7 @@ pub(super) async fn resolve_scheme(param: String) -> Result<()> { let uid = match item.uid.clone() { Some(uid) => uid, None => { - logging!(error, Type::Config, true, "Profile item missing UID"); + logging!(error, Type::Config, "Profile item missing UID"); handle::Handle::notice_message( "import_sub_url::error", "Profile item missing UID".to_string(), diff --git a/clash-verge-rev/src-tauri/src/utils/resolve/ui.rs b/clash-verge-rev/src-tauri/src/utils/resolve/ui.rs index 009e55ab5c..4ebe30ad10 100644 --- a/clash-verge-rev/src-tauri/src/utils/resolve/ui.rs +++ b/clash-verge-rev/src-tauri/src/utils/resolve/ui.rs @@ -66,7 +66,7 @@ pub fn update_ui_ready_stage(stage: UiReadyStage) { // 标记UI已准备就绪 pub fn mark_ui_ready() { get_ui_ready().store(true, Ordering::Release); - logging!(info, Type::Window, true, "UI已标记为完全就绪"); + logging!(info, Type::Window, "UI已标记为完全就绪"); // 通知所有等待的任务 get_ui_ready_notify().notify_waiters(); diff --git a/clash-verge-rev/src-tauri/src/utils/resolve/window.rs b/clash-verge-rev/src-tauri/src/utils/resolve/window.rs index e99e3307f1..753342275f 100644 --- a/clash-verge-rev/src-tauri/src/utils/resolve/window.rs +++ b/clash-verge-rev/src-tauri/src/utils/resolve/window.rs @@ -2,7 +2,7 @@ use tauri::WebviewWindow; use crate::{ core::handle, - logging, logging_error, + logging_error, utils::{ logging::Type, resolve::window_script::{INITIAL_LOADING_OVERLAY, WINDOW_INITIAL_SCRIPT}, @@ -18,24 +18,17 @@ const MINIMAL_HEIGHT: f64 = 520.0; /// 构建新的 WebView 窗口 pub fn build_new_window() -> Result { - let app_handle = handle::Handle::global().app_handle().ok_or_else(|| { - logging!( - error, - Type::Window, - true, - "无法获取app_handle,窗口创建失败" - ); - "无法获取app_handle".to_string() - })?; + let app_handle = handle::Handle::app_handle(); match tauri::WebviewWindowBuilder::new( - &app_handle, + app_handle, "main", /* the unique window label */ tauri::WebviewUrl::App("index.html".into()), ) .title("Clash Verge") .center() - .decorations(true) + // Using WindowManager::prefer_system_titlebar to control if show system built-in titlebar + // .decorations(true) .fullscreen(false) .inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT) .min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT) @@ -44,7 +37,7 @@ pub fn build_new_window() -> Result { .build() { Ok(window) => { - logging_error!(Type::Window, true, window.eval(INITIAL_LOADING_OVERLAY)); + logging_error!(Type::Window, window.eval(INITIAL_LOADING_OVERLAY)); Ok(window) } Err(e) => Err(e.to_string()), diff --git a/clash-verge-rev/src-tauri/src/utils/server.rs b/clash-verge-rev/src-tauri/src/utils/server.rs index e16986e41d..97be9e9be2 100644 --- a/clash-verge-rev/src-tauri/src/utils/server.rs +++ b/clash-verge-rev/src-tauri/src/utils/server.rs @@ -6,7 +6,10 @@ use crate::{ utils::logging::Type, }; use anyhow::{Result, bail}; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; use port_scanner::local_port_available; +use tokio::sync::oneshot; use warp::Filter; #[derive(serde::Deserialize, Debug)] @@ -14,6 +17,9 @@ struct QueryParam { param: String, } +// 关闭 embedded server 的信号发送端 +static SHUTDOWN_SENDER: OnceCell>>> = OnceCell::new(); + /// check whether there is already exists pub async fn check_singleton() -> Result<()> { let port = IVerge::get_singleton_port(); @@ -42,6 +48,11 @@ pub async fn check_singleton() -> Result<()> { /// The embed server only be used to implement singleton process /// maybe it can be used as pac server later pub fn embed_server() { + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + #[allow(clippy::expect_used)] + SHUTDOWN_SENDER + .set(Mutex::new(Some(shutdown_tx))) + .expect("failed to set shutdown signal for embedded server"); let port = IVerge::get_singleton_port(); AsyncHandler::spawn(move || async move { @@ -84,12 +95,28 @@ pub fn embed_server() { // Spawn async work in a fire-and-forget manner let param = query.param.clone(); tokio::task::spawn_local(async move { - logging_error!(Type::Setup, true, resolve::resolve_scheme(param).await); + logging_error!(Type::Setup, resolve::resolve_scheme(param).await); }); warp::reply::with_status("ok".to_string(), warp::http::StatusCode::OK) }); let commands = visible.or(scheme).or(pac); - warp::serve(commands).run(([127, 0, 0, 1], port)).await; + warp::serve(commands) + .bind(([127, 0, 0, 1], port)) + .await + .graceful(async { + shutdown_rx.await.ok(); + }) + .run() + .await; }); } + +pub fn shutdown_embedded_server() { + log::info!("shutting down embedded server"); + if let Some(sender) = SHUTDOWN_SENDER.get() + && let Some(sender) = sender.lock().take() + { + sender.send(()).ok(); + } +} diff --git a/clash-verge-rev/src-tauri/src/utils/singleton.rs b/clash-verge-rev/src-tauri/src/utils/singleton.rs index 2a18edc98b..1c79a8ba5a 100644 --- a/clash-verge-rev/src-tauri/src/utils/singleton.rs +++ b/clash-verge-rev/src-tauri/src/utils/singleton.rs @@ -50,7 +50,6 @@ macro_rules! singleton_with_logging { $crate::logging!( info, $crate::utils::logging::Type::Setup, - true, concat!($struct_name_str, " initialized") ); instance @@ -88,7 +87,6 @@ macro_rules! singleton_lazy_with_logging { $crate::logging!( info, $crate::utils::logging::Type::Setup, - true, concat!($struct_name_str, " initialized") ); instance diff --git a/clash-verge-rev/src-tauri/src/utils/window_manager.rs b/clash-verge-rev/src-tauri/src/utils/window_manager.rs index d982a8e254..fa86f7e0b3 100644 --- a/clash-verge-rev/src-tauri/src/utils/window_manager.rs +++ b/clash-verge-rev/src-tauri/src/utils/window_manager.rs @@ -67,7 +67,7 @@ fn should_handle_window_operation() -> bool { let now = Instant::now(); let elapsed = now.duration_since(*last_operation); - log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)", + log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)", elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS); if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) { @@ -76,7 +76,7 @@ fn should_handle_window_operation() -> bool { log::info!(target: "app", "[防抖] 窗口操作被允许执行"); true } else { - log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms", + log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms", elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS); false } @@ -117,9 +117,8 @@ impl WindowManager { /// 获取主窗口实例 pub fn get_main_window() -> Option> { - handle::Handle::global() - .app_handle() - .and_then(|app| app.get_webview_window("main")) + let app_handle = handle::Handle::app_handle(); + app_handle.get_webview_window("main") } /// 智能显示主窗口 @@ -132,43 +131,32 @@ impl WindowManager { finish_window_operation(); }); - logging!(info, Type::Window, true, "开始智能显示主窗口"); - logging!( - debug, - Type::Window, - true, - "{}", - Self::get_window_status_info() - ); + logging!(info, Type::Window, "开始智能显示主窗口"); + logging!(debug, Type::Window, "{}", Self::get_window_status_info()); let current_state = Self::get_main_window_state(); match current_state { WindowState::NotExist => { - logging!(info, Type::Window, true, "窗口不存在,创建新窗口"); + logging!(info, Type::Window, "窗口不存在,创建新窗口"); if Self::create_window(true).await { - logging!(info, Type::Window, true, "窗口创建成功"); + logging!(info, Type::Window, "窗口创建成功"); std::thread::sleep(std::time::Duration::from_millis(100)); WindowOperationResult::Created } else { - logging!(warn, Type::Window, true, "窗口创建失败"); + logging!(warn, Type::Window, "窗口创建失败"); WindowOperationResult::Failed } } WindowState::VisibleFocused => { - logging!(info, Type::Window, true, "窗口已经可见且有焦点,无需操作"); + logging!(info, Type::Window, "窗口已经可见且有焦点,无需操作"); WindowOperationResult::NoAction } 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, - "窗口在检查期间已变为可见和有焦点状态" - ); + logging!(info, Type::Window, "窗口在检查期间已变为可见和有焦点状态"); return WindowOperationResult::NoAction; } Self::activate_window(&window) @@ -189,13 +177,12 @@ impl WindowManager { finish_window_operation(); }); - logging!(info, Type::Window, true, "开始切换主窗口显示状态"); + logging!(info, Type::Window, "开始切换主窗口显示状态"); let current_state = Self::get_main_window_state(); logging!( info, Type::Window, - true, "当前窗口状态: {:?} | 详细状态: {}", current_state, Self::get_window_status_info() @@ -204,7 +191,7 @@ impl WindowManager { match current_state { WindowState::NotExist => { // 窗口不存在,创建新窗口 - logging!(info, Type::Window, true, "窗口不存在,将创建新窗口"); + logging!(info, Type::Window, "窗口不存在,将创建新窗口"); // 由于已经有防抖保护,直接调用内部方法 if Self::create_window(true).await { WindowOperationResult::Created @@ -216,7 +203,6 @@ impl WindowManager { logging!( info, Type::Window, - true, "窗口可见(焦点状态: {}),将隐藏窗口", if current_state == WindowState::VisibleFocused { "有焦点" @@ -227,30 +213,25 @@ impl WindowManager { if let Some(window) = Self::get_main_window() { match window.hide() { Ok(_) => { - logging!(info, Type::Window, true, "窗口已成功隐藏"); + logging!(info, Type::Window, "窗口已成功隐藏"); WindowOperationResult::Hidden } Err(e) => { - logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e); + logging!(warn, Type::Window, "隐藏窗口失败: {}", e); WindowOperationResult::Failed } } } else { - logging!(warn, Type::Window, true, "无法获取窗口实例"); + logging!(warn, Type::Window, "无法获取窗口实例"); WindowOperationResult::Failed } } WindowState::Minimized | WindowState::Hidden => { - logging!( - info, - Type::Window, - true, - "窗口存在但被隐藏或最小化,将激活窗口" - ); + logging!(info, Type::Window, "窗口存在但被隐藏或最小化,将激活窗口"); if let Some(window) = Self::get_main_window() { Self::activate_window(&window) } else { - logging!(warn, Type::Window, true, "无法获取窗口实例"); + logging!(warn, Type::Window, "无法获取窗口实例"); WindowOperationResult::Failed } } @@ -259,35 +240,35 @@ impl WindowManager { /// 激活窗口(取消最小化、显示、设置焦点) fn activate_window(window: &WebviewWindow) -> WindowOperationResult { - logging!(info, Type::Window, true, "开始激活窗口"); + logging!(info, Type::Window, "开始激活窗口"); let mut operations_successful = true; // 1. 如果窗口最小化,先取消最小化 if window.is_minimized().unwrap_or(false) { - logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化"); + logging!(info, Type::Window, "窗口已最小化,正在取消最小化"); if let Err(e) = window.unminimize() { - logging!(warn, Type::Window, true, "取消最小化失败: {}", e); + logging!(warn, Type::Window, "取消最小化失败: {}", e); operations_successful = false; } } // 2. 显示窗口 if let Err(e) = window.show() { - logging!(warn, Type::Window, true, "显示窗口失败: {}", e); + logging!(warn, Type::Window, "显示窗口失败: {}", e); operations_successful = false; } // 3. 设置焦点 if let Err(e) = window.set_focus() { - logging!(warn, Type::Window, true, "设置窗口焦点失败: {}", e); + logging!(warn, Type::Window, "设置窗口焦点失败: {}", e); operations_successful = false; } // 4. 平台特定的激活策略 #[cfg(target_os = "macos")] { - logging!(info, Type::Window, true, "应用 macOS 特定的激活策略"); + logging!(info, Type::Window, "应用 macOS 特定的激活策略"); handle::Handle::global().set_activation_policy_regular(); } @@ -295,31 +276,19 @@ impl WindowManager { { // Windows 尝试额外的激活方法 if let Err(e) = window.set_always_on_top(true) { - logging!( - debug, - Type::Window, - true, - "设置置顶失败(非关键错误): {}", - e - ); + logging!(debug, Type::Window, "设置置顶失败(非关键错误): {}", e); } // 立即取消置顶 if let Err(e) = window.set_always_on_top(false) { - logging!( - debug, - Type::Window, - true, - "取消置顶失败(非关键错误): {}", - e - ); + logging!(debug, Type::Window, "取消置顶失败(非关键错误): {}", e); } } if operations_successful { - logging!(info, Type::Window, true, "窗口激活成功"); + logging!(info, Type::Window, "窗口激活成功"); WindowOperationResult::Shown } else { - logging!(warn, Type::Window, true, "窗口激活部分失败"); + logging!(warn, Type::Window, "窗口激活部分失败"); WindowOperationResult::Failed } } @@ -351,7 +320,6 @@ impl WindowManager { logging!( info, Type::Window, - true, "开始创建/显示主窗口, is_show={}", is_show ); @@ -362,15 +330,15 @@ impl WindowManager { match build_new_window() { Ok(_) => { - logging!(info, Type::Window, true, "新窗口创建成功"); + logging!(info, Type::Window, "新窗口创建成功"); } Err(e) => { - logging!(error, Type::Window, true, "新窗口创建失败: {}", e); + logging!(error, Type::Window, "新窗口创建失败: {}", e); return false; } } - if WindowOperationResult::Failed != Self::show_main_window().await { + if WindowOperationResult::Failed == Self::show_main_window().await { return false; } @@ -384,15 +352,15 @@ impl WindowManager { pub fn destroy_main_window() -> WindowOperationResult { if let Some(window) = Self::get_main_window() { let _ = window.destroy(); - logging!(info, Type::Window, true, "窗口已摧毁"); + logging!(info, Type::Window, "窗口已摧毁"); #[cfg(target_os = "macos")] { - logging!(info, Type::Window, true, "应用 macOS 特定的激活策略"); + logging!(info, Type::Window, "应用 macOS 特定的激活策略"); handle::Handle::global().set_activation_policy_accessory(); } return WindowOperationResult::Destroyed; } - logging!(warn, Type::Window, true, "窗口摧毁失败"); + logging!(warn, Type::Window, "窗口摧毁失败"); WindowOperationResult::Failed } diff --git a/clash-verge-rev/src-tauri/tauri.conf.json b/clash-verge-rev/src-tauri/tauri.conf.json index 747e9909a9..8e2c72cef8 100755 --- a/clash-verge-rev/src-tauri/tauri.conf.json +++ b/clash-verge-rev/src-tauri/tauri.conf.json @@ -49,8 +49,11 @@ "security": { "capabilities": ["desktop-capability", "migrated"], "assetProtocol": { - "scope": ["$APPDATA/**", "$RESOURCE/../**", "**"], - "enable": true + "enable": true, + "scope": { + "allow": ["**"], + "requireLiteralLeadingDot": false + } }, "csp": null } diff --git a/clash-verge-rev/src-tauri/tauri.linux.conf.json b/clash-verge-rev/src-tauri/tauri.linux.conf.json index 655106b3e1..0d5c0de395 100644 --- a/clash-verge-rev/src-tauri/tauri.linux.conf.json +++ b/clash-verge-rev/src-tauri/tauri.linux.conf.json @@ -5,7 +5,7 @@ "targets": ["deb", "rpm"], "linux": { "deb": { - "depends": ["openssl", "pkexec"], + "depends": ["openssl"], "desktopTemplate": "./packages/linux/clash-verge.desktop", "provides": ["clash-verge"], "conflicts": ["clash-verge"], @@ -14,7 +14,7 @@ "preRemoveScript": "./packages/linux/pre-remove.sh" }, "rpm": { - "depends": ["openssl", "pkexec"], + "depends": ["openssl"], "desktopTemplate": "./packages/linux/clash-verge.desktop", "provides": ["clash-verge"], "conflicts": ["clash-verge"], @@ -25,8 +25,8 @@ }, "externalBin": [ "./resources/clash-verge-service", - "./resources/install-service", - "./resources/uninstall-service", + "./resources/clash-verge-service-install", + "./resources/clash-verge-service-uninstall", "./sidecar/verge-mihomo", "./sidecar/verge-mihomo-alpha" ] diff --git a/clash-verge-rev/src/assets/styles/layout.scss b/clash-verge-rev/src/assets/styles/layout.scss index 59426ee3a3..d328a30d91 100644 --- a/clash-verge-rev/src/assets/styles/layout.scss +++ b/clash-verge-rev/src/assets/styles/layout.scss @@ -2,118 +2,103 @@ width: 100%; height: 100vh; display: flex; + flex-direction: column; overflow: hidden; - &__left { - flex: 1 0 200px; + .layout-content { + /* New container for the flex layout */ display: flex; - height: 100%; - width: 100%; - // max-width: 225px; - // min-width: 225px; - // padding: 16px 0 8px; - padding: 0px 0px 8px; - // position: relative; - flex-direction: column; - align-self: stretch; - box-sizing: border-box; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; + flex: 1; /* Take remaining height */ overflow: hidden; - border-right: 1px solid var(--divider-color); - // background-color: var(--background-color-alpha); - // $maxLogo: 100px; - - .the-logo { - position: relative; - flex: 1 0 58px; - // width: 100%; + &__left { + flex: 1 0 200px; display: flex; height: 100%; - padding: 0px 20px; + width: 100%; + padding: 0 0 8px; flex-direction: column; - justify-content: center; - align-items: flex-start; align-self: stretch; - // border-bottom: 1px solid var(--divider-color); - // max-width: $maxLogo + 32px; - // max-height: $maxLogo; - // margin: 0 auto; - // padding: 0 auto; - // text-align: center; box-sizing: border-box; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + overflow: hidden; + border-right: 1px solid var(--divider-color); - img, - svg { - width: 100%; + .the-logo { + position: relative; + flex: 1 0 58px; + display: flex; height: 100%; - pointer-events: none; - // fill: var(--primary-main); + padding: 0 20px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + align-self: stretch; + box-sizing: border-box; - // #bg { - // fill: var(--background-color); - // } + img, + svg { + width: 100%; + height: 100%; + pointer-events: none; + } + + .the-newbtn { + position: absolute; + right: 10px; + top: 15px; + border-radius: 8px; + padding: 2px 4px; + transform: scale(0.8); + } } - .the-newbtn { + .the-menu { + flex: 1 1 80%; + overflow-y: auto; + margin-bottom: 0px; + padding-top: 4px; + } + + .the-traffic { + flex: 0 0 60px; + + > div { + margin: 0 auto; + padding: 0 20px; + } + } + } + + &__right { + position: relative; + flex: 1 1 100%; + height: 100%; + + .the-bar { + height: 36px; + display: flex; + justify-content: end; + box-sizing: border-box; + z-index: 2; + + .the-dragbar { + margin-top: 5px; + app-region: drag; + } + } + + .the-content { position: absolute; - right: 10px; - top: 15px; - border-radius: 8px; - padding: 2px 4px; - transform: scale(0.8); + top: 0; + left: 0; + right: 1px; + bottom: 0px; } } - - .the-menu { - flex: 1 1 80%; - overflow-y: auto; - margin-bottom: 0px; - padding-top: 4px; - } - - .the-traffic { - flex: 0 0 60px; - - > div { - margin: 0 auto; - padding: 0px 20px; - } - } - } - - &__right { - position: relative; - flex: 1 1 100%; - height: 100%; - // background-color: var(--background-color-alpha); - - .the-bar { - // position: absolute; - // top: 0px; - // right: 0px; - height: 36px; - display: flex; - // align-items: center; - justify-content: end; - box-sizing: border-box; - z-index: 2; - .the-dragbar { - margin-top: 5px; - app-region: drag; - } - } - - .the-content { - position: absolute; - top: 0; - left: 0; - right: 1px; - bottom: 0px; - } } } @@ -121,11 +106,17 @@ .windows, .unknown { &.layout { - //.layout__left { - // padding-top: 24px; - //} + .the_titlebar { + width: 100%; + display: flex; + justify-content: flex-end; + padding: 10px; + box-sizing: border-box; + height: 36px; + border-bottom: 1px solid var(--divider-color); + } - .layout__left .the-logo { + .layout-content__left .the-logo { flex: 1 0 58px; margin-top: 10px; margin-left: 10px; @@ -135,7 +126,7 @@ padding-bottom: 16px; } - .layout__right .the-content { + .layout-content__right .the-content { top: 5px; } } @@ -143,15 +134,25 @@ .macos { &.layout { - .layout__left { + .the_titlebar { + width: 100%; + display: flex; + justify-content: flex-start; + padding: 10px; + box-sizing: border-box; + height: 36px; + border-bottom: 1px solid var(--divider-color); + } + + .layout-content__left { padding-top: 5px; } - .layout__right .the-content { + .layout-content__right .the-content { top: 5px; } - .layout__left .the-newbtn { + .layout-content__left .the-newbtn { right: 9px; top: 2px; } diff --git a/clash-verge-rev/src/components/connection/connection-detail.tsx b/clash-verge-rev/src/components/connection/connection-detail.tsx index dd129f5c38..f580ae0060 100644 --- a/clash-verge-rev/src/components/connection/connection-detail.tsx +++ b/clash-verge-rev/src/components/connection/connection-detail.tsx @@ -3,8 +3,8 @@ import { useLockFn } from "ahooks"; import dayjs from "dayjs"; import { t } from "i18next"; import { useImperativeHandle, useState, type Ref } from "react"; +import { closeConnections } from "tauri-plugin-mihomo-api"; -import { deleteConnection } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; export interface ConnectionDetailRef { @@ -97,7 +97,7 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => { { label: t("Type"), value: `${metadata.type}(${metadata.network})` }, ]; - const onDelete = useLockFn(async () => deleteConnection(data.id)); + const onDelete = useLockFn(async () => closeConnections(data.id)); return ( diff --git a/clash-verge-rev/src/components/connection/connection-item.tsx b/clash-verge-rev/src/components/connection/connection-item.tsx index 77d2c7e94f..a596ec72bb 100644 --- a/clash-verge-rev/src/components/connection/connection-item.tsx +++ b/clash-verge-rev/src/components/connection/connection-item.tsx @@ -9,8 +9,8 @@ import { } from "@mui/material"; import { useLockFn } from "ahooks"; import dayjs from "dayjs"; +import { closeConnections } from "tauri-plugin-mihomo-api"; -import { deleteConnection } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; const Tag = styled("span")(({ theme }) => ({ @@ -34,7 +34,7 @@ export const ConnectionItem = (props: Props) => { const { id, metadata, chains, start, curUpload, curDownload } = value; - const onDelete = useLockFn(async () => deleteConnection(id)); + const onDelete = useLockFn(async () => closeConnections(id)); const showTraffic = curUpload! >= 100 || curDownload! >= 100; return ( diff --git a/clash-verge-rev/src/components/controller/window-controller.tsx b/clash-verge-rev/src/components/controller/window-controller.tsx new file mode 100644 index 0000000000..5e0fb638a6 --- /dev/null +++ b/clash-verge-rev/src/components/controller/window-controller.tsx @@ -0,0 +1,114 @@ +import { Close, CropSquare, FilterNone, Minimize } from "@mui/icons-material"; +import { IconButton } from "@mui/material"; +import { forwardRef, useImperativeHandle } from "react"; + +import { useWindowControls } from "@/hooks/use-window"; +import getSystem from "@/utils/get-system"; + +export const WindowControls = forwardRef(function WindowControls(props, ref) { + const OS = getSystem(); + const { + currentWindow, + maximized, + minimize, + close, + toggleFullscreen, + toggleMaximize, + } = useWindowControls(); + + useImperativeHandle( + ref, + () => ({ + currentWindow, + maximized, + minimize, + close, + toggleFullscreen, + toggleMaximize, + }), + [ + currentWindow, + maximized, + minimize, + close, + toggleFullscreen, + toggleMaximize, + ], + ); + + // 通过前端对 tauri 窗口进行翻转全屏时会短暂地与系统图标重叠渲染。 + // 这可能是上游缺陷,保险起见跨平台以窗口的最大化翻转为准 + + return ( +
+ {OS === "macos" && ( + <> + {/* macOS 风格:关闭 → 最小化 → 全屏 */} + + + + + + + + {maximized ? ( + + ) : ( + + )} + + + )} + + {OS === "windows" && ( + <> + {/* Windows 风格:最小化 → 最大化 → 关闭 */} + + + + + {maximized ? ( + + ) : ( + + )} + + + + + + )} + + {OS === "linux" && ( + <> + {/* Linux 桌面常见布局(GNOME/KDE 多为:最小化 → 最大化 → 关闭) */} + + + + + {maximized ? ( + + ) : ( + + )} + + + + + + )} +
+ ); +}); diff --git a/clash-verge-rev/src/components/home/clash-info-card.tsx b/clash-verge-rev/src/components/home/clash-info-card.tsx index a3ca1f75d6..9b6ff4b71b 100644 --- a/clash-verge-rev/src/components/home/clash-info-card.tsx +++ b/clash-verge-rev/src/components/home/clash-info-card.tsx @@ -53,7 +53,7 @@ export const ClashInfoCard = () => { {t("Mixed Port")} - {clashConfig["mixed-port"] || "-"} + {clashConfig.mixedPort || "-"} diff --git a/clash-verge-rev/src/components/home/clash-mode-card.tsx b/clash-verge-rev/src/components/home/clash-mode-card.tsx index bceaadb4f9..9458da0503 100644 --- a/clash-verge-rev/src/components/home/clash-mode-card.tsx +++ b/clash-verge-rev/src/components/home/clash-mode-card.tsx @@ -7,10 +7,11 @@ import { Box, Paper, Stack, Typography } from "@mui/material"; import { useLockFn } from "ahooks"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; +import { closeAllConnections } from "tauri-plugin-mihomo-api"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-context"; -import { closeAllConnections, patchClashMode } from "@/services/cmds"; +import { patchClashMode } from "@/services/cmds"; export const ClashModeCard = () => { const { t } = useTranslation(); diff --git a/clash-verge-rev/src/components/home/current-proxy-card.tsx b/clash-verge-rev/src/components/home/current-proxy-card.tsx index b96dbb0525..b3f372eaf5 100644 --- a/clash-verge-rev/src/components/home/current-proxy-card.tsx +++ b/clash-verge-rev/src/components/home/current-proxy-card.tsx @@ -1,6 +1,7 @@ import { AccessTimeRounded, ChevronRight, + NetworkCheckRounded, WifiOff as SignalError, SignalWifi3Bar as SignalGood, SignalWifi2Bar as SignalMedium, @@ -25,12 +26,16 @@ import { alpha, useTheme, } from "@mui/material"; +import { useLockFn } from "ahooks"; +import React from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; +import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api"; import { EnhancedCard } from "@/components/home/enhanced-card"; import { useProxySelection } from "@/hooks/use-proxy-selection"; +import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-context"; import delayManager from "@/services/delay"; @@ -47,7 +52,9 @@ interface ProxyOption { // 排序类型: 默认 | 按延迟 | 按字母 type ProxySortType = 0 | 1 | 2; -function convertDelayColor(delayValue: number) { +function convertDelayColor( + delayValue: number, +): "success" | "warning" | "error" | "primary" | "default" { const colorStr = delayManager.formatDelayColor(delayValue); if (!colorStr) return "default"; @@ -67,7 +74,11 @@ function convertDelayColor(delayValue: number) { } } -function getSignalIcon(delay: number) { +function getSignalIcon(delay: number): { + icon: React.ReactElement; + text: string; + color: string; +} { if (delay < 0) return { icon: , text: "未测试", color: "text.secondary" }; if (delay >= 10000) @@ -81,20 +92,12 @@ function getSignalIcon(delay: number) { return { icon: , text: "延迟极佳", color: "success.main" }; } -// 简单的防抖函数 -function debounce(fn: Function, ms = 100) { - let timeoutId: ReturnType; - return function (this: any, ...args: any[]) { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => fn.apply(this, args), ms); - }; -} - export const CurrentProxyCard = () => { const { t } = useTranslation(); const navigate = useNavigate(); const theme = useTheme(); const { proxies, clashConfig, refreshProxy } = useAppData(); + const { verge } = useVerge(); // 统一代理选择器 const { handleSelectChange } = useProxySelection({ @@ -172,6 +175,7 @@ export const CurrentProxyCard = () => { // 根据模式确定初始组 if (isGlobalMode) { + // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect setState((prev) => ({ ...prev, selection: { @@ -180,6 +184,7 @@ export const CurrentProxyCard = () => { }, })); } else if (isDirectMode) { + // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect setState((prev) => ({ ...prev, selection: { @@ -189,6 +194,7 @@ export const CurrentProxyCard = () => { })); } else { const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP); + // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect setState((prev) => ({ ...prev, selection: { @@ -203,6 +209,7 @@ export const CurrentProxyCard = () => { useEffect(() => { if (!proxies) return; + // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect setState((prev) => { // 只保留 Selector 类型的组用于选择 const filteredGroups = proxies.groups @@ -270,16 +277,23 @@ export const CurrentProxyCard = () => { }, [proxies, isGlobalMode, isDirectMode]); // 使用防抖包装状态更新 + const timeoutRef = React.useRef | null>(null); + const debouncedSetState = useCallback( - debounce((updateFn: (prev: ProxyState) => ProxyState) => { - setState(updateFn); - }, 300), - [], + (updateFn: (prev: ProxyState) => ProxyState) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setState(updateFn); + }, 300); + }, + [setState], ); // 处理代理组变更 const handleGroupChange = useCallback( - (event: SelectChangeEvent) => { + (event: SelectChangeEvent) => { if (isGlobalMode || isDirectMode) return; const newGroup = event.target.value; @@ -314,7 +328,7 @@ export const CurrentProxyCard = () => { // 处理代理节点变更 const handleProxyChange = useCallback( - (event: SelectChangeEvent) => { + (event: SelectChangeEvent) => { if (isDirectMode) return; const newProxy = event.target.value; @@ -399,6 +413,85 @@ export const CurrentProxyCard = () => { localStorage.setItem(STORAGE_KEY_SORT_TYPE, newSortType.toString()); }, [sortType]); + // 延迟测试 + const handleCheckDelay = useLockFn(async () => { + const groupName = state.selection.group; + if (!groupName || isDirectMode) return; + + console.log(`[CurrentProxyCard] 开始测试所有延迟,组: ${groupName}`); + + const timeout = verge?.default_latency_timeout || 10000; + + // 获取当前组的所有代理 + const proxyNames: string[] = []; + const providers: Set = new Set(); + + if (isGlobalMode && proxies?.global) { + // 全局模式 + const allProxies = proxies.global.all + .filter((p: any) => { + const name = typeof p === "string" ? p : p.name; + return name !== "DIRECT" && name !== "REJECT"; + }) + .map((p: any) => (typeof p === "string" ? p : p.name)); + + allProxies.forEach((name: string) => { + const proxy = state.proxyData.records[name]; + if (proxy?.provider) { + providers.add(proxy.provider); + } else { + proxyNames.push(name); + } + }); + } else { + // 规则模式 + const group = state.proxyData.groups.find((g) => g.name === groupName); + if (group) { + group.all.forEach((name: string) => { + const proxy = state.proxyData.records[name]; + if (proxy?.provider) { + providers.add(proxy.provider); + } else { + proxyNames.push(name); + } + }); + } + } + + console.log( + `[CurrentProxyCard] 找到代理数量: ${proxyNames.length}, 提供者数量: ${providers.size}`, + ); + + // 测试提供者的节点 + if (providers.size > 0) { + console.log(`[CurrentProxyCard] 开始测试提供者节点`); + await Promise.allSettled( + [...providers].map((p) => healthcheckProxyProvider(p)), + ); + } + + // 测试非提供者的节点 + if (proxyNames.length > 0) { + const url = delayManager.getUrl(groupName); + console.log(`[CurrentProxyCard] 测试URL: ${url}, 超时: ${timeout}ms`); + + try { + await Promise.race([ + delayManager.checkListDelay(proxyNames, groupName, timeout), + delayGroup(groupName, url, timeout), + ]); + console.log(`[CurrentProxyCard] 延迟测试完成,组: ${groupName}`); + } catch (error) { + console.error( + `[CurrentProxyCard] 延迟测试出错,组: ${groupName}`, + error, + ); + } + } + + refreshProxy(); + }); + // 排序代理函数(增加非空校验) const sortProxies = useCallback( (proxies: ProxyOption[]) => { @@ -474,7 +567,7 @@ export const CurrentProxyCard = () => { ]); // 获取排序图标 - const getSortIcon = () => { + const getSortIcon = (): React.ReactElement => { switch (sortType) { case 1: return ; @@ -486,7 +579,7 @@ export const CurrentProxyCard = () => { }; // 获取排序提示文本 - const getSortTooltip = () => { + const getSortTooltip = (): string => { switch (sortType) { case 0: return t("Sort by default"); @@ -517,13 +610,24 @@ export const CurrentProxyCard = () => { } iconColor={currentProxy ? "primary" : undefined} action={ - + + + + + + + + {getSortIcon()} @@ -657,7 +761,7 @@ export const CurrentProxyCard = () => { > {isDirectMode ? null - : proxyOptions.map((proxy, index) => { + : proxyOptions.map((proxy) => { const delayValue = state.proxyData.records[proxy.name] && state.selection.group @@ -668,7 +772,7 @@ export const CurrentProxyCard = () => { : -1; return ( {/* 控制层覆盖 */} @@ -962,8 +963,8 @@ export const EnhancedCanvasTrafficGraph = memo( lineHeight: 1.2, }} > - Points: {displayData.length} | Fresh: {isDataFresh ? "✓" : "✗"} | - Compressed: {samplerStats.compressedBufferSize} + Points: {displayData.length} | Compressed:{" "} + {samplerStats.compressedBufferSize} {/* 悬浮提示框 */} @@ -988,6 +989,7 @@ export const EnhancedCanvasTrafficGraph = memo( boxShadow: "0 4px 12px rgba(0,0,0,0.15)", backdropFilter: "none", opacity: 1, + whiteSpace: "nowrap", }} > diff --git a/clash-verge-rev/src/components/home/enhanced-traffic-stats.tsx b/clash-verge-rev/src/components/home/enhanced-traffic-stats.tsx index 9bf97200a7..e731ac72c2 100644 --- a/clash-verge-rev/src/components/home/enhanced-traffic-stats.tsx +++ b/clash-verge-rev/src/components/home/enhanced-traffic-stats.tsx @@ -7,7 +7,6 @@ import { MemoryRounded, } from "@mui/icons-material"; import { - Box, Grid, PaletteColor, Paper, @@ -15,16 +14,16 @@ import { alpha, useTheme, } from "@mui/material"; -import { ReactNode, memo, useCallback, useMemo, useRef } from "react"; +import { useRef, memo, useMemo } from "react"; +import { ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import useSWR from "swr"; import { TrafficErrorBoundary } from "@/components/common/traffic-error-boundary"; -import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor"; +import { useConnectionData } from "@/hooks/use-connection-data"; +import { useMemoryData } from "@/hooks/use-memory-data"; +import { useTrafficData } from "@/hooks/use-traffic-data"; import { useVerge } from "@/hooks/use-verge"; import { useVisibility } from "@/hooks/use-visibility"; -import { useAppData } from "@/providers/app-data-context"; -import { gc, isDebugEnabled } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; import { @@ -148,51 +147,33 @@ export const EnhancedTrafficStats = () => { const trafficRef = useRef(null); const pageVisible = useVisibility(); - // 使用AppDataProvider - const { connections } = useAppData(); + const { + response: { data: traffic }, + } = useTrafficData(); - // 使用增强版的统一流量数据Hook - const { traffic, memory, isLoading, isDataFresh, hasValidData } = - useTrafficDataEnhanced(); + const { + response: { data: memory }, + } = useMemoryData(); + + const { + response: { data: connections }, + } = useConnectionData(); // 是否显示流量图表 const trafficGraph = verge?.traffic_graph ?? true; - // 检查是否支持调试 - // TODO: merge this hook with layout-traffic.tsx - const { data: isDebug } = useSWR( - `clash-verge-rev-internal://isDebugEnabled`, - () => isDebugEnabled(), - { - // default value before is fetched - fallbackData: false, - }, - ); - // Canvas组件现在直接从全局Hook获取数据,无需手动添加数据点 - // 执行垃圾回收 - const handleGarbageCollection = useCallback(async () => { - if (isDebug) { - try { - await gc(); - console.log("[Debug] 垃圾回收已执行"); - } catch (err) { - console.error("[Debug] 垃圾回收失败:", err); - } - } - }, [isDebug]); - // 使用useMemo计算解析后的流量数据 const parsedData = useMemo(() => { - const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0); - const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0); - const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0); + const [up, upUnit] = parseTraffic(traffic?.up || 0); + const [down, downUnit] = parseTraffic(traffic?.down || 0); + const [inuse, inuseUnit] = parseTraffic(memory?.inuse || 0); const [uploadTotal, uploadTotalUnit] = parseTraffic( - connections.uploadTotal, + connections?.uploadTotal, ); const [downloadTotal, downloadTotalUnit] = parseTraffic( - connections.downloadTotal, + connections?.downloadTotal, ); return { @@ -206,7 +187,7 @@ export const EnhancedTrafficStats = () => { uploadTotalUnit, downloadTotal, downloadTotalUnit, - connectionsCount: connections.count, + connectionsCount: connections?.connections.length, }; }, [traffic, memory, connections]); @@ -228,33 +209,10 @@ export const EnhancedTrafficStats = () => { >
- {isDebug && ( -
- DEBUG: {trafficRef.current ? "图表已初始化" : "图表未初始化"} -
- 状态: {isDataFresh ? "active" : "inactive"} -
- 数据新鲜度: {traffic?.is_fresh ? "Fresh" : "Stale"} -
- {new Date().toISOString().slice(11, 19)} -
- )}
); - }, [trafficGraph, pageVisible, theme.palette.divider, isDebug]); + }, [trafficGraph, pageVisible, theme.palette.divider]); // 使用useMemo计算统计卡片配置 const statCards = useMemo( @@ -300,10 +258,10 @@ export const EnhancedTrafficStats = () => { value: parsedData.inuse, unit: parsedData.inuseUnit, color: "error" as const, - onClick: isDebug ? handleGarbageCollection : undefined, + onClick: undefined, }, ], - [t, parsedData, isDebug, handleGarbageCollection], + [t, parsedData], ); return ( @@ -320,28 +278,11 @@ export const EnhancedTrafficStats = () => { )} {/* 统计卡片区域 */} - {statCards.map((card, index) => ( - - + {statCards.map((card, _index) => ( + + ))} - - {/* 数据状态指示器(调试用)*/} - {isDebug && ( - - - 数据状态: {isDataFresh ? "新鲜" : "过期"} | 有效数据:{" "} - {hasValidData ? "是" : "否"} | 加载中: {isLoading ? "是" : "否"} - - - )} ); diff --git a/clash-verge-rev/src/components/layout/layout-traffic.tsx b/clash-verge-rev/src/components/layout/layout-traffic.tsx index 71f662f2f8..9b37c5252f 100644 --- a/clash-verge-rev/src/components/layout/layout-traffic.tsx +++ b/clash-verge-rev/src/components/layout/layout-traffic.tsx @@ -6,34 +6,19 @@ import { import { Box, Typography } from "@mui/material"; import { useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; -import useSWR from "swr"; import { LightweightTrafficErrorBoundary } from "@/components/common/traffic-error-boundary"; -import { useClashInfo } from "@/hooks/use-clash"; -import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor"; +import { useMemoryData } from "@/hooks/use-memory-data"; +import { useTrafficData } from "@/hooks/use-traffic-data"; import { useVerge } from "@/hooks/use-verge"; import { useVisibility } from "@/hooks/use-visibility"; -import { isDebugEnabled, gc, startTrafficService } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; import { TrafficGraph, type TrafficRef } from "./traffic-graph"; // setup the traffic export const LayoutTraffic = () => { - const { data: isDebug } = useSWR( - "clash-verge-rev-internal://isDebugEnabled", - () => isDebugEnabled(), - { - // default value before is fetched - fallbackData: false, - }, - ); - - if (isDebug) { - console.debug("[Traffic][LayoutTraffic] 组件正在渲染"); - } const { t } = useTranslation(); - const { clashInfo } = useClashInfo(); const { verge } = useVerge(); // whether hide traffic graph @@ -42,31 +27,19 @@ export const LayoutTraffic = () => { const trafficRef = useRef(null); const pageVisible = useVisibility(); - // 使用增强版的统一流量数据Hook - const { traffic, memory } = useTrafficDataEnhanced(); - - // 启动流量服务 - useEffect(() => { - console.log( - "[Traffic][LayoutTraffic] useEffect 触发,clashInfo:", - clashInfo, - "pageVisible:", - pageVisible, - ); - - // 简化条件,只要组件挂载就尝试启动服务 - console.log("[Traffic][LayoutTraffic] 开始启动流量服务"); - startTrafficService().catch((error) => { - console.error("[Traffic][LayoutTraffic] 启动流量服务失败:", error); - }); - }, []); // 移除依赖,只在组件挂载时启动一次 + const { + response: { data: traffic }, + } = useTrafficData(); + const { + response: { data: memory }, + } = useMemoryData(); // 监听数据变化,为图表添加数据点 useEffect(() => { - if (traffic?.raw && trafficRef.current) { + if (trafficRef.current) { trafficRef.current.appendData({ - up: traffic.raw.up_rate || 0, - down: traffic.raw.down_rate || 0, + up: traffic?.up || 0, + down: traffic?.down || 0, }); } }, [traffic]); @@ -75,9 +48,9 @@ export const LayoutTraffic = () => { const displayMemory = verge?.enable_memory_usage ?? true; // 使用parseTraffic统一处理转换,保持与首页一致的显示格式 - const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0); - const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0); - const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0); + const [up, upUnit] = parseTraffic(traffic?.up || 0); + const [down, downUnit] = parseTraffic(traffic?.down || 0); + const [inuse, inuseUnit] = parseTraffic(memory?.inuse || 0); const boxStyle: any = { display: "flex", @@ -114,18 +87,16 @@ export const LayoutTraffic = () => { 0 ? "secondary" : "disabled" - } + color={(traffic?.up || 0) > 0 ? "secondary" : "disabled"} /> {up} @@ -134,18 +105,16 @@ export const LayoutTraffic = () => { 0 ? "primary" : "disabled" - } + color={(traffic?.down || 0) > 0 ? "primary" : "disabled"} /> {down} @@ -155,15 +124,15 @@ export const LayoutTraffic = () => { {displayMemory && ( { - isDebug && (await gc()); + // isDebug && (await gc()); }} > diff --git a/clash-verge-rev/src/components/layout/use-custom-theme.ts b/clash-verge-rev/src/components/layout/use-custom-theme.ts index 1bc05b26ae..145bc5bd71 100644 --- a/clash-verge-rev/src/components/layout/use-custom-theme.ts +++ b/clash-verge-rev/src/components/layout/use-custom-theme.ts @@ -53,6 +53,13 @@ export const useCustomTheme = () => { return; } + if ( + typeof window !== "undefined" && + typeof window.matchMedia === "function" + ) { + return; + } + let isMounted = true; const timerId = setTimeout(() => { @@ -90,6 +97,44 @@ export const useCustomTheme = () => { }; }, [theme_mode, appWindow, setMode]); + useEffect(() => { + if (theme_mode !== "system") { + return; + } + + if ( + typeof window === "undefined" || + typeof window.matchMedia !== "function" + ) { + return; + } + + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const syncMode = (isDark: boolean) => setMode(isDark ? "dark" : "light"); + const handleChange = (event: MediaQueryListEvent) => + syncMode(event.matches); + + syncMode(mediaQuery.matches); + + if (typeof mediaQuery.addEventListener === "function") { + mediaQuery.addEventListener("change", handleChange); + return () => mediaQuery.removeEventListener("change", handleChange); + } + + type MediaQueryListLegacy = MediaQueryList & { + addListener?: ( + listener: (this: MediaQueryList, event: MediaQueryListEvent) => void, + ) => void; + removeListener?: ( + listener: (this: MediaQueryList, event: MediaQueryListEvent) => void, + ) => void; + }; + + const legacyQuery = mediaQuery as MediaQueryListLegacy; + legacyQuery.addListener?.(handleChange); + return () => legacyQuery.removeListener?.(handleChange); + }, [theme_mode, setMode]); + useEffect(() => { if (theme_mode === undefined) { return; diff --git a/clash-verge-rev/src/components/profile/profile-item.tsx b/clash-verge-rev/src/components/profile/profile-item.tsx index a7b3a30ca5..876466fb6a 100644 --- a/clash-verge-rev/src/components/profile/profile-item.tsx +++ b/clash-verge-rev/src/components/profile/profile-item.tsx @@ -1,6 +1,11 @@ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { RefreshRounded, DragIndicatorRounded } from "@mui/icons-material"; +import { + RefreshRounded, + DragIndicatorRounded, + CheckBoxRounded, + CheckBoxOutlineBlankRounded, +} from "@mui/icons-material"; import { Box, Typography, @@ -49,11 +54,24 @@ interface Props { onEdit: () => void; onSave?: (prev?: string, curr?: string) => void; onDelete: () => void; + batchMode?: boolean; + isSelected?: boolean; + onSelectionChange?: () => void; } export const ProfileItem = (props: Props) => { - const { selected, activating, itemData, onSelect, onEdit, onSave, onDelete } = - props; + const { + selected, + activating, + itemData, + onSelect, + onEdit, + onSave, + onDelete, + batchMode, + isSelected, + onSelectionChange, + } = props; const { attributes, listeners, @@ -363,7 +381,12 @@ export const ProfileItem = (props: Props) => { label: "Delete", handler: () => { setAnchorEl(null); - setConfirmOpen(true); + if (batchMode) { + // If in batch mode, just toggle selection instead of showing delete confirmation + onSelectionChange && onSelectionChange(); + } else { + setConfirmOpen(true); + } }, disabled: false, }, @@ -402,7 +425,12 @@ export const ProfileItem = (props: Props) => { label: "Delete", handler: () => { setAnchorEl(null); - setConfirmOpen(true); + if (batchMode) { + // If in batch mode, just toggle selection instead of showing delete confirmation + onSelectionChange && onSelectionChange(); + } else { + setConfirmOpen(true); + } }, disabled: false, }, @@ -510,9 +538,29 @@ export const ProfileItem = (props: Props) => { )} + {batchMode && ( + { + e.stopPropagation(); + onSelectionChange && onSelectionChange(); + }} + > + {isSelected ? ( + + ) : ( + + )} + + )} @@ -527,7 +575,7 @@ export const ProfileItem = (props: Props) => { (({ theme }) => ({ display: "inline-block", @@ -74,7 +60,7 @@ export const ProviderButton = () => { // 设置更新状态 setUpdating((prev) => ({ ...prev, [name]: true })); - await proxyProviderUpdate(name); + await updateProxyProvider(name); // 刷新数据 await refreshProxy(); @@ -115,7 +101,7 @@ export const ProviderButton = () => { // 改为串行逐个更新所有provider for (const name of allProviders) { try { - await proxyProviderUpdate(name); + await updateProxyProvider(name); // 每个更新完成后更新状态 setUpdating((prev) => ({ ...prev, [name]: false })); } catch (err) { @@ -177,161 +163,164 @@ export const ProviderButton = () => { - {Object.entries(proxyProviders || {}).map(([key, item]) => { - const provider = item as ProxyProviderItem; - const time = dayjs(provider.updatedAt); - const isUpdating = updating[key]; + {Object.entries(proxyProviders || {}) + .sort() + .map(([key, item]) => { + const provider = item; + const time = dayjs(provider.updatedAt); + const isUpdating = updating[key]; - // 订阅信息 - const sub = provider.subscriptionInfo; - const hasSubInfo = !!sub; - const upload = sub?.Upload || 0; - const download = sub?.Download || 0; - const total = sub?.Total || 0; - const expire = sub?.Expire || 0; + // 订阅信息 + const sub = provider.subscriptionInfo; + const hasSubInfo = !!sub; + const upload = sub?.Upload || 0; + const download = sub?.Download || 0; + const total = sub?.Total || 0; + const expire = sub?.Expire || 0; - // 流量使用进度 - const progress = - total > 0 - ? Math.min( - Math.round(((download + upload) * 100) / total) + 1, - 100, - ) - : 0; + // 流量使用进度 + const progress = + total > 0 + ? Math.min( + Math.round(((download + upload) * 100) / total) + 1, + 100, + ) + : 0; - return ( - { - const bgcolor = mode === "light" ? "#ffffff" : "#24252f"; - const hoverColor = - mode === "light" - ? alpha(primary.main, 0.1) - : alpha(primary.main, 0.2); + return ( + { + const bgcolor = + mode === "light" ? "#ffffff" : "#24252f"; + const hoverColor = + mode === "light" + ? alpha(primary.main, 0.1) + : alpha(primary.main, 0.2); - return { - backgroundColor: bgcolor, - "&:hover": { - backgroundColor: hoverColor, - }, - }; - }, - ]} - > - - - {key} - - {provider.proxies.length} - - - {provider.vehicleType} - - - - - {t("Update At")}: - {time.fromNow()} - - - } - secondary={ - <> - {/* 订阅信息 */} - {hasSubInfo && ( - <> - - - {parseTraffic(upload + download)} /{" "} - {parseTraffic(total)} - - - {parseExpire(expire)} - - - - {/* 进度条 */} - 0 ? 1 : 0, - }} - /> - - )} - - } - /> - - - { - updateProvider(key); - }} - disabled={isUpdating} + + + {key} + + {provider.proxies.length} + + + {provider.vehicleType} + + + + + {t("Update At")}: + {time.fromNow()} + + + } + secondary={ + <> + {/* 订阅信息 */} + {hasSubInfo && ( + <> + + + {parseTraffic(upload + download)} /{" "} + {parseTraffic(total)} + + + {parseExpire(expire)} + + + + {/* 进度条 */} + 0 ? 1 : 0, + }} + /> + + )} + + } + /> + + - - - - - ); - })} + { + updateProvider(key); + }} + disabled={isUpdating} + sx={{ + animation: isUpdating + ? "spin 1s linear infinite" + : "none", + "@keyframes spin": { + "0%": { transform: "rotate(0deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + }} + title={t("Update Provider") as string} + > + + + + + ); + })} diff --git a/clash-verge-rev/src/components/proxy/proxy-chain.tsx b/clash-verge-rev/src/components/proxy/proxy-chain.tsx index 285a6889c6..651fcbc950 100644 --- a/clash-verge-rev/src/components/proxy/proxy-chain.tsx +++ b/clash-verge-rev/src/components/proxy/proxy-chain.tsx @@ -34,14 +34,13 @@ import { import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import useSWR from "swr"; - -import { useAppData } from "@/providers/app-data-context"; import { closeAllConnections, - getProxies, - updateProxyAndSync, - updateProxyChainConfigInRuntime, -} from "@/services/cmds"; + selectNodeForGroup, +} from "tauri-plugin-mihomo-api"; + +import { useAppData } from "@/providers/app-data-context"; +import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds"; interface ProxyChainItem { id: string; @@ -204,7 +203,7 @@ export const ProxyChain = ({ // 获取当前代理信息以检查连接状态 const { data: currentProxies, mutate: mutateProxies } = useSWR( "getProxies", - getProxies, + calcuProxies, { revalidateOnFocus: true, revalidateIfStale: true, @@ -367,7 +366,7 @@ export const ProxyChain = ({ const targetGroup = mode === "global" ? "GLOBAL" : selectedGroup; - await updateProxyAndSync(targetGroup || "GLOBAL", lastNode.name); + await selectNodeForGroup(targetGroup || "GLOBAL", lastNode.name); localStorage.setItem("proxy-chain-group", targetGroup || "GLOBAL"); localStorage.setItem("proxy-chain-exit-node", lastNode.name); diff --git a/clash-verge-rev/src/components/proxy/proxy-groups.tsx b/clash-verge-rev/src/components/proxy/proxy-groups.tsx index 2622dfb1b2..f3fd255c01 100644 --- a/clash-verge-rev/src/components/proxy/proxy-groups.tsx +++ b/clash-verge-rev/src/components/proxy/proxy-groups.tsx @@ -14,14 +14,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"; import useSWR from "swr"; +import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api"; import { useProxySelection } from "@/hooks/use-proxy-selection"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-context"; import { - getGroupProxyDelays, getRuntimeConfig, - providerHealthCheck, updateProxyChainConfigInRuntime, } from "@/services/cmds"; import delayManager from "@/services/delay"; @@ -153,15 +152,14 @@ export const ProxyGroups = (props: Props) => { // 添加和清理滚动事件监听器 useEffect(() => { - const currentScroller = scrollerRef.current; - if (currentScroller) { - currentScroller.addEventListener("scroll", handleScroll, { - passive: true, - }); - return () => { - currentScroller.removeEventListener("scroll", handleScroll); - }; - } + if (!scrollerRef.current) return; + scrollerRef.current.addEventListener("scroll", handleScroll, { + passive: true, + }); + + return () => { + scrollerRef.current?.removeEventListener("scroll", handleScroll); + }; }, [handleScroll]); // 滚动到顶部 @@ -215,6 +213,7 @@ export const ProxyGroups = (props: Props) => { const currentGroup = getCurrentGroup(); const availableGroups = getAvailableGroups(); + // TODO: 频繁点击切换代理节点,导致应用卡死 const handleChangeProxy = useCallback( (group: IProxyGroupItem, proxy: IProxyItem) => { if (isChainMode) { @@ -273,7 +272,7 @@ export const ProxyGroups = (props: Props) => { if (providers.size) { console.log(`[ProxyGroups] 发现提供者,数量: ${providers.size}`); Promise.allSettled( - [...providers].map((p) => providerHealthCheck(p)), + [...providers].map((p) => healthcheckProxyProvider(p)), ).then(() => { console.log(`[ProxyGroups] 提供者健康检查完成`); onProxies(); @@ -289,7 +288,7 @@ export const ProxyGroups = (props: Props) => { try { await Promise.race([ delayManager.checkListDelay(names, groupName, timeout), - getGroupProxyDelays(groupName, url, timeout).then((result) => { + delayGroup(groupName, url, timeout).then((result) => { console.log( `[ProxyGroups] getGroupProxyDelays返回结果数量:`, Object.keys(result || {}).length, @@ -518,7 +517,7 @@ export const ProxyGroups = (props: Props) => { }, }} > - {availableGroups.map((group: any, index: number) => ( + {availableGroups.map((group: any, _index: number) => ( handleGroupSelect(group.name)} diff --git a/clash-verge-rev/src/components/proxy/proxy-head.tsx b/clash-verge-rev/src/components/proxy/proxy-head.tsx index fd0315450f..f10af411be 100644 --- a/clash-verge-rev/src/components/proxy/proxy-head.tsx +++ b/clash-verge-rev/src/components/proxy/proxy-head.tsx @@ -52,11 +52,13 @@ export const ProxyHead = ({ }, []); const { verge } = useVerge(); - const default_latency_test = verge!.default_latency_test!; + const defaultLatencyUrl = + verge?.default_latency_test?.trim() || + "https://cp.cloudflare.com/generate_204"; useEffect(() => { - delayManager.setUrl(groupName, testUrl || url || default_latency_test); - }, [groupName, testUrl, default_latency_test, url]); + delayManager.setUrl(groupName, testUrl?.trim() || url || defaultLatencyUrl); + }, [groupName, testUrl, defaultLatencyUrl, url]); return ( diff --git a/clash-verge-rev/src/components/proxy/proxy-item-mini.tsx b/clash-verge-rev/src/components/proxy/proxy-item-mini.tsx index aa6e9a6bfb..8e456f2953 100644 --- a/clash-verge-rev/src/components/proxy/proxy-item-mini.tsx +++ b/clash-verge-rev/src/components/proxy/proxy-item-mini.tsx @@ -37,12 +37,12 @@ export const ProxyItemMini = (props: Props) => { return () => { delayManager.removeListener(proxy.name, group.name); }; - }, [proxy.name, group.name]); + }, [isPreset, proxy.name, group.name]); useEffect(() => { if (!proxy) return; setDelay(delayManager.getDelayFix(proxy, group.name)); - }, [proxy]); + }, [proxy, group.name]); const onDelay = useLockFn(async () => { setDelay(-2); @@ -200,7 +200,7 @@ export const ProxyItemMini = (props: Props) => { )} - {delay > 0 && ( + {delay >= 0 && ( // 显示延迟 { {delayManager.formatDelay(delay, timeout)} )} - {delay !== -2 && delay <= 0 && selected && ( + {proxy.type !== "Direct" && delay !== -2 && delay < 0 && selected && ( // 展示已选择的icon (({ theme }) => ({ display: "inline-block", @@ -60,7 +52,7 @@ export const ProviderButton = () => { // 设置更新状态 setUpdating((prev) => ({ ...prev, [name]: true })); - await ruleProviderUpdate(name); + await updateRuleProvider(name); // 刷新数据 await refreshRules(); @@ -101,7 +93,7 @@ export const ProviderButton = () => { // 改为串行逐个更新所有provider for (const name of allProviders) { try { - await ruleProviderUpdate(name); + await updateRuleProvider(name); // 每个更新完成后更新状态 setUpdating((prev) => ({ ...prev, [name]: false })); } catch (err) { @@ -160,112 +152,117 @@ export const ProviderButton = () => { - {Object.entries(ruleProviders || {}).map(([key, item]) => { - const provider = item as RuleProviderItem; - const time = dayjs(provider.updatedAt); - const isUpdating = updating[key]; + {Object.entries(ruleProviders || {}) + .sort() + .map(([key, item]) => { + const provider = item; + const time = dayjs(provider.updatedAt); + const isUpdating = updating[key]; - return ( - { - const bgcolor = mode === "light" ? "#ffffff" : "#24252f"; - const hoverColor = - mode === "light" - ? alpha(primary.main, 0.1) - : alpha(primary.main, 0.2); + return ( + { + const bgcolor = + mode === "light" ? "#ffffff" : "#24252f"; + const hoverColor = + mode === "light" + ? alpha(primary.main, 0.1) + : alpha(primary.main, 0.2); - return { - backgroundColor: bgcolor, - "&:hover": { - backgroundColor: hoverColor, - borderColor: alpha(primary.main, 0.3), - }, - }; - }, - ]} - > - - - {key} - - {provider.ruleCount} - - - - - {t("Update At")}: - {time.fromNow()} - - - } - secondary={ - - - {provider.vehicleType} - - {provider.behavior} - - } - /> - - - updateProvider(key)} - disabled={isUpdating} + + + {key} + + {provider.ruleCount} + + + + + {t("Update At")}: + {time.fromNow()} + + + } + secondary={ + + + {provider.vehicleType} + + + {provider.behavior} + + + } + /> + + - - - - - ); - })} + updateProvider(key)} + disabled={isUpdating} + sx={{ + animation: isUpdating + ? "spin 1s linear infinite" + : "none", + "@keyframes spin": { + "0%": { transform: "rotate(0deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + }} + title={t("Update Provider") as string} + > + + + + + ); + })} diff --git a/clash-verge-rev/src/components/setting/mods/clash-core-viewer.tsx b/clash-verge-rev/src/components/setting/mods/clash-core-viewer.tsx index 373b61fe84..be951de6c3 100644 --- a/clash-verge-rev/src/components/setting/mods/clash-core-viewer.tsx +++ b/clash-verge-rev/src/components/setting/mods/clash-core-viewer.tsx @@ -16,16 +16,11 @@ import type { Ref } from "react"; import { useImperativeHandle, useState } from "react"; import { useTranslation } from "react-i18next"; import { mutate } from "swr"; +import { closeAllConnections, upgradeCore } from "tauri-plugin-mihomo-api"; import { BaseDialog, DialogRef } from "@/components/base"; import { useVerge } from "@/hooks/use-verge"; -import { - changeClashCore, - closeAllConnections, - forceRefreshClashConfig, - restartCore, - upgradeCore, -} from "@/services/cmds"; +import { changeClashCore, restartCore } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; const VALID_CORE = [ @@ -66,8 +61,6 @@ export function ClashCoreViewer({ ref }: { ref?: Ref }) { mutateVerge(); setTimeout(async () => { - // 核心切换后强制刷新配置缓存 - await forceRefreshClashConfig(); mutate("getClashConfig"); mutate("getVersion"); setChangingCore(null); diff --git a/clash-verge-rev/src/components/setting/mods/clash-port-viewer.tsx b/clash-verge-rev/src/components/setting/mods/clash-port-viewer.tsx index ec49580400..5f0db1bae3 100644 --- a/clash-verge-rev/src/components/setting/mods/clash-port-viewer.tsx +++ b/clash-verge-rev/src/components/setting/mods/clash-port-viewer.tsx @@ -20,8 +20,6 @@ import getSystem from "@/utils/get-system"; const OS = getSystem(); -interface ClashPortViewerProps {} - interface ClashPortViewerRef { open: () => void; close: () => void; @@ -30,10 +28,7 @@ interface ClashPortViewerRef { const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025; -export const ClashPortViewer = forwardRef< - ClashPortViewerRef, - ClashPortViewerProps ->((props, ref) => { +export const ClashPortViewer = forwardRef((_, ref) => { const { t } = useTranslation(); const { clashInfo, patchInfo } = useClashInfo(); const { verge, patchVerge } = useVerge(); @@ -74,10 +69,10 @@ export const ClashPortViewer = forwardRef< manual: true, onSuccess: () => { setOpen(false); - showNotice("success", t("Port settings saved")); // 调用提示函数 + showNotice("success", t("Port settings saved")); }, onError: () => { - showNotice("error", t("Failed to save settings")); // 调用提示函数 + showNotice("error", t("Failed to save port settings")); }, }, ); @@ -154,7 +149,7 @@ export const ClashPortViewer = forwardRef< return (
setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5)) } - inputProps={{ style: { fontSize: 12 } }} + slotProps={{ htmlInput: { style: { fontSize: 12 } } }} />
((props, ref) => { const [sysproxyIcon, setSysproxyIcon] = useState(""); const [tunIcon, setTunIcon] = useState(""); + const { decorated, toggleDecorations } = useWindowDecorations(); + useEffect(() => { initIconPath(); }, []); @@ -108,6 +110,21 @@ export const LayoutViewer = forwardRef((props, ref) => { onCancel={() => setOpen(false)} > + + + { + await toggleDecorations(); + }} + > + + + + ((props, ref) => { return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,"; }; - const { data: clashConfig } = useSWR("getClashConfig", getClashConfig, { + const { data: clashConfig } = useSWR("getClashConfig", getBaseConfig, { revalidateOnFocus: false, revalidateIfStale: true, dedupingInterval: 1000, errorRetryInterval: 5000, }); - const [prevMixedPort, setPrevMixedPort] = useState( - clashConfig?.["mixed-port"], - ); + const [prevMixedPort, setPrevMixedPort] = useState(clashConfig?.mixedPort); useEffect(() => { - if ( - clashConfig?.["mixed-port"] && - clashConfig?.["mixed-port"] !== prevMixedPort - ) { - setPrevMixedPort(clashConfig?.["mixed-port"]); + if (clashConfig?.mixedPort && clashConfig.mixedPort !== prevMixedPort) { + setPrevMixedPort(clashConfig.mixedPort); resetSystemProxy(); } - }, [clashConfig?.["mixed-port"]]); + }, [clashConfig?.mixedPort]); const resetSystemProxy = async () => { try { @@ -180,7 +175,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (isPacMode) { const host = value.proxy_host || "127.0.0.1"; - const port = verge?.verge_mixed_port || clashConfig["mixed-port"] || 7897; + const port = verge?.verge_mixed_port || clashConfig.mixedPort || 7897; return `${host}:${port}`; } else { return systemProxyAddress; @@ -332,7 +327,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (pacContent) { pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host); // 将 mixed-port 转换为字符串 - const mixedPortStr = (clashConfig?.["mixed-port"] || "").toString(); + const mixedPortStr = (clashConfig?.mixedPort || "").toString(); pacContent = pacContent.replace(/%mixed-port%/g, mixedPortStr); } diff --git a/clash-verge-rev/src/components/setting/setting-clash.tsx b/clash-verge-rev/src/components/setting/setting-clash.tsx index 481683fa5b..1d2b982fd4 100644 --- a/clash-verge-rev/src/components/setting/setting-clash.tsx +++ b/clash-verge-rev/src/components/setting/setting-clash.tsx @@ -4,14 +4,15 @@ import { invoke } from "@tauri-apps/api/core"; import { useLockFn } from "ahooks"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { updateGeo } from "tauri-plugin-mihomo-api"; import { DialogRef, Switch } from "@/components/base"; import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { useClash } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { invoke_uwp_tool } from "@/services/cmds"; -import { updateGeoData } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; +import { useClashLog } from "@/services/states"; import getSystem from "@/utils/get-system"; import { ClashCoreViewer } from "./mods/clash-core-viewer"; @@ -35,6 +36,7 @@ const SettingClash = ({ onError }: Props) => { const { clash, version, mutateClash, patchClash } = useClash(); const { verge, patchVerge } = useVerge(); + const [, setClashLog] = useClashLog(); const { ipv6, @@ -64,7 +66,7 @@ const SettingClash = ({ onError }: Props) => { }; const onUpdateGeo = async () => { try { - await updateGeoData(); + await updateGeo(); showNotice("success", t("GeoData Updated")); } catch (err: any) { showNotice("error", err?.response.data.message || err.toString()); @@ -186,7 +188,10 @@ const SettingClash = ({ onError }: Props) => { onCatch={onError} onFormat={(e: any) => e.target.value} onChange={(e) => onChangeData({ "log-level": e })} - onGuard={(e) => patchClash({ "log-level": e })} + onGuard={(e) => { + setClashLog((pre: any) => ({ ...pre, logLevel: e })); + return patchClash({ "log-level": e }); + }} >