@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,170 @@
|
|
|
1
|
+
//! Current location from each platform's system location API.
|
|
2
|
+
//!
|
|
3
|
+
//! * Windows: WinRT `Windows.Devices.Geolocation.Geolocator`, plus an explicit
|
|
4
|
+
//! permission-request command that reports privacy/integrity blockers.
|
|
5
|
+
//! * Linux: GeoClue2 (`org.freedesktop.GeoClue2`) over the session/system bus
|
|
6
|
+
//! * macOS: CoreLocation requires an app bundle/usage description, which a
|
|
7
|
+
//! sidecar process cannot provide; returns a clear error so Node can fall
|
|
8
|
+
//! back to IP geolocation. Source only, untested.
|
|
9
|
+
//!
|
|
10
|
+
//! The Node weather service treats this as a best-effort first step and falls
|
|
11
|
+
//! back to IP geolocation / timezone.
|
|
12
|
+
|
|
13
|
+
use serde_json::{json, Value};
|
|
14
|
+
|
|
15
|
+
use crate::cli::{action_of, has_flag, CliError, CliResult};
|
|
16
|
+
|
|
17
|
+
#[cfg(windows)]
|
|
18
|
+
#[path = "windows.rs"]
|
|
19
|
+
mod platform;
|
|
20
|
+
#[cfg(target_os = "linux")]
|
|
21
|
+
#[path = "linux.rs"]
|
|
22
|
+
mod platform;
|
|
23
|
+
#[cfg(target_os = "macos")]
|
|
24
|
+
#[path = "macos.rs"]
|
|
25
|
+
mod platform;
|
|
26
|
+
|
|
27
|
+
#[cfg(all(feature = "macos-check", not(target_os = "macos")))]
|
|
28
|
+
#[allow(dead_code)]
|
|
29
|
+
#[path = "macos.rs"]
|
|
30
|
+
mod macos_preview;
|
|
31
|
+
|
|
32
|
+
#[derive(Debug, Clone)]
|
|
33
|
+
pub struct Reading {
|
|
34
|
+
pub latitude: f64,
|
|
35
|
+
pub longitude: f64,
|
|
36
|
+
pub accuracy: Option<f64>,
|
|
37
|
+
pub source: &'static str,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// The outcome of asking the OS for location permission.
|
|
41
|
+
#[derive(Debug, Clone)]
|
|
42
|
+
pub struct AccessReport {
|
|
43
|
+
/// `allowed`, `denied`, `unspecified`, `unknown` or `unsupported`.
|
|
44
|
+
pub access: &'static str,
|
|
45
|
+
/// The process runs at the Low integrity level (Windows only).
|
|
46
|
+
pub low_integrity: bool,
|
|
47
|
+
/// The privacy settings page was opened for the user.
|
|
48
|
+
pub opened_settings: bool,
|
|
49
|
+
/// Machine-readable blocker: `low-integrity`, `privacy-denied`,
|
|
50
|
+
/// `unspecified`, `request-failed`, `unavailable`.
|
|
51
|
+
pub reason: Option<&'static str>,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn reading_json(reading: &Reading, sampled_at: i64) -> Value {
|
|
55
|
+
json!({
|
|
56
|
+
"ok": true,
|
|
57
|
+
"available": true,
|
|
58
|
+
"latitude": reading.latitude,
|
|
59
|
+
"longitude": reading.longitude,
|
|
60
|
+
"accuracy": reading.accuracy,
|
|
61
|
+
"source": reading.source,
|
|
62
|
+
"error": null,
|
|
63
|
+
"sampledAt": sampled_at,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn error_json(error: &str, sampled_at: i64) -> Value {
|
|
68
|
+
json!({
|
|
69
|
+
"ok": false,
|
|
70
|
+
"available": false,
|
|
71
|
+
"latitude": null,
|
|
72
|
+
"longitude": null,
|
|
73
|
+
"accuracy": null,
|
|
74
|
+
"source": "none",
|
|
75
|
+
"error": error,
|
|
76
|
+
"sampledAt": sampled_at,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn access_json(report: &AccessReport, sampled_at: i64) -> Value {
|
|
81
|
+
json!({
|
|
82
|
+
"ok": true,
|
|
83
|
+
"available": report.access == "allowed",
|
|
84
|
+
"access": report.access,
|
|
85
|
+
"allowed": report.access == "allowed",
|
|
86
|
+
"lowIntegrity": report.low_integrity,
|
|
87
|
+
"openedSettings": report.opened_settings,
|
|
88
|
+
"reason": report.reason,
|
|
89
|
+
"sampledAt": sampled_at,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn read_json(sampled_at: i64) -> Value {
|
|
94
|
+
match platform::read() {
|
|
95
|
+
Ok(reading) => reading_json(&reading, sampled_at),
|
|
96
|
+
Err(error) => error_json(&error, sampled_at),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Ask for location permission. The platform module owns the consent flow
|
|
101
|
+
/// (Windows) or reports whether a location can be read at all.
|
|
102
|
+
fn request_access(open_settings: bool) -> AccessReport {
|
|
103
|
+
platform::request_access(open_settings)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub fn cli(args: &[String], started_at: i64) -> CliResult {
|
|
107
|
+
match action_of(args).as_str() {
|
|
108
|
+
"read" => Ok(read_json(started_at)),
|
|
109
|
+
"request" => Ok(access_json(
|
|
110
|
+
&request_access(has_flag(args, "--open-settings")),
|
|
111
|
+
started_at,
|
|
112
|
+
)),
|
|
113
|
+
other => Err(CliError::usage(format!(
|
|
114
|
+
"unsupported location action: {other} (expected read, request)"
|
|
115
|
+
))),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[cfg(test)]
|
|
120
|
+
mod tests {
|
|
121
|
+
use super::*;
|
|
122
|
+
|
|
123
|
+
#[test]
|
|
124
|
+
fn success_shape_is_camel_case() {
|
|
125
|
+
let value = reading_json(
|
|
126
|
+
&Reading {
|
|
127
|
+
latitude: 31.2,
|
|
128
|
+
longitude: 121.5,
|
|
129
|
+
accuracy: Some(12.0),
|
|
130
|
+
source: "test",
|
|
131
|
+
},
|
|
132
|
+
5,
|
|
133
|
+
);
|
|
134
|
+
assert_eq!(value["ok"], true);
|
|
135
|
+
assert_eq!(value["latitude"], 31.2);
|
|
136
|
+
assert_eq!(value["longitude"], 121.5);
|
|
137
|
+
assert_eq!(value["source"], "test");
|
|
138
|
+
assert_eq!(value["sampledAt"], 5);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn error_shape_is_clean() {
|
|
143
|
+
let value = error_json("no location", 6);
|
|
144
|
+
assert_eq!(value["ok"], false);
|
|
145
|
+
assert_eq!(value["available"], false);
|
|
146
|
+
assert_eq!(value["latitude"], serde_json::Value::Null);
|
|
147
|
+
assert_eq!(value["error"], "no location");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[test]
|
|
151
|
+
fn access_shape_reports_the_blocker() {
|
|
152
|
+
let value = access_json(
|
|
153
|
+
&AccessReport {
|
|
154
|
+
access: "denied",
|
|
155
|
+
low_integrity: true,
|
|
156
|
+
opened_settings: true,
|
|
157
|
+
reason: Some("low-integrity"),
|
|
158
|
+
},
|
|
159
|
+
7,
|
|
160
|
+
);
|
|
161
|
+
assert_eq!(value["ok"], true);
|
|
162
|
+
assert_eq!(value["available"], false);
|
|
163
|
+
assert_eq!(value["access"], "denied");
|
|
164
|
+
assert_eq!(value["allowed"], false);
|
|
165
|
+
assert_eq!(value["lowIntegrity"], true);
|
|
166
|
+
assert_eq!(value["openedSettings"], true);
|
|
167
|
+
assert_eq!(value["reason"], "low-integrity");
|
|
168
|
+
assert_eq!(value["sampledAt"], 7);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//! Windows location: WinRT `Windows.Devices.Geolocation.Geolocator`.
|
|
2
|
+
//!
|
|
3
|
+
//! This is the same system API the old PowerShell probe used, now called from
|
|
4
|
+
//! Rust. Access can be denied by the Windows privacy settings *or* because the
|
|
5
|
+
//! process runs below the Medium integrity level (a Low mandatory label on the
|
|
6
|
+
//! plugin directory is enough), which Windows treats as untrusted for location.
|
|
7
|
+
//! `request_access` reports both cases and can open the privacy settings page
|
|
8
|
+
//! for the user.
|
|
9
|
+
|
|
10
|
+
use std::mem::{size_of, size_of_val};
|
|
11
|
+
|
|
12
|
+
use windows::core::PCWSTR;
|
|
13
|
+
use windows::Devices::Geolocation::{GeolocationAccessStatus, Geolocator, PositionAccuracy};
|
|
14
|
+
use windows::Foundation::TimeSpan;
|
|
15
|
+
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
|
16
|
+
use windows::Win32::Security::{
|
|
17
|
+
GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, TokenIntegrityLevel,
|
|
18
|
+
TOKEN_MANDATORY_LABEL, TOKEN_QUERY,
|
|
19
|
+
};
|
|
20
|
+
use windows::Win32::System::Com::{CoInitializeEx, COINIT_MULTITHREADED};
|
|
21
|
+
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
|
|
22
|
+
use windows::Win32::UI::Shell::ShellExecuteW;
|
|
23
|
+
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
|
|
24
|
+
|
|
25
|
+
use super::{AccessReport, Reading};
|
|
26
|
+
|
|
27
|
+
const REQUEST_TIMEOUT: TimeSpan = TimeSpan {
|
|
28
|
+
// 8 seconds in 100 ns ticks.
|
|
29
|
+
Duration: 80_000_000,
|
|
30
|
+
};
|
|
31
|
+
const MAXIMUM_AGE: TimeSpan = TimeSpan {
|
|
32
|
+
// 10 minutes in 100 ns ticks.
|
|
33
|
+
Duration: 6_000_000_000,
|
|
34
|
+
};
|
|
35
|
+
/// `SECURITY_MANDATORY_MEDIUM_RID`; anything below it is Low/Untrusted.
|
|
36
|
+
const SECURITY_MANDATORY_MEDIUM_RID: u32 = 0x2000;
|
|
37
|
+
|
|
38
|
+
fn ensure_com() {
|
|
39
|
+
static ONCE: std::sync::Once = std::sync::Once::new();
|
|
40
|
+
ONCE.call_once(|| unsafe {
|
|
41
|
+
// S_FALSE / RPC_E_CHANGED_MODE are harmless.
|
|
42
|
+
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn access_status() -> Result<GeolocationAccessStatus, String> {
|
|
47
|
+
ensure_com();
|
|
48
|
+
Geolocator::RequestAccessAsync()
|
|
49
|
+
.map_err(|error| format!("geolocation request failed: {error}"))?
|
|
50
|
+
.join()
|
|
51
|
+
.map_err(|error| format!("geolocation access request failed: {error}"))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Whether this process is running at the Low integrity level. Windows refuses
|
|
55
|
+
/// location access to Low-integrity processes even when the privacy settings
|
|
56
|
+
/// allow desktop apps, so this distinction matters for the hint shown in the UI.
|
|
57
|
+
fn is_low_integrity() -> bool {
|
|
58
|
+
unsafe {
|
|
59
|
+
let mut token = HANDLE::default();
|
|
60
|
+
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).is_err() {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
let mut size = 0_u32;
|
|
64
|
+
let _ = GetTokenInformation(token, TokenIntegrityLevel, None, 0, &mut size);
|
|
65
|
+
if size == 0 {
|
|
66
|
+
let _ = CloseHandle(token);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// `GetTokenInformation` writes a TOKEN_MANDATORY_LABEL followed by the
|
|
70
|
+
// SID; use word-aligned storage so the cast below is well defined.
|
|
71
|
+
let words = (size as usize).div_ceil(size_of::<usize>()).max(1);
|
|
72
|
+
let mut buffer = vec![0_usize; words];
|
|
73
|
+
let query = GetTokenInformation(
|
|
74
|
+
token,
|
|
75
|
+
TokenIntegrityLevel,
|
|
76
|
+
Some(buffer.as_mut_ptr() as *mut core::ffi::c_void),
|
|
77
|
+
size_of_val(&buffer[..]) as u32,
|
|
78
|
+
&mut size,
|
|
79
|
+
);
|
|
80
|
+
let _ = CloseHandle(token);
|
|
81
|
+
if query.is_err() {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
let label = &*(buffer.as_ptr() as *const TOKEN_MANDATORY_LABEL);
|
|
85
|
+
let sid = label.Label.Sid;
|
|
86
|
+
if sid.is_invalid() {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
let count = GetSidSubAuthorityCount(sid);
|
|
90
|
+
if count.is_null() || *count == 0 {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
let rid = GetSidSubAuthority(sid, 0);
|
|
94
|
+
if rid.is_null() {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
*rid < SECURITY_MANDATORY_MEDIUM_RID
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Open `Settings > Privacy & security > Location` so the user can grant access.
|
|
102
|
+
fn open_location_settings() -> bool {
|
|
103
|
+
unsafe {
|
|
104
|
+
let result = ShellExecuteW(
|
|
105
|
+
None,
|
|
106
|
+
windows::core::w!("open"),
|
|
107
|
+
windows::core::w!("ms-settings:privacy-location"),
|
|
108
|
+
PCWSTR::null(),
|
|
109
|
+
PCWSTR::null(),
|
|
110
|
+
SW_SHOWNORMAL,
|
|
111
|
+
);
|
|
112
|
+
result.0 as isize > 32
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Ask the OS for location permission and report the exact blocker.
|
|
117
|
+
pub fn request_access(open_settings: bool) -> AccessReport {
|
|
118
|
+
let low_integrity = is_low_integrity();
|
|
119
|
+
let (access, reason) = match access_status() {
|
|
120
|
+
Ok(status) if status == GeolocationAccessStatus::Allowed => ("allowed", None),
|
|
121
|
+
Ok(status) if status == GeolocationAccessStatus::Denied => {
|
|
122
|
+
("denied", Some("privacy-denied"))
|
|
123
|
+
}
|
|
124
|
+
Ok(_) => ("unspecified", Some("unspecified")),
|
|
125
|
+
Err(_) => ("unknown", Some("request-failed")),
|
|
126
|
+
};
|
|
127
|
+
let reason = if access == "denied" && low_integrity {
|
|
128
|
+
Some("low-integrity")
|
|
129
|
+
} else {
|
|
130
|
+
reason
|
|
131
|
+
};
|
|
132
|
+
let opened_settings = open_settings && access != "allowed" && open_location_settings();
|
|
133
|
+
AccessReport {
|
|
134
|
+
access,
|
|
135
|
+
low_integrity,
|
|
136
|
+
opened_settings,
|
|
137
|
+
reason,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pub fn read() -> Result<Reading, String> {
|
|
142
|
+
let access = access_status()?;
|
|
143
|
+
if access != GeolocationAccessStatus::Allowed {
|
|
144
|
+
let label = if access == GeolocationAccessStatus::Denied {
|
|
145
|
+
"denied"
|
|
146
|
+
} else {
|
|
147
|
+
"not granted"
|
|
148
|
+
};
|
|
149
|
+
if is_low_integrity() {
|
|
150
|
+
return Err(format!(
|
|
151
|
+
"Windows location access {label}: dsh-probe is running at low integrity, and Windows blocks location for low-integrity processes; rebuild the probe into a normal-integrity directory or open the location settings"
|
|
152
|
+
));
|
|
153
|
+
}
|
|
154
|
+
return Err(format!(
|
|
155
|
+
"Windows location access {label}; enable Settings > Privacy > Location (desktop apps) or use the location permission request in the weather settings"
|
|
156
|
+
));
|
|
157
|
+
}
|
|
158
|
+
let locator =
|
|
159
|
+
Geolocator::new().map_err(|error| format!("could not create Geolocator: {error}"))?;
|
|
160
|
+
let _ = locator.SetDesiredAccuracy(PositionAccuracy::High);
|
|
161
|
+
let position = locator
|
|
162
|
+
.GetGeopositionAsyncWithAgeAndTimeout(MAXIMUM_AGE, REQUEST_TIMEOUT)
|
|
163
|
+
.map_err(|error| format!("could not request a position: {error}"))?
|
|
164
|
+
.join()
|
|
165
|
+
.map_err(|error| format!("could not read a position: {error}"))?;
|
|
166
|
+
let coordinate = position
|
|
167
|
+
.Coordinate()
|
|
168
|
+
.map_err(|error| format!("position has no coordinate: {error}"))?;
|
|
169
|
+
let point = coordinate
|
|
170
|
+
.Point()
|
|
171
|
+
.map_err(|error| format!("coordinate has no point: {error}"))?;
|
|
172
|
+
let basic = point
|
|
173
|
+
.Position()
|
|
174
|
+
.map_err(|error| format!("point has no position: {error}"))?;
|
|
175
|
+
let accuracy = coordinate.Accuracy().ok();
|
|
176
|
+
if !basic.Latitude.is_finite()
|
|
177
|
+
|| !basic.Longitude.is_finite()
|
|
178
|
+
|| basic.Latitude.abs() > 90.0
|
|
179
|
+
|| basic.Longitude.abs() > 180.0
|
|
180
|
+
{
|
|
181
|
+
return Err("Windows returned an invalid coordinate".to_string());
|
|
182
|
+
}
|
|
183
|
+
Ok(Reading {
|
|
184
|
+
latitude: basic.Latitude,
|
|
185
|
+
longitude: basic.Longitude,
|
|
186
|
+
accuracy,
|
|
187
|
+
source: "windows-geolocation",
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[cfg(test)]
|
|
192
|
+
mod tests {
|
|
193
|
+
use super::*;
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
fn recovers_from_com_initialization() {
|
|
197
|
+
ensure_com();
|
|
198
|
+
ensure_com();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn integrity_probe_returns_a_bool() {
|
|
203
|
+
let _ = is_low_integrity();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//! `dsh-probe`: native OS probe sidecar for the DSH LAN console.
|
|
2
|
+
//!
|
|
3
|
+
//! The Node host executes this binary with a subcommand and parses one line of
|
|
4
|
+
//! JSON from stdout. Native OS APIs are used per platform:
|
|
5
|
+
//! * Windows: WinRT SMTC, Core Audio (WASAPI endpoint), Win32 power info
|
|
6
|
+
//! * Linux: MPRIS over D-Bus (zbus), wpctl/pactl/amixer, /proc + sysfs
|
|
7
|
+
//! * macOS: nowplaying-cli/osascript (source only, untested)
|
|
8
|
+
//!
|
|
9
|
+
//! WASM is deliberately not used: WinRT, Core Audio, D-Bus and IOKit are not
|
|
10
|
+
//! reachable from a WebAssembly sandbox.
|
|
11
|
+
|
|
12
|
+
mod cli;
|
|
13
|
+
mod daemon;
|
|
14
|
+
mod location;
|
|
15
|
+
mod media;
|
|
16
|
+
mod notifications;
|
|
17
|
+
mod perf;
|
|
18
|
+
mod util;
|
|
19
|
+
mod volume;
|
|
20
|
+
|
|
21
|
+
fn main() {
|
|
22
|
+
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
23
|
+
// The long-lived mode must own stdout (JSON lines) and stdin (requests);
|
|
24
|
+
// it never returns through the one-shot printing path below.
|
|
25
|
+
if args.first().map(String::as_str) == Some("daemon") {
|
|
26
|
+
std::process::exit(daemon::run());
|
|
27
|
+
}
|
|
28
|
+
let (value, exit_code) = cli::dispatch(&args);
|
|
29
|
+
match serde_json::to_string(&value) {
|
|
30
|
+
Ok(text) => println!("{text}"),
|
|
31
|
+
Err(error) => {
|
|
32
|
+
// serde_json only fails here if a custom value contains invalid data;
|
|
33
|
+
// still emit something the bridge can parse.
|
|
34
|
+
println!(
|
|
35
|
+
"{{\"ok\":false,\"error\":{}}}",
|
|
36
|
+
serde_json::Value::String(format!("failed to encode probe result: {error}"))
|
|
37
|
+
);
|
|
38
|
+
std::process::exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
std::process::exit(exit_code);
|
|
42
|
+
}
|