@ohmykreee/dsh-remote-control 0.0.0-stage → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +556 -0
- package/INSTALLATION.md +310 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/client.js +3 -0
- package/components.json +20 -0
- package/cordis.patch.yml +3 -0
- package/docs/README.en.md +196 -0
- package/docs/screenshots/en/overview.png +0 -0
- package/docs/screenshots/en/question.png +0 -0
- package/docs/screenshots/zh/overview.png +0 -0
- package/docs/screenshots/zh/question.png +0 -0
- package/icon.svg +24 -0
- package/index.js +2 -0
- package/lib/account.js +1 -0
- package/lib/app-mapping.js +1 -0
- package/lib/i18n.js +1 -0
- package/lib/media-enrich.js +1 -0
- package/lib/notifications.js +1 -0
- package/lib/performance.js +1 -0
- package/lib/platform/index.js +1 -0
- package/lib/probe-adapter.js +1 -0
- package/lib/probe-daemon.js +1 -0
- package/lib/probe.js +1 -0
- package/lib/qr.js +1 -0
- package/lib/runtime.js +1 -0
- package/lib/service.js +1 -0
- package/lib/sessions.js +1 -0
- package/lib/settings.js +1 -0
- package/lib/types.js +1 -0
- package/lib/util.js +1 -0
- package/lib/weather.js +1 -0
- package/lib/ws.js +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +118 -4
- package/scripts/browser-shot.mjs +217 -0
- package/scripts/build-dsh.mjs +228 -0
- package/scripts/build-rust.mjs +319 -0
- package/scripts/build-web.mjs +17 -0
- package/scripts/check-npm-package.mjs +322 -0
- package/scripts/mock-preview.py +574 -0
- package/scripts/screenshot-compose.py +89 -0
- package/scripts/screenshot-device.sh +75 -0
- package/src/dsh/client/api.ts +68 -0
- package/src/dsh/client/i18n.ts +573 -0
- package/src/dsh/client/icons.ts +56 -0
- package/src/dsh/client/index.ts +95 -0
- package/src/dsh/client/logic.ts +310 -0
- package/src/dsh/client/panel.ts +483 -0
- package/src/dsh/client/sections/apps.ts +40 -0
- package/src/dsh/client/sections/balance.ts +38 -0
- package/src/dsh/client/sections/connection.ts +97 -0
- package/src/dsh/client/sections/notifications.ts +43 -0
- package/src/dsh/client/sections/pomodoro.ts +184 -0
- package/src/dsh/client/sections/preferences.ts +138 -0
- package/src/dsh/client/sections/system.ts +120 -0
- package/src/dsh/client/sections/weather.ts +276 -0
- package/src/dsh/client/styles.ts +1119 -0
- package/src/dsh/client/types.ts +366 -0
- package/src/dsh/client/ui.ts +142 -0
- package/src/dsh/index.ts +108 -0
- package/src/dsh/lib/account.ts +520 -0
- package/src/dsh/lib/app-mapping.ts +366 -0
- package/src/dsh/lib/i18n.ts +82 -0
- package/src/dsh/lib/media-enrich.ts +202 -0
- package/src/dsh/lib/notifications.ts +322 -0
- package/src/dsh/lib/performance.ts +279 -0
- package/src/dsh/lib/platform/index.ts +43 -0
- package/src/dsh/lib/probe-adapter.ts +275 -0
- package/src/dsh/lib/probe-daemon.ts +359 -0
- package/src/dsh/lib/probe.ts +395 -0
- package/src/dsh/lib/qr.ts +42 -0
- package/src/dsh/lib/runtime.ts +126 -0
- package/src/dsh/lib/service.ts +1645 -0
- package/src/dsh/lib/sessions.ts +907 -0
- package/src/dsh/lib/settings.ts +468 -0
- package/src/dsh/lib/types.ts +231 -0
- package/src/dsh/lib/util.ts +115 -0
- package/src/dsh/lib/weather.ts +532 -0
- package/src/dsh/lib/ws.ts +328 -0
- package/src/dsh/types/qrcode-generator.d.ts +16 -0
- package/src/rust/Cargo.lock +1431 -0
- package/src/rust/Cargo.toml +68 -0
- package/src/rust/src/cli.rs +233 -0
- package/src/rust/src/daemon.rs +140 -0
- package/src/rust/src/location/linux.rs +118 -0
- package/src/rust/src/location/macos.rs +26 -0
- package/src/rust/src/location/mod.rs +170 -0
- package/src/rust/src/location/windows.rs +205 -0
- package/src/rust/src/main.rs +42 -0
- package/src/rust/src/media/catalog.rs +454 -0
- package/src/rust/src/media/linux.rs +350 -0
- package/src/rust/src/media/macos/accessibility.rs +59 -0
- package/src/rust/src/media/macos.rs +366 -0
- package/src/rust/src/media/mod.rs +298 -0
- package/src/rust/src/media/vendor/apple_music.rs +90 -0
- package/src/rust/src/media/vendor/common.rs +186 -0
- package/src/rust/src/media/vendor/kugou.rs +52 -0
- package/src/rust/src/media/vendor/kuwo.rs +49 -0
- package/src/rust/src/media/vendor/mod.rs +75 -0
- package/src/rust/src/media/vendor/netease.rs +1715 -0
- package/src/rust/src/media/vendor/qishui.rs +36 -0
- package/src/rust/src/media/vendor/qqmusic.rs +33 -0
- package/src/rust/src/media/vendor/spotify.rs +46 -0
- package/src/rust/src/media/vendor/wesing.rs +39 -0
- package/src/rust/src/media/vendor/youtube_music.rs +49 -0
- package/src/rust/src/media/windows/accessibility.rs +85 -0
- package/src/rust/src/media/windows/audio.rs +116 -0
- package/src/rust/src/media/windows/private.rs +291 -0
- package/src/rust/src/media/windows.rs +775 -0
- package/src/rust/src/notifications/linux.rs +276 -0
- package/src/rust/src/notifications/macos.rs +382 -0
- package/src/rust/src/notifications/mod.rs +244 -0
- package/src/rust/src/notifications/windows.rs +565 -0
- package/src/rust/src/perf/common.rs +109 -0
- package/src/rust/src/perf/linux.rs +282 -0
- package/src/rust/src/perf/macos.rs +28 -0
- package/src/rust/src/perf/mod.rs +157 -0
- package/src/rust/src/perf/windows.rs +981 -0
- package/src/rust/src/util.rs +360 -0
- package/src/rust/src/volume/linux.rs +195 -0
- package/src/rust/src/volume/macos.rs +54 -0
- package/src/rust/src/volume/mod.rs +116 -0
- package/src/rust/src/volume/windows.rs +75 -0
- package/src/rust/tests/cli.rs +79 -0
- package/src/rust/tests/daemon.rs +107 -0
- package/src/svelte/App.svelte +385 -0
- package/src/svelte/index.html +18 -0
- package/src/svelte/lib/billing.svelte.ts +228 -0
- package/src/svelte/lib/components/BalanceCard.svelte +173 -0
- package/src/svelte/lib/components/ClockCard.svelte +183 -0
- package/src/svelte/lib/components/CountdownCard.svelte +261 -0
- package/src/svelte/lib/components/CpuMemoryCard.svelte +238 -0
- package/src/svelte/lib/components/EmptyState.svelte +34 -0
- package/src/svelte/lib/components/GpuCard.svelte +245 -0
- package/src/svelte/lib/components/HomePage.svelte +34 -0
- package/src/svelte/lib/components/MusicControls.svelte +256 -0
- package/src/svelte/lib/components/MusicCover.svelte +88 -0
- package/src/svelte/lib/components/MusicPage.svelte +116 -0
- package/src/svelte/lib/components/NotificationsCard.svelte +109 -0
- package/src/svelte/lib/components/Panel.svelte +22 -0
- package/src/svelte/lib/components/PerformancePage.svelte +55 -0
- package/src/svelte/lib/components/PomodoroCard.svelte +250 -0
- package/src/svelte/lib/components/QuestionSheet.svelte +354 -0
- package/src/svelte/lib/components/RadialGauge.svelte +99 -0
- package/src/svelte/lib/components/Rail.svelte +217 -0
- package/src/svelte/lib/components/SectionHeader.svelte +40 -0
- package/src/svelte/lib/components/SeekBar.svelte +421 -0
- package/src/svelte/lib/components/SessionList.svelte +364 -0
- package/src/svelte/lib/components/SessionStats.svelte +98 -0
- package/src/svelte/lib/components/SessionsPage.svelte +61 -0
- package/src/svelte/lib/components/SpendCard.svelte +105 -0
- package/src/svelte/lib/components/ThermalCard.svelte +249 -0
- package/src/svelte/lib/components/Toast.svelte +32 -0
- package/src/svelte/lib/components/WeatherIcon.svelte +40 -0
- package/src/svelte/lib/components/WeatherPanel.svelte +420 -0
- package/src/svelte/lib/components/ui/alert/alert-action.svelte +20 -0
- package/src/svelte/lib/components/ui/alert/alert-description.svelte +23 -0
- package/src/svelte/lib/components/ui/alert/alert-title.svelte +23 -0
- package/src/svelte/lib/components/ui/alert/alert.svelte +43 -0
- package/src/svelte/lib/components/ui/alert/index.ts +17 -0
- package/src/svelte/lib/components/ui/avatar/avatar-badge.svelte +26 -0
- package/src/svelte/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
- package/src/svelte/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
- package/src/svelte/lib/components/ui/avatar/avatar-group.svelte +23 -0
- package/src/svelte/lib/components/ui/avatar/avatar-image.svelte +17 -0
- package/src/svelte/lib/components/ui/avatar/avatar.svelte +26 -0
- package/src/svelte/lib/components/ui/avatar/index.ts +22 -0
- package/src/svelte/lib/components/ui/badge/badge.svelte +49 -0
- package/src/svelte/lib/components/ui/badge/index.ts +2 -0
- package/src/svelte/lib/components/ui/button/button.svelte +82 -0
- package/src/svelte/lib/components/ui/button/index.ts +17 -0
- package/src/svelte/lib/components/ui/card/card-action.svelte +23 -0
- package/src/svelte/lib/components/ui/card/card-content.svelte +20 -0
- package/src/svelte/lib/components/ui/card/card-description.svelte +20 -0
- package/src/svelte/lib/components/ui/card/card-footer.svelte +20 -0
- package/src/svelte/lib/components/ui/card/card-header.svelte +23 -0
- package/src/svelte/lib/components/ui/card/card-title.svelte +20 -0
- package/src/svelte/lib/components/ui/card/card.svelte +22 -0
- package/src/svelte/lib/components/ui/card/index.ts +25 -0
- package/src/svelte/lib/components/ui/dialog/dialog-close.svelte +11 -0
- package/src/svelte/lib/components/ui/dialog/dialog-content.svelte +48 -0
- package/src/svelte/lib/components/ui/dialog/dialog-description.svelte +17 -0
- package/src/svelte/lib/components/ui/dialog/dialog-footer.svelte +32 -0
- package/src/svelte/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/svelte/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
- package/src/svelte/lib/components/ui/dialog/dialog-portal.svelte +7 -0
- package/src/svelte/lib/components/ui/dialog/dialog-title.svelte +17 -0
- package/src/svelte/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/src/svelte/lib/components/ui/dialog/dialog.svelte +7 -0
- package/src/svelte/lib/components/ui/dialog/index.ts +34 -0
- package/src/svelte/lib/components/ui/empty/empty-content.svelte +23 -0
- package/src/svelte/lib/components/ui/empty/empty-description.svelte +23 -0
- package/src/svelte/lib/components/ui/empty/empty-header.svelte +20 -0
- package/src/svelte/lib/components/ui/empty/empty-media.svelte +41 -0
- package/src/svelte/lib/components/ui/empty/empty-title.svelte +20 -0
- package/src/svelte/lib/components/ui/empty/empty.svelte +23 -0
- package/src/svelte/lib/components/ui/empty/index.ts +22 -0
- package/src/svelte/lib/components/ui/input/index.ts +7 -0
- package/src/svelte/lib/components/ui/input/input.svelte +48 -0
- package/src/svelte/lib/components/ui/progress/index.ts +7 -0
- package/src/svelte/lib/components/ui/progress/progress.svelte +27 -0
- package/src/svelte/lib/components/ui/scroll-area/index.ts +10 -0
- package/src/svelte/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte +30 -0
- package/src/svelte/lib/components/ui/scroll-area/scroll-area.svelte +43 -0
- package/src/svelte/lib/components/ui/separator/index.ts +7 -0
- package/src/svelte/lib/components/ui/separator/separator.svelte +21 -0
- package/src/svelte/lib/components/ui/sheet/index.ts +34 -0
- package/src/svelte/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/src/svelte/lib/components/ui/sheet/sheet-content.svelte +55 -0
- package/src/svelte/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/src/svelte/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/src/svelte/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/src/svelte/lib/components/ui/sheet/sheet-overlay.svelte +20 -0
- package/src/svelte/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/src/svelte/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/src/svelte/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/src/svelte/lib/components/ui/sheet/sheet.svelte +7 -0
- package/src/svelte/lib/components/ui/skeleton/index.ts +7 -0
- package/src/svelte/lib/components/ui/skeleton/skeleton.svelte +17 -0
- package/src/svelte/lib/components/ui/slider/index.ts +7 -0
- package/src/svelte/lib/components/ui/slider/slider.svelte +52 -0
- package/src/svelte/lib/components/ui/tooltip/index.ts +19 -0
- package/src/svelte/lib/components/ui/tooltip/tooltip-content.svelte +51 -0
- package/src/svelte/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/src/svelte/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/src/svelte/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/src/svelte/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/src/svelte/lib/countdown.svelte.ts +252 -0
- package/src/svelte/lib/format.ts +199 -0
- package/src/svelte/lib/i18n.svelte.ts +583 -0
- package/src/svelte/lib/media.ts +32 -0
- package/src/svelte/lib/pages.ts +18 -0
- package/src/svelte/lib/pomodoro.svelte.ts +223 -0
- package/src/svelte/lib/session-tree.ts +80 -0
- package/src/svelte/lib/state.svelte.ts +359 -0
- package/src/svelte/lib/timer-alarm.svelte.ts +319 -0
- package/src/svelte/lib/types.ts +306 -0
- package/src/svelte/lib/utils.ts +8 -0
- package/src/svelte/lib/weather.ts +84 -0
- package/src/svelte/lib/ws.ts +104 -0
- package/src/svelte/main.ts +13 -0
- package/src/svelte/styles/global.css +315 -0
- package/src/svelte/vite-env.d.ts +12 -0
- package/static/fonts/LICENSE.txt +287 -0
- package/static/icon.svg +24 -0
- package/static/manifest.webmanifest +18 -0
- package/svelte.config.js +5 -0
- package/tsconfig.dsh-client.json +18 -0
- package/tsconfig.dsh.json +21 -0
- package/tsconfig.json +20 -0
- package/vite.config.js +31 -0
- package/web/assets/index-DLdSfnJ3.css +1 -0
- package/web/assets/index-YNE8X0Uy.js +6 -0
- package/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/web/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
- package/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/web/assets/noto-sans-sc-100-wght-normal-DluiXYzi.woff2 +0 -0
- package/web/assets/noto-sans-sc-101-wght-normal-CPkZHnw0.woff2 +0 -0
- package/web/assets/noto-sans-sc-102-wght-normal-gzIulUH6.woff2 +0 -0
- package/web/assets/noto-sans-sc-103-wght-normal-CJfSBxFo.woff2 +0 -0
- package/web/assets/noto-sans-sc-104-wght-normal-B3QMglgE.woff2 +0 -0
- package/web/assets/noto-sans-sc-105-wght-normal-xXh_OMfk.woff2 +0 -0
- package/web/assets/noto-sans-sc-106-wght-normal-B9n3jMjP.woff2 +0 -0
- package/web/assets/noto-sans-sc-107-wght-normal-D7fvVprN.woff2 +0 -0
- package/web/assets/noto-sans-sc-108-wght-normal-J0_LhxVo.woff2 +0 -0
- package/web/assets/noto-sans-sc-109-wght-normal-Dsp-De_0.woff2 +0 -0
- package/web/assets/noto-sans-sc-110-wght-normal-DE_6NUrb.woff2 +0 -0
- package/web/assets/noto-sans-sc-111-wght-normal-C4S-TlTn.woff2 +0 -0
- package/web/assets/noto-sans-sc-112-wght-normal-BcFuP3w6.woff2 +0 -0
- package/web/assets/noto-sans-sc-113-wght-normal-Dk932kmj.woff2 +0 -0
- package/web/assets/noto-sans-sc-114-wght-normal-CO_5Xo-i.woff2 +0 -0
- package/web/assets/noto-sans-sc-115-wght-normal-BWy07H14.woff2 +0 -0
- package/web/assets/noto-sans-sc-116-wght-normal-B4HVAfHo.woff2 +0 -0
- package/web/assets/noto-sans-sc-117-wght-normal-siZzFqgK.woff2 +0 -0
- package/web/assets/noto-sans-sc-118-wght-normal-xhBB7f6V.woff2 +0 -0
- package/web/assets/noto-sans-sc-119-wght-normal-Ck_g2FGz.woff2 +0 -0
- package/web/assets/noto-sans-sc-21-wght-normal-D0x9-yBt.woff2 +0 -0
- package/web/assets/noto-sans-sc-22-wght-normal-BBUVTShg.woff2 +0 -0
- package/web/assets/noto-sans-sc-23-wght-normal-Du1oYWkE.woff2 +0 -0
- package/web/assets/noto-sans-sc-24-wght-normal-CmWjJ0gt.woff2 +0 -0
- package/web/assets/noto-sans-sc-25-wght-normal-BFHiqilg.woff2 +0 -0
- package/web/assets/noto-sans-sc-26-wght-normal-C0sF2Z2f.woff2 +0 -0
- package/web/assets/noto-sans-sc-27-wght-normal-COFdrquJ.woff2 +0 -0
- package/web/assets/noto-sans-sc-28-wght-normal-DMgdNu1o.woff2 +0 -0
- package/web/assets/noto-sans-sc-29-wght-normal-Cmq8em30.woff2 +0 -0
- package/web/assets/noto-sans-sc-30-wght-normal-CZWqZ3yU.woff2 +0 -0
- package/web/assets/noto-sans-sc-31-wght-normal-Wfi_lutW.woff2 +0 -0
- package/web/assets/noto-sans-sc-32-wght-normal-BRJQ4PY5.woff2 +0 -0
- package/web/assets/noto-sans-sc-33-wght-normal-CUVQXNAJ.woff2 +0 -0
- package/web/assets/noto-sans-sc-34-wght-normal-Bspba0wC.woff2 +0 -0
- package/web/assets/noto-sans-sc-35-wght-normal-K8fX_YPG.woff2 +0 -0
- package/web/assets/noto-sans-sc-36-wght-normal-CkITyjoT.woff2 +0 -0
- package/web/assets/noto-sans-sc-37-wght-normal-WCZ2zfEe.woff2 +0 -0
- package/web/assets/noto-sans-sc-38-wght-normal-DeM7CUce.woff2 +0 -0
- package/web/assets/noto-sans-sc-39-wght-normal-BMZNLWQU.woff2 +0 -0
- package/web/assets/noto-sans-sc-4-wght-normal-AR5bdDjz.woff2 +0 -0
- package/web/assets/noto-sans-sc-40-wght-normal-BQ5xpS5j.woff2 +0 -0
- package/web/assets/noto-sans-sc-41-wght-normal-Hthm7FFi.woff2 +0 -0
- package/web/assets/noto-sans-sc-42-wght-normal-eaS2gKds.woff2 +0 -0
- package/web/assets/noto-sans-sc-43-wght-normal-o-T53XXV.woff2 +0 -0
- package/web/assets/noto-sans-sc-44-wght-normal-BJH5J4Ez.woff2 +0 -0
- package/web/assets/noto-sans-sc-45-wght-normal-BKuxSCOJ.woff2 +0 -0
- package/web/assets/noto-sans-sc-46-wght-normal-BR_5Mu6e.woff2 +0 -0
- package/web/assets/noto-sans-sc-47-wght-normal-CyY7gKNX.woff2 +0 -0
- package/web/assets/noto-sans-sc-48-wght-normal-BNMfTR6k.woff2 +0 -0
- package/web/assets/noto-sans-sc-49-wght-normal-DX-0y7wg.woff2 +0 -0
- package/web/assets/noto-sans-sc-5-wght-normal-CfWugllB.woff2 +0 -0
- package/web/assets/noto-sans-sc-50-wght-normal-DAOGW-Dr.woff2 +0 -0
- package/web/assets/noto-sans-sc-51-wght-normal-BlFLOjcP.woff2 +0 -0
- package/web/assets/noto-sans-sc-52-wght-normal-OZjrybnw.woff2 +0 -0
- package/web/assets/noto-sans-sc-53-wght-normal-CwyWNT6x.woff2 +0 -0
- package/web/assets/noto-sans-sc-54-wght-normal-DM0nRe18.woff2 +0 -0
- package/web/assets/noto-sans-sc-55-wght-normal-DBK2wUOR.woff2 +0 -0
- package/web/assets/noto-sans-sc-56-wght-normal-C-VKK1LS.woff2 +0 -0
- package/web/assets/noto-sans-sc-57-wght-normal-Cdbfh5kq.woff2 +0 -0
- package/web/assets/noto-sans-sc-58-wght-normal-CflMKARZ.woff2 +0 -0
- package/web/assets/noto-sans-sc-59-wght-normal-BS4Kwzxx.woff2 +0 -0
- package/web/assets/noto-sans-sc-6-wght-normal-ChnZIQfQ.woff2 +0 -0
- package/web/assets/noto-sans-sc-60-wght-normal-B7QOYsXJ.woff2 +0 -0
- package/web/assets/noto-sans-sc-61-wght-normal-C8JjQS7w.woff2 +0 -0
- package/web/assets/noto-sans-sc-62-wght-normal-bS4wq1Gg.woff2 +0 -0
- package/web/assets/noto-sans-sc-63-wght-normal-BJr8P-OB.woff2 +0 -0
- package/web/assets/noto-sans-sc-64-wght-normal-DVB11t7j.woff2 +0 -0
- package/web/assets/noto-sans-sc-65-wght-normal-DMPCXBvK.woff2 +0 -0
- package/web/assets/noto-sans-sc-66-wght-normal-BobaG10V.woff2 +0 -0
- package/web/assets/noto-sans-sc-67-wght-normal-Dzk7WhCR.woff2 +0 -0
- package/web/assets/noto-sans-sc-68-wght-normal-DGbCD9xu.woff2 +0 -0
- package/web/assets/noto-sans-sc-69-wght-normal-Ctrv1EqA.woff2 +0 -0
- package/web/assets/noto-sans-sc-70-wght-normal-RgTO941D.woff2 +0 -0
- package/web/assets/noto-sans-sc-71-wght-normal-D9sG-CWv.woff2 +0 -0
- package/web/assets/noto-sans-sc-72-wght-normal-C4oWy3ul.woff2 +0 -0
- package/web/assets/noto-sans-sc-73-wght-normal-B1ZoOTgM.woff2 +0 -0
- package/web/assets/noto-sans-sc-74-wght-normal-F7dH_rZJ.woff2 +0 -0
- package/web/assets/noto-sans-sc-75-wght-normal-Cp2wp4si.woff2 +0 -0
- package/web/assets/noto-sans-sc-76-wght-normal-B494D7xS.woff2 +0 -0
- package/web/assets/noto-sans-sc-77-wght-normal-B3MVY9Ax.woff2 +0 -0
- package/web/assets/noto-sans-sc-78-wght-normal-5jX7iNMC.woff2 +0 -0
- package/web/assets/noto-sans-sc-79-wght-normal-BIoZy8fr.woff2 +0 -0
- package/web/assets/noto-sans-sc-80-wght-normal-Bje1Fxy1.woff2 +0 -0
- package/web/assets/noto-sans-sc-81-wght-normal-B2NN7Qi3.woff2 +0 -0
- package/web/assets/noto-sans-sc-82-wght-normal-CUggNZi4.woff2 +0 -0
- package/web/assets/noto-sans-sc-83-wght-normal-99rD8YO-.woff2 +0 -0
- package/web/assets/noto-sans-sc-84-wght-normal-zMBmUocm.woff2 +0 -0
- package/web/assets/noto-sans-sc-85-wght-normal-CjI3qdxG.woff2 +0 -0
- package/web/assets/noto-sans-sc-86-wght-normal-BHY8TVEH.woff2 +0 -0
- package/web/assets/noto-sans-sc-87-wght-normal-DioZSHnU.woff2 +0 -0
- package/web/assets/noto-sans-sc-88-wght-normal-BXYxwf2t.woff2 +0 -0
- package/web/assets/noto-sans-sc-89-wght-normal-ClMFrFaL.woff2 +0 -0
- package/web/assets/noto-sans-sc-90-wght-normal-BnOlPreP.woff2 +0 -0
- package/web/assets/noto-sans-sc-91-wght-normal-DTr9RQcS.woff2 +0 -0
- package/web/assets/noto-sans-sc-97-wght-normal-9u6Aiav-.woff2 +0 -0
- package/web/assets/noto-sans-sc-98-wght-normal-BR-H2C0V.woff2 +0 -0
- package/web/assets/noto-sans-sc-99-wght-normal-BI6XPoxp.woff2 +0 -0
- package/web/assets/noto-sans-sc-cyrillic-wght-normal-BGmsQlw-.woff2 +0 -0
- package/web/assets/noto-sans-sc-latin-ext-wght-normal-DuJTpTg_.woff2 +0 -0
- package/web/assets/noto-sans-sc-latin-wght-normal-DDW8mKAR.woff2 +0 -0
- package/web/assets/noto-sans-sc-vietnamese-wght-normal-Bi3ATJ76.woff2 +0 -0
- package/web/fonts/LICENSE.txt +287 -0
- package/web/icon.svg +24 -0
- package/web/index.html +19 -0
- package/web/manifest.webmanifest +18 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "dsh-probe"
|
|
3
|
+
version = "1.0.3"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
rust-version = "1.89"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
description = "Native OS probe sidecar for the DSH LAN console (media, volume, performance)"
|
|
8
|
+
publish = false
|
|
9
|
+
|
|
10
|
+
[[bin]]
|
|
11
|
+
name = "dsh-probe"
|
|
12
|
+
path = "src/main.rs"
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
base64 = "0.22"
|
|
16
|
+
plist = "1.10"
|
|
17
|
+
rusqlite = { version = "0.42", features = ["bundled"] }
|
|
18
|
+
serde = { version = "1", features = ["derive"] }
|
|
19
|
+
serde_json = "1"
|
|
20
|
+
sysinfo = "0.33"
|
|
21
|
+
|
|
22
|
+
[target.'cfg(windows)'.dependencies]
|
|
23
|
+
windows = { version = "0.62", features = [
|
|
24
|
+
"Devices_Geolocation",
|
|
25
|
+
"Foundation",
|
|
26
|
+
"Media_Control",
|
|
27
|
+
"Storage_Streams",
|
|
28
|
+
"Wdk_Graphics_Direct3D",
|
|
29
|
+
"Win32_Foundation",
|
|
30
|
+
"Win32_Graphics_Dxgi",
|
|
31
|
+
"Win32_Media_Audio",
|
|
32
|
+
"Win32_Media_Audio_Endpoints",
|
|
33
|
+
"Win32_Security",
|
|
34
|
+
"Win32_Storage_FileSystem",
|
|
35
|
+
"Win32_System_Com",
|
|
36
|
+
"Win32_System_Com_StructuredStorage",
|
|
37
|
+
"Win32_System_IO",
|
|
38
|
+
"Win32_System_Ole",
|
|
39
|
+
"Win32_System_Performance",
|
|
40
|
+
"Win32_System_Power",
|
|
41
|
+
"Win32_System_Threading",
|
|
42
|
+
"Win32_System_Variant",
|
|
43
|
+
"Win32_UI_Accessibility",
|
|
44
|
+
"Win32_UI_Shell",
|
|
45
|
+
"Win32_UI_WindowsAndMessaging",
|
|
46
|
+
] }
|
|
47
|
+
windows-future = "0.3"
|
|
48
|
+
winreg = "0.56"
|
|
49
|
+
|
|
50
|
+
[target.'cfg(target_os = "linux")'.dependencies]
|
|
51
|
+
zbus = "5"
|
|
52
|
+
|
|
53
|
+
[features]
|
|
54
|
+
# Compile the macOS modules on any host so their syntax and types can be
|
|
55
|
+
# checked without a Mac. This does not make them tested; see README.
|
|
56
|
+
macos-check = []
|
|
57
|
+
|
|
58
|
+
[profile.release]
|
|
59
|
+
opt-level = "s"
|
|
60
|
+
lto = true
|
|
61
|
+
codegen-units = 1
|
|
62
|
+
strip = true
|
|
63
|
+
|
|
64
|
+
# libsqlite3-sys builds the bundled SQLite C source. MSVC's `/Z7` can fail with
|
|
65
|
+
# C compiler error D8050 on some Build Tools installations; the crate itself does
|
|
66
|
+
# not need debug info for our tests.
|
|
67
|
+
[profile.dev.package.libsqlite3-sys]
|
|
68
|
+
debug = false
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
//! Command-line parsing and the JSON envelope shared by every subcommand.
|
|
2
|
+
|
|
3
|
+
use serde_json::{json, Value};
|
|
4
|
+
|
|
5
|
+
use crate::location;
|
|
6
|
+
use crate::media;
|
|
7
|
+
use crate::notifications;
|
|
8
|
+
use crate::perf;
|
|
9
|
+
use crate::util;
|
|
10
|
+
use crate::volume;
|
|
11
|
+
|
|
12
|
+
/// A command failure. Probe failures still print JSON and exit 0 so the Node
|
|
13
|
+
/// bridge can surface a useful error and fall back; usage errors exit 2.
|
|
14
|
+
pub struct CliError {
|
|
15
|
+
pub message: String,
|
|
16
|
+
pub usage: bool,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl CliError {
|
|
20
|
+
pub fn usage(message: impl Into<String>) -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
message: message.into(),
|
|
23
|
+
usage: true,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn into_json(self, command: Option<&str>, started_at: i64) -> Value {
|
|
28
|
+
json!({
|
|
29
|
+
"ok": false,
|
|
30
|
+
"available": false,
|
|
31
|
+
"error": self.message,
|
|
32
|
+
"command": command,
|
|
33
|
+
"usageError": self.usage,
|
|
34
|
+
"sampledAt": started_at,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub type CliResult = Result<Value, CliError>;
|
|
40
|
+
|
|
41
|
+
const HELP: &str = "dsh-probe <command> [options]
|
|
42
|
+
|
|
43
|
+
Commands:
|
|
44
|
+
version
|
|
45
|
+
media read [--app-id ID] [--player DEST] [--no-thumbnail]
|
|
46
|
+
media playpause|next|previous|seek [--position SECONDS] [--app-id ID] [--player DEST]
|
|
47
|
+
media like [--value true|false] [--app-id ID] [--player DEST]
|
|
48
|
+
media cdp # NetEase CDP diagnostics
|
|
49
|
+
volume read
|
|
50
|
+
volume set --value 0.0..1.0
|
|
51
|
+
volume toggle
|
|
52
|
+
performance
|
|
53
|
+
notifications [--limit 1..20]
|
|
54
|
+
notifications watch [--limit 1..20]
|
|
55
|
+
location
|
|
56
|
+
location request [--open-settings]
|
|
57
|
+
|
|
58
|
+
Every command prints one line of JSON on stdout. Probe errors are reported as
|
|
59
|
+
{\"ok\":false,...\"} with exit code 0; malformed invocations exit with code 2.
|
|
60
|
+
`notifications watch` streams one JSON line per update and runs until killed.";
|
|
61
|
+
|
|
62
|
+
pub fn dispatch(args: &[String]) -> (Value, i32) {
|
|
63
|
+
let started_at = util::now_ms();
|
|
64
|
+
let command = args.first().map(String::as_str);
|
|
65
|
+
let result = match command {
|
|
66
|
+
Some("version") | Some("--version") | Some("-V") => Ok(version_json()),
|
|
67
|
+
Some("help") | Some("--help") | Some("-h") | None => Ok(help_json()),
|
|
68
|
+
Some("media") => media::cli(&args[1..], started_at),
|
|
69
|
+
Some("volume") => volume::cli(&args[1..], started_at),
|
|
70
|
+
Some("performance") | Some("perf") => perf::cli(&args[1..], started_at),
|
|
71
|
+
Some("notifications") | Some("notification") => notifications::cli(&args[1..], started_at),
|
|
72
|
+
Some("location") => location::cli(&args[1..], started_at),
|
|
73
|
+
Some(other) => Err(CliError::usage(format!("unknown command: {other}"))),
|
|
74
|
+
};
|
|
75
|
+
match result {
|
|
76
|
+
Ok(value) => (value, 0),
|
|
77
|
+
Err(error) => {
|
|
78
|
+
let usage = error.usage;
|
|
79
|
+
let value = error.into_json(command, started_at);
|
|
80
|
+
(value, if usage { 2 } else { 0 })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub fn version_json() -> Value {
|
|
86
|
+
json!({
|
|
87
|
+
"ok": true,
|
|
88
|
+
"name": "dsh-probe",
|
|
89
|
+
"version": env!("CARGO_PKG_VERSION"),
|
|
90
|
+
"os": std::env::consts::OS,
|
|
91
|
+
"arch": std::env::consts::ARCH,
|
|
92
|
+
"media": platform_media_backend(),
|
|
93
|
+
"volume": platform_volume_backend(),
|
|
94
|
+
"performance": platform_perf_backend(),
|
|
95
|
+
"notifications": platform_notification_backend(),
|
|
96
|
+
"location": platform_location_backend(),
|
|
97
|
+
"sampledAt": util::now_ms(),
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn help_json() -> Value {
|
|
102
|
+
json!({ "ok": true, "help": HELP, "version": env!("CARGO_PKG_VERSION") })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fn platform_media_backend() -> &'static str {
|
|
106
|
+
#[cfg(windows)]
|
|
107
|
+
{
|
|
108
|
+
"windows-smtc"
|
|
109
|
+
}
|
|
110
|
+
#[cfg(target_os = "linux")]
|
|
111
|
+
{
|
|
112
|
+
"linux-mpris"
|
|
113
|
+
}
|
|
114
|
+
#[cfg(target_os = "macos")]
|
|
115
|
+
{
|
|
116
|
+
"macos-media"
|
|
117
|
+
}
|
|
118
|
+
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
|
119
|
+
{
|
|
120
|
+
"unsupported"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn platform_volume_backend() -> &'static str {
|
|
125
|
+
#[cfg(windows)]
|
|
126
|
+
{
|
|
127
|
+
"windows-coreaudio"
|
|
128
|
+
}
|
|
129
|
+
#[cfg(target_os = "linux")]
|
|
130
|
+
{
|
|
131
|
+
"linux-wpctl-pactl-amixer"
|
|
132
|
+
}
|
|
133
|
+
#[cfg(target_os = "macos")]
|
|
134
|
+
{
|
|
135
|
+
"macos-osascript"
|
|
136
|
+
}
|
|
137
|
+
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
|
138
|
+
{
|
|
139
|
+
"unsupported"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub fn platform_perf_backend() -> &'static str {
|
|
144
|
+
#[cfg(windows)]
|
|
145
|
+
{
|
|
146
|
+
"windows-dxgi-pdh-d3dkmt"
|
|
147
|
+
}
|
|
148
|
+
#[cfg(target_os = "linux")]
|
|
149
|
+
{
|
|
150
|
+
"linux-sysinfo-sysfs"
|
|
151
|
+
}
|
|
152
|
+
#[cfg(target_os = "macos")]
|
|
153
|
+
{
|
|
154
|
+
"macos-sysinfo"
|
|
155
|
+
}
|
|
156
|
+
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
|
157
|
+
{
|
|
158
|
+
"unsupported"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
pub fn platform_notification_backend() -> &'static str {
|
|
163
|
+
#[cfg(windows)]
|
|
164
|
+
{
|
|
165
|
+
"windows-notification-database"
|
|
166
|
+
}
|
|
167
|
+
#[cfg(target_os = "linux")]
|
|
168
|
+
{
|
|
169
|
+
"linux-dbus-notifications"
|
|
170
|
+
}
|
|
171
|
+
#[cfg(target_os = "macos")]
|
|
172
|
+
{
|
|
173
|
+
"macos-notification-center"
|
|
174
|
+
}
|
|
175
|
+
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
|
176
|
+
{
|
|
177
|
+
"unsupported"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
pub fn platform_location_backend() -> &'static str {
|
|
182
|
+
#[cfg(windows)]
|
|
183
|
+
{
|
|
184
|
+
"windows-geolocation"
|
|
185
|
+
}
|
|
186
|
+
#[cfg(target_os = "linux")]
|
|
187
|
+
{
|
|
188
|
+
"linux-geoclue2"
|
|
189
|
+
}
|
|
190
|
+
#[cfg(target_os = "macos")]
|
|
191
|
+
{
|
|
192
|
+
"macos-corelocation-unavailable"
|
|
193
|
+
}
|
|
194
|
+
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
|
195
|
+
{
|
|
196
|
+
"unsupported"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Read `--name value` (or `--name=value`) from an argument list.
|
|
201
|
+
pub fn option_value(args: &[String], name: &str) -> Option<String> {
|
|
202
|
+
let mut index = 0;
|
|
203
|
+
while index < args.len() {
|
|
204
|
+
let arg = &args[index];
|
|
205
|
+
if arg == name {
|
|
206
|
+
return args.get(index + 1).cloned();
|
|
207
|
+
}
|
|
208
|
+
if let Some(value) = arg.strip_prefix(&format!("{name}=")) {
|
|
209
|
+
return Some(value.to_string());
|
|
210
|
+
}
|
|
211
|
+
index += 1;
|
|
212
|
+
}
|
|
213
|
+
None
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Parse an option as a finite number, ignoring malformed values.
|
|
217
|
+
pub fn option_number(args: &[String], name: &str) -> Option<f64> {
|
|
218
|
+
option_value(args, name)
|
|
219
|
+
.and_then(|value| value.trim().parse::<f64>().ok())
|
|
220
|
+
.filter(|value| value.is_finite())
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
pub fn has_flag(args: &[String], name: &str) -> bool {
|
|
224
|
+
args.iter().any(|arg| arg == name)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// The action word for `media`/`volume`, defaulting to `read`.
|
|
228
|
+
pub fn action_of(args: &[String]) -> String {
|
|
229
|
+
args.first()
|
|
230
|
+
.filter(|value| !value.starts_with("--"))
|
|
231
|
+
.map(|value| value.to_ascii_lowercase())
|
|
232
|
+
.unwrap_or_else(|| "read".to_string())
|
|
233
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
//! Persistent probe mode: newline-delimited JSON over stdin/stdout.
|
|
2
|
+
//!
|
|
3
|
+
//! The host starts `dsh-probe daemon` once and keeps it alive, sending one
|
|
4
|
+
//! request per line instead of paying process startup plus platform sampler
|
|
5
|
+
//! warm-up on every poll:
|
|
6
|
+
//!
|
|
7
|
+
//! ```text
|
|
8
|
+
//! -> {"id":1,"argv":["performance"]}
|
|
9
|
+
//! <- {"id":1,"ok":true,"result":{...}}
|
|
10
|
+
//! ```
|
|
11
|
+
//!
|
|
12
|
+
//! The first line on stdout is a `ready` event. Requests are handled in order
|
|
13
|
+
//! on the same thread so platform sampler caches (sysinfo, PDH handles, D-Bus
|
|
14
|
+
//! connections) stay warm. A malformed request is rejected without exiting;
|
|
15
|
+
//! the host closes stdin to stop the daemon. The daemon never opens a socket:
|
|
16
|
+
//! all traffic stays on the inherited stdio pipes.
|
|
17
|
+
|
|
18
|
+
use std::io::{self, BufRead, Write};
|
|
19
|
+
|
|
20
|
+
use serde_json::{json, Value};
|
|
21
|
+
|
|
22
|
+
use crate::cli;
|
|
23
|
+
|
|
24
|
+
/// Bumped only when the request/response shape changes incompatibly.
|
|
25
|
+
pub const PROTOCOL_VERSION: u32 = 1;
|
|
26
|
+
|
|
27
|
+
/// Run the daemon loop until stdin reaches EOF or a pipe error occurs.
|
|
28
|
+
pub fn run() -> i32 {
|
|
29
|
+
let stdout = io::stdout();
|
|
30
|
+
let mut output = stdout.lock();
|
|
31
|
+
let ready = json!({
|
|
32
|
+
"event": "ready",
|
|
33
|
+
"ok": true,
|
|
34
|
+
"protocol": PROTOCOL_VERSION,
|
|
35
|
+
"version": env!("CARGO_PKG_VERSION"),
|
|
36
|
+
"pid": std::process::id(),
|
|
37
|
+
});
|
|
38
|
+
if write_message(&mut output, &ready).is_err() {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let stdin = io::stdin();
|
|
43
|
+
let mut reader = stdin.lock();
|
|
44
|
+
let mut line = String::new();
|
|
45
|
+
loop {
|
|
46
|
+
line.clear();
|
|
47
|
+
match reader.read_line(&mut line) {
|
|
48
|
+
Ok(0) => break,
|
|
49
|
+
Ok(_) => {}
|
|
50
|
+
Err(_) => break,
|
|
51
|
+
}
|
|
52
|
+
let request = line.trim();
|
|
53
|
+
if request.is_empty() {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
let response = handle_request(request);
|
|
57
|
+
if write_message(&mut output, &response).is_err() {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn write_message(output: &mut impl Write, value: &Value) -> io::Result<()> {
|
|
65
|
+
serde_json::to_writer(&mut *output, value)?;
|
|
66
|
+
output.write_all(b"\n")?;
|
|
67
|
+
output.flush()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn error_response(id: Value, message: impl Into<String>) -> Value {
|
|
71
|
+
json!({ "id": id, "ok": false, "error": message.into() })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn handle_request(line: &str) -> Value {
|
|
75
|
+
let request: Value = match serde_json::from_str(line) {
|
|
76
|
+
Ok(value) => value,
|
|
77
|
+
Err(error) => {
|
|
78
|
+
return error_response(Value::Null, format!("invalid request JSON: {error}"));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
let id = request.get("id").cloned().unwrap_or(Value::Null);
|
|
82
|
+
let Some(argv) = request.get("argv").and_then(Value::as_array) else {
|
|
83
|
+
return error_response(id, "request.argv must be an array of strings");
|
|
84
|
+
};
|
|
85
|
+
let mut args = Vec::with_capacity(argv.len());
|
|
86
|
+
for item in argv {
|
|
87
|
+
match item.as_str() {
|
|
88
|
+
Some(value) => args.push(value.to_string()),
|
|
89
|
+
None => return error_response(id, "request.argv entries must be strings"),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if args.is_empty() {
|
|
93
|
+
return error_response(id, "request.argv must not be empty");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// `dispatch` already turns probe failures and usage errors into a JSON
|
|
97
|
+
// payload; keep that payload nested under `result` so the host sees exactly
|
|
98
|
+
// what one-shot mode would have printed.
|
|
99
|
+
let (result, _exit_code) = cli::dispatch(&args);
|
|
100
|
+
json!({ "id": id, "ok": true, "result": result })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[cfg(test)]
|
|
104
|
+
mod tests {
|
|
105
|
+
use super::*;
|
|
106
|
+
|
|
107
|
+
#[test]
|
|
108
|
+
fn version_request_returns_a_result_payload() {
|
|
109
|
+
let response = handle_request(r#"{"id":7,"argv":["version"]}"#);
|
|
110
|
+
assert_eq!(response["id"], 7);
|
|
111
|
+
assert_eq!(response["ok"], true);
|
|
112
|
+
assert_eq!(response["result"]["name"], "dsh-probe");
|
|
113
|
+
assert_eq!(response["result"]["ok"], true);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[test]
|
|
117
|
+
fn malformed_json_is_rejected_without_panicking() {
|
|
118
|
+
let response = handle_request("{");
|
|
119
|
+
assert_eq!(response["ok"], false);
|
|
120
|
+
assert!(response["error"]
|
|
121
|
+
.as_str()
|
|
122
|
+
.unwrap()
|
|
123
|
+
.contains("invalid request"));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[test]
|
|
127
|
+
fn non_string_argv_is_rejected() {
|
|
128
|
+
let response = handle_request(r#"{"id":1,"argv":["performance",3]}"#);
|
|
129
|
+
assert_eq!(response["ok"], false);
|
|
130
|
+
assert_eq!(response["id"], 1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn usage_errors_stay_inside_the_result() {
|
|
135
|
+
let response = handle_request(r#"{"id":"x","argv":["not-a-command"]}"#);
|
|
136
|
+
assert_eq!(response["ok"], true);
|
|
137
|
+
assert_eq!(response["result"]["ok"], false);
|
|
138
|
+
assert_eq!(response["result"]["usageError"], true);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//! Linux location: GeoClue2 over D-Bus.
|
|
2
|
+
//!
|
|
3
|
+
//! GeoClue is the system location service used by GNOME/KDE. A client asks the
|
|
4
|
+
//! manager for a client object, sets `DesktopId`, calls `Start`, then reads the
|
|
5
|
+
//! `Location` object. Authorization is controlled by the desktop/GeoClue
|
|
6
|
+
//! policy; failures are returned so Node can fall back to IP geolocation.
|
|
7
|
+
|
|
8
|
+
use std::time::Duration;
|
|
9
|
+
|
|
10
|
+
use zbus::blocking::{Connection, Proxy};
|
|
11
|
+
use zbus::zvariant::OwnedObjectPath;
|
|
12
|
+
|
|
13
|
+
use super::{AccessReport, Reading};
|
|
14
|
+
|
|
15
|
+
const GEOCLUE_SERVICE: &str = "org.freedesktop.GeoClue2";
|
|
16
|
+
const GEOCLUE_MANAGER_PATH: &str = "/org/freedesktop/GeoClue2/Manager";
|
|
17
|
+
const GEOCLUE_MANAGER_INTERFACE: &str = "org.freedesktop.GeoClue2.Manager";
|
|
18
|
+
const GEOCLUE_CLIENT_INTERFACE: &str = "org.freedesktop.GeoClue2.Client";
|
|
19
|
+
const GEOCLUE_LOCATION_INTERFACE: &str = "org.freedesktop.GeoClue2.Location";
|
|
20
|
+
|
|
21
|
+
fn failure(error: impl std::fmt::Display) -> String {
|
|
22
|
+
format!("GeoClue: {error}")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub fn request_access(_open_settings: bool) -> AccessReport {
|
|
26
|
+
match read() {
|
|
27
|
+
Ok(_) => AccessReport {
|
|
28
|
+
access: "allowed",
|
|
29
|
+
low_integrity: false,
|
|
30
|
+
opened_settings: false,
|
|
31
|
+
reason: None,
|
|
32
|
+
},
|
|
33
|
+
Err(_) => AccessReport {
|
|
34
|
+
access: "denied",
|
|
35
|
+
low_integrity: false,
|
|
36
|
+
opened_settings: false,
|
|
37
|
+
reason: Some("unavailable"),
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn read() -> Result<Reading, String> {
|
|
43
|
+
let connection = Connection::system()
|
|
44
|
+
.map_err(|error| format!("system D-Bus unavailable for GeoClue: {error}"))?;
|
|
45
|
+
let manager = Proxy::new(
|
|
46
|
+
&connection,
|
|
47
|
+
GEOCLUE_SERVICE,
|
|
48
|
+
GEOCLUE_MANAGER_PATH,
|
|
49
|
+
GEOCLUE_MANAGER_INTERFACE,
|
|
50
|
+
)
|
|
51
|
+
.map_err(failure)?;
|
|
52
|
+
let client_path: OwnedObjectPath = manager.call("CreateClient", &()).map_err(failure)?;
|
|
53
|
+
let client = Proxy::new(
|
|
54
|
+
&connection,
|
|
55
|
+
GEOCLUE_SERVICE,
|
|
56
|
+
client_path.as_str(),
|
|
57
|
+
GEOCLUE_CLIENT_INTERFACE,
|
|
58
|
+
)
|
|
59
|
+
.map_err(failure)?;
|
|
60
|
+
client
|
|
61
|
+
.set_property("DesktopId", "dsh-remote-control")
|
|
62
|
+
.map_err(failure)?;
|
|
63
|
+
let _: () = client.call("Start", &()).map_err(failure)?;
|
|
64
|
+
|
|
65
|
+
let mut location_path: OwnedObjectPath = client.get_property("Location").map_err(failure)?;
|
|
66
|
+
// Start() is asynchronous; wait briefly for GeoClue to resolve a location.
|
|
67
|
+
for _ in 0..50 {
|
|
68
|
+
if location_path.as_str() != "/" {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
std::thread::sleep(Duration::from_millis(100));
|
|
72
|
+
location_path = client.get_property("Location").map_err(failure)?;
|
|
73
|
+
}
|
|
74
|
+
let result = if location_path.as_str() == "/" {
|
|
75
|
+
Err("GeoClue did not return a location".to_string())
|
|
76
|
+
} else {
|
|
77
|
+
let location = Proxy::new(
|
|
78
|
+
&connection,
|
|
79
|
+
GEOCLUE_SERVICE,
|
|
80
|
+
location_path.as_str(),
|
|
81
|
+
GEOCLUE_LOCATION_INTERFACE,
|
|
82
|
+
)
|
|
83
|
+
.map_err(failure)?;
|
|
84
|
+
let latitude: f64 = location.get_property("Latitude").map_err(failure)?;
|
|
85
|
+
let longitude: f64 = location.get_property("Longitude").map_err(failure)?;
|
|
86
|
+
let accuracy: f64 = location
|
|
87
|
+
.get_property("Accuracy")
|
|
88
|
+
.map_err(failure)
|
|
89
|
+
.unwrap_or(0.0);
|
|
90
|
+
if !latitude.is_finite()
|
|
91
|
+
|| !longitude.is_finite()
|
|
92
|
+
|| latitude.abs() > 90.0
|
|
93
|
+
|| longitude.abs() > 180.0
|
|
94
|
+
{
|
|
95
|
+
Err("GeoClue returned an invalid coordinate".to_string())
|
|
96
|
+
} else {
|
|
97
|
+
Ok(Reading {
|
|
98
|
+
latitude,
|
|
99
|
+
longitude,
|
|
100
|
+
accuracy: if accuracy > 0.0 { Some(accuracy) } else { None },
|
|
101
|
+
source: "linux-geoclue2",
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
let _: () = client.call("Stop", &()).unwrap_or(());
|
|
106
|
+
result
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[cfg(test)]
|
|
110
|
+
mod tests {
|
|
111
|
+
use super::*;
|
|
112
|
+
|
|
113
|
+
#[test]
|
|
114
|
+
fn geoclue_interfaces_are_named() {
|
|
115
|
+
assert_eq!(GEOCLUE_SERVICE, "org.freedesktop.GeoClue2");
|
|
116
|
+
assert!(GEOCLUE_MANAGER_INTERFACE.ends_with("Manager"));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//! macOS location (source only, untested).
|
|
2
|
+
//!
|
|
3
|
+
//! CoreLocation only grants authorization to an application bundle that ships
|
|
4
|
+
//! `NSLocationUsageDescription` (and is signed/notarized). A child sidecar
|
|
5
|
+
//! process has no bundle identity, so `CLLocationManager` would be denied. The
|
|
6
|
+
//! probe therefore reports a clear error and Node falls back to IP
|
|
7
|
+
//! geolocation/timezone; manual coordinates remain available in settings.
|
|
8
|
+
|
|
9
|
+
use super::{AccessReport, Reading};
|
|
10
|
+
|
|
11
|
+
pub fn request_access(_open_settings: bool) -> AccessReport {
|
|
12
|
+
AccessReport {
|
|
13
|
+
access: "denied",
|
|
14
|
+
low_integrity: false,
|
|
15
|
+
opened_settings: false,
|
|
16
|
+
reason: Some("unavailable"),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn read() -> Result<Reading, String> {
|
|
21
|
+
Err(
|
|
22
|
+
"CoreLocation requires an application bundle with a location usage description; \
|
|
23
|
+
falling back to IP location"
|
|
24
|
+
.to_string(),
|
|
25
|
+
)
|
|
26
|
+
}
|