@ohmykreee/dsh-remote-control 0.0.0-stage → 1.0.2
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,90 @@
|
|
|
1
|
+
//! Apple Music / Cider private adapter.
|
|
2
|
+
//!
|
|
3
|
+
//! Apple Music normally uses SMTC/MediaRemote. Cider (a third-party Apple Music
|
|
4
|
+
//! client) writes the current title to `Plugins/title.txt`; that local file is
|
|
5
|
+
//! the private interface used when MediaRemote cannot identify the track.
|
|
6
|
+
|
|
7
|
+
use std::path::PathBuf;
|
|
8
|
+
|
|
9
|
+
use super::common;
|
|
10
|
+
use crate::media::catalog::ParsedTitle;
|
|
11
|
+
use crate::util;
|
|
12
|
+
|
|
13
|
+
const SUFFIXES: &[&str] = &[" - Apple Music", " - Music", " - Cider"];
|
|
14
|
+
const PRODUCT_NAMES: &[&str] = &["apple music", "music", "cider"];
|
|
15
|
+
|
|
16
|
+
pub fn parse_title(raw: &str) -> Option<ParsedTitle> {
|
|
17
|
+
let text = common::strip_suffixes(common::normalize_spaces(raw), SUFFIXES);
|
|
18
|
+
if common::is_generic_title(&text, PRODUCT_NAMES) {
|
|
19
|
+
return None;
|
|
20
|
+
}
|
|
21
|
+
let mut parsed = common::split_title_artist(&text, false);
|
|
22
|
+
// Apple Music appends the album to the artist: "Floating Points — Cascade".
|
|
23
|
+
parsed.artist = parsed
|
|
24
|
+
.artist
|
|
25
|
+
.split(" — ")
|
|
26
|
+
.next()
|
|
27
|
+
.unwrap_or(&parsed.artist)
|
|
28
|
+
.trim()
|
|
29
|
+
.to_string();
|
|
30
|
+
if parsed.title.is_empty() {
|
|
31
|
+
return None;
|
|
32
|
+
}
|
|
33
|
+
Some(parsed)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Read Cider's local current-title file, checking the Windows and macOS
|
|
37
|
+
/// locations used by the clients.
|
|
38
|
+
#[allow(dead_code)] // Windows/macOS callers; Linux has no Cider client.
|
|
39
|
+
pub fn cider_title() -> Option<ParsedTitle> {
|
|
40
|
+
let mut candidates: Vec<PathBuf> = Vec::new();
|
|
41
|
+
if let Some(appdata) = std::env::var_os("APPDATA").map(PathBuf::from) {
|
|
42
|
+
candidates.push(appdata.join("Cider").join("Plugins").join("title.txt"));
|
|
43
|
+
}
|
|
44
|
+
if let Some(home) = util::home_dir() {
|
|
45
|
+
candidates.push(
|
|
46
|
+
home.join("Library")
|
|
47
|
+
.join("Application Support")
|
|
48
|
+
.join("Cider")
|
|
49
|
+
.join("Plugins")
|
|
50
|
+
.join("title.txt"),
|
|
51
|
+
);
|
|
52
|
+
candidates.push(
|
|
53
|
+
home.join(".config")
|
|
54
|
+
.join("Cider")
|
|
55
|
+
.join("Plugins")
|
|
56
|
+
.join("title.txt"),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
for path in candidates {
|
|
60
|
+
let Ok(text) = std::fs::read_to_string(path) else {
|
|
61
|
+
continue;
|
|
62
|
+
};
|
|
63
|
+
let Some(line) = text.lines().map(str::trim).find(|line| !line.is_empty()) else {
|
|
64
|
+
continue;
|
|
65
|
+
};
|
|
66
|
+
if let Some(parsed) = parse_title(line) {
|
|
67
|
+
return Some(parsed);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
None
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[cfg(test)]
|
|
74
|
+
mod tests {
|
|
75
|
+
use super::*;
|
|
76
|
+
|
|
77
|
+
#[test]
|
|
78
|
+
fn parses_apple_music_title() {
|
|
79
|
+
let parsed = parse_title("Crush - Floating Points - Apple Music").unwrap();
|
|
80
|
+
assert_eq!(parsed.title, "Crush");
|
|
81
|
+
assert_eq!(parsed.artist, "Floating Points");
|
|
82
|
+
assert!(parse_title("Apple Music").is_none());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[test]
|
|
86
|
+
fn strips_album_from_artist() {
|
|
87
|
+
let parsed = parse_title("Crush - Floating Points — Cascade").unwrap();
|
|
88
|
+
assert_eq!(parsed.artist, "Floating Points");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//! Shared helpers for the per-client vendor adapters.
|
|
2
|
+
//!
|
|
3
|
+
//! The adapters read only local client state (window titles, Cider's title
|
|
4
|
+
//! file, NetEase's SQLite database). They never call a network API.
|
|
5
|
+
|
|
6
|
+
use crate::media::catalog::{self, App, ParsedTitle};
|
|
7
|
+
use crate::media::Media;
|
|
8
|
+
|
|
9
|
+
/// Collapse runs of whitespace and trim.
|
|
10
|
+
pub fn normalize_spaces(value: &str) -> String {
|
|
11
|
+
value.split_whitespace().collect::<Vec<_>>().join(" ")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Split a player window title into a title/artist pair.
|
|
15
|
+
pub fn split_title_artist(value: &str, artist_first: bool) -> ParsedTitle {
|
|
16
|
+
let value = value.trim();
|
|
17
|
+
for separator in [" - ", " – "] {
|
|
18
|
+
if let Some((left, right)) = value.split_once(separator) {
|
|
19
|
+
let (left, right) = (left.trim(), right.trim());
|
|
20
|
+
if !left.is_empty() && !right.is_empty() {
|
|
21
|
+
return if artist_first {
|
|
22
|
+
ParsedTitle {
|
|
23
|
+
title: right.to_string(),
|
|
24
|
+
artist: left.to_string(),
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
ParsedTitle {
|
|
28
|
+
title: left.to_string(),
|
|
29
|
+
artist: right.to_string(),
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
ParsedTitle {
|
|
36
|
+
title: value.to_string(),
|
|
37
|
+
artist: String::new(),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Remove the client/product suffix used by most desktop players.
|
|
42
|
+
pub fn strip_suffixes(value: String, suffixes: &[&str]) -> String {
|
|
43
|
+
let mut text = value.trim().to_string();
|
|
44
|
+
for suffix in suffixes {
|
|
45
|
+
if let Some(stripped) = strip_suffix_ci(&text, suffix) {
|
|
46
|
+
text = stripped.trim().to_string();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
text
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn strip_prefix_ci<'a>(value: &'a str, prefix: &str) -> Option<&'a str> {
|
|
54
|
+
if value.len() >= prefix.len()
|
|
55
|
+
&& value.is_char_boundary(prefix.len())
|
|
56
|
+
&& value[..prefix.len()].eq_ignore_ascii_case(prefix)
|
|
57
|
+
{
|
|
58
|
+
Some(&value[prefix.len()..])
|
|
59
|
+
} else {
|
|
60
|
+
None
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn strip_suffix_ci<'a>(value: &'a str, suffix: &str) -> Option<&'a str> {
|
|
65
|
+
if value.len() < suffix.len() {
|
|
66
|
+
return None;
|
|
67
|
+
}
|
|
68
|
+
let index = value.len() - suffix.len();
|
|
69
|
+
if !value.is_char_boundary(index) {
|
|
70
|
+
return None;
|
|
71
|
+
}
|
|
72
|
+
if value[index..].eq_ignore_ascii_case(suffix) {
|
|
73
|
+
Some(&value[..index])
|
|
74
|
+
} else {
|
|
75
|
+
None
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// True for product names, "connecting" placeholders and empty titles.
|
|
80
|
+
pub fn is_generic_title(value: &str, product_names: &[&str]) -> bool {
|
|
81
|
+
let lower = value.trim().to_lowercase();
|
|
82
|
+
if lower.is_empty() {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if lower.contains("正在连接") || lower.contains("connecting") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if product_names.iter().any(|name| lower == *name) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
matches!(lower.as_str(), "未知" | "unknown" | "未命名媒体")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Apply client-specific artist separators (e.g. `、`/`&` -> ` / `).
|
|
95
|
+
pub fn normalize_artist(value: &str, replacements: &[(&str, &str)]) -> String {
|
|
96
|
+
let mut artist = value.trim().to_string();
|
|
97
|
+
for (from, to) in replacements {
|
|
98
|
+
artist = artist.replace(from, to);
|
|
99
|
+
}
|
|
100
|
+
normalize_spaces(&artist)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn find_chars(haystack: &[char], needle: &[char]) -> Option<usize> {
|
|
104
|
+
if needle.is_empty() || haystack.len() < needle.len() {
|
|
105
|
+
return None;
|
|
106
|
+
}
|
|
107
|
+
haystack
|
|
108
|
+
.windows(needle.len())
|
|
109
|
+
.position(|window| window == needle)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Vendor-first policy for clients whose private interface is only a window
|
|
113
|
+
/// title: use the system session when it is complete (controls and seek stay
|
|
114
|
+
/// on SMTC/MPRIS), otherwise fall back to the client's private title.
|
|
115
|
+
pub fn resolve_system_or_private(
|
|
116
|
+
app: App,
|
|
117
|
+
system: Option<Media>,
|
|
118
|
+
private: Option<Media>,
|
|
119
|
+
) -> Option<Media> {
|
|
120
|
+
if let Some(system) = system.as_ref() {
|
|
121
|
+
let profile = catalog::profile_for_app(app);
|
|
122
|
+
if catalog::system_complete(profile, system) {
|
|
123
|
+
return Some(system.clone());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
private.or(system)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Rotate a rolling window title so it starts at `marker`.
|
|
130
|
+
pub fn rotate_to_marker(raw: &str, marker: &[char]) -> Option<String> {
|
|
131
|
+
let chars: Vec<char> = raw.chars().collect();
|
|
132
|
+
let index = find_chars(&chars, marker)?;
|
|
133
|
+
Some(
|
|
134
|
+
chars[index..]
|
|
135
|
+
.iter()
|
|
136
|
+
.chain(chars[..index].iter())
|
|
137
|
+
.collect::<String>(),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[cfg(test)]
|
|
142
|
+
mod tests {
|
|
143
|
+
use super::*;
|
|
144
|
+
|
|
145
|
+
fn system(title: &str, position: f64) -> Media {
|
|
146
|
+
let mut media = Media::new("test.exe");
|
|
147
|
+
media.title = title.to_string();
|
|
148
|
+
media.playback_status = "Playing".to_string();
|
|
149
|
+
media.start_time = 0.0;
|
|
150
|
+
media.end_time = 100.0;
|
|
151
|
+
media.position = position;
|
|
152
|
+
media.can_seek = true;
|
|
153
|
+
media
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn private(title: &str) -> Media {
|
|
157
|
+
let mut media = Media::new("test.exe");
|
|
158
|
+
media.title = title.to_string();
|
|
159
|
+
media.provider = "private".to_string();
|
|
160
|
+
media
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn vendor_prefers_a_complete_system_session() {
|
|
165
|
+
let chosen = resolve_system_or_private(
|
|
166
|
+
App::Spotify,
|
|
167
|
+
Some(system("System", 10.0)),
|
|
168
|
+
Some(private("Private")),
|
|
169
|
+
)
|
|
170
|
+
.unwrap();
|
|
171
|
+
assert_eq!(chosen.title, "System");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn vendor_falls_back_to_private_when_system_is_incomplete() {
|
|
176
|
+
// QQ requires a real position; zero means the session is not complete.
|
|
177
|
+
let chosen = resolve_system_or_private(
|
|
178
|
+
App::QQMusic,
|
|
179
|
+
Some(system("System", 0.0)),
|
|
180
|
+
Some(private("Private")),
|
|
181
|
+
)
|
|
182
|
+
.unwrap();
|
|
183
|
+
assert_eq!(chosen.title, "Private");
|
|
184
|
+
assert_eq!(chosen.provider, "private");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//! KuGou Music private window-title adapter.
|
|
2
|
+
//!
|
|
3
|
+
//! KuGou rotates its window title, e.g.
|
|
4
|
+
//! `还是他 - 酷狗音乐 陶喆 - 爱我` should become `爱我还是他 - 陶喆`.
|
|
5
|
+
//! The "un-rotating" heuristic is ported from the reference project.
|
|
6
|
+
|
|
7
|
+
use super::common;
|
|
8
|
+
use crate::media::catalog::ParsedTitle;
|
|
9
|
+
|
|
10
|
+
const PRODUCT_NAMES: &[&str] = &["酷狗音乐", "酷狗", "kugou"];
|
|
11
|
+
|
|
12
|
+
pub fn parse_title(raw: &str) -> Option<ParsedTitle> {
|
|
13
|
+
let fixed = fix_title(raw);
|
|
14
|
+
if common::is_generic_title(&fixed, PRODUCT_NAMES) {
|
|
15
|
+
return None;
|
|
16
|
+
}
|
|
17
|
+
let parsed = common::split_title_artist(&fixed, false);
|
|
18
|
+
if parsed.title.is_empty() {
|
|
19
|
+
return None;
|
|
20
|
+
}
|
|
21
|
+
Some(parsed)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn fix_title(raw: &str) -> String {
|
|
25
|
+
let rotated = common::rotate_to_marker(raw, &['酷', '狗']).unwrap_or_else(|| raw.to_string());
|
|
26
|
+
let mut text = rotated.trim().to_string();
|
|
27
|
+
text = common::strip_prefix_ci(&text, "酷狗音乐")
|
|
28
|
+
.or_else(|| common::strip_prefix_ci(&text, "酷狗"))
|
|
29
|
+
.unwrap_or(&text)
|
|
30
|
+
.trim_start_matches([' ', '-', '—', '–'])
|
|
31
|
+
.to_string();
|
|
32
|
+
text = text.trim_end_matches([' ', '-', '—', '–']).to_string();
|
|
33
|
+
text = text.replace('、', " / ");
|
|
34
|
+
// KuGou leaves `artist - song`; put the song first.
|
|
35
|
+
if let Some((artist, title)) = text.split_once(" - ") {
|
|
36
|
+
return format!("{} - {}", title.trim(), artist.trim());
|
|
37
|
+
}
|
|
38
|
+
text
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[cfg(test)]
|
|
42
|
+
mod tests {
|
|
43
|
+
use super::*;
|
|
44
|
+
|
|
45
|
+
#[test]
|
|
46
|
+
fn fixes_rolling_kugou_title() {
|
|
47
|
+
let parsed = parse_title("还是他 - 酷狗音乐 陶喆 - 爱我").unwrap();
|
|
48
|
+
assert_eq!(parsed.title, "爱我还是他");
|
|
49
|
+
assert_eq!(parsed.artist, "陶喆");
|
|
50
|
+
assert!(parse_title("酷狗音乐").is_none());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//! KuWo Music private window-title adapter.
|
|
2
|
+
//!
|
|
3
|
+
//! KuWo also rotates its window title and drops spaces around the separator:
|
|
4
|
+
//! `nd&Daft Punk-酷我音乐 Starboy -The Week` -> `Starboy - The Weeknd / Daft Punk`.
|
|
5
|
+
|
|
6
|
+
use super::common;
|
|
7
|
+
use crate::media::catalog::ParsedTitle;
|
|
8
|
+
|
|
9
|
+
const PRODUCT_NAMES: &[&str] = &["酷我音乐", "酷我", "kuwo"];
|
|
10
|
+
|
|
11
|
+
pub fn parse_title(raw: &str) -> Option<ParsedTitle> {
|
|
12
|
+
let fixed = fix_title(raw);
|
|
13
|
+
if common::is_generic_title(&fixed, PRODUCT_NAMES) {
|
|
14
|
+
return None;
|
|
15
|
+
}
|
|
16
|
+
let parsed = common::split_title_artist(&fixed, false);
|
|
17
|
+
if parsed.title.is_empty() {
|
|
18
|
+
return None;
|
|
19
|
+
}
|
|
20
|
+
Some(parsed)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn fix_title(raw: &str) -> String {
|
|
24
|
+
let rotated = common::rotate_to_marker(raw, &['酷', '我']).unwrap_or_else(|| raw.to_string());
|
|
25
|
+
let mut text = rotated.trim().to_string();
|
|
26
|
+
text = common::strip_prefix_ci(&text, "酷我音乐")
|
|
27
|
+
.or_else(|| common::strip_prefix_ci(&text, "酷我"))
|
|
28
|
+
.unwrap_or(&text)
|
|
29
|
+
.trim_start_matches([' ', '-', '—', '–'])
|
|
30
|
+
.to_string();
|
|
31
|
+
text = text.trim_end_matches([' ', '-', '—', '–']).to_string();
|
|
32
|
+
text = text.replace('&', " / ");
|
|
33
|
+
// KuWo drops the spaces around its separator.
|
|
34
|
+
text = text.replace(" -", " - ").replace("- ", " - ");
|
|
35
|
+
common::normalize_spaces(&text)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[cfg(test)]
|
|
39
|
+
mod tests {
|
|
40
|
+
use super::*;
|
|
41
|
+
|
|
42
|
+
#[test]
|
|
43
|
+
fn fixes_rolling_kuwo_title() {
|
|
44
|
+
let parsed = parse_title("nd&Daft Punk-酷我音乐 Starboy -The Week").unwrap();
|
|
45
|
+
assert_eq!(parsed.title, "Starboy");
|
|
46
|
+
assert_eq!(parsed.artist, "The Weeknd / Daft Punk");
|
|
47
|
+
assert!(parse_title("酷我音乐").is_none());
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//! Vendor-specific local integrations.
|
|
2
|
+
//!
|
|
3
|
+
//! Each child module reads one third-party desktop client's local state (never
|
|
4
|
+
//! its network API): window titles, Cider's title file, NetEase's SQLite
|
|
5
|
+
//! database, etc. Keep this folder for vendor-only code so the generic media /
|
|
6
|
+
//! notification / performance probes stay platform-focused.
|
|
7
|
+
|
|
8
|
+
use crate::media::catalog::{App, ParsedTitle};
|
|
9
|
+
use crate::media::Media;
|
|
10
|
+
|
|
11
|
+
/// Which system branch a vendor adapter is running on. Vendor modules use this
|
|
12
|
+
/// to pick the matching system-API rule (WinRT SMTC, MPRIS, Now Playing) while
|
|
13
|
+
/// keeping all client-specific logic in one file.
|
|
14
|
+
#[allow(dead_code)] // every platform constructs only its own variant.
|
|
15
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
16
|
+
pub enum Platform {
|
|
17
|
+
Windows,
|
|
18
|
+
Linux,
|
|
19
|
+
MacOS,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub mod apple_music;
|
|
23
|
+
pub mod common;
|
|
24
|
+
pub mod kugou;
|
|
25
|
+
pub mod kuwo;
|
|
26
|
+
pub mod netease;
|
|
27
|
+
pub mod qishui;
|
|
28
|
+
pub mod qqmusic;
|
|
29
|
+
pub mod spotify;
|
|
30
|
+
pub mod wesing;
|
|
31
|
+
pub mod youtube_music;
|
|
32
|
+
|
|
33
|
+
/// Parse a client's window title using its vendor adapter.
|
|
34
|
+
///
|
|
35
|
+
/// `App::Browser` has no private interface: browser playback is handled by the
|
|
36
|
+
/// system media session only.
|
|
37
|
+
pub fn parse_title(app: App, raw: &str) -> Option<ParsedTitle> {
|
|
38
|
+
match app {
|
|
39
|
+
App::Netease => netease::parse_title(raw),
|
|
40
|
+
App::QQMusic => qqmusic::parse_title(raw),
|
|
41
|
+
App::Kugou => kugou::parse_title(raw),
|
|
42
|
+
App::Kuwo => kuwo::parse_title(raw),
|
|
43
|
+
App::Qishui => qishui::parse_title(raw),
|
|
44
|
+
App::WeSing => wesing::parse_title(raw),
|
|
45
|
+
App::Spotify => spotify::parse_title(raw),
|
|
46
|
+
App::AppleMusic => apple_music::parse_title(raw),
|
|
47
|
+
App::YouTubeMusic => youtube_music::parse_title(raw),
|
|
48
|
+
App::Browser => None,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Vendor-first media resolution.
|
|
53
|
+
///
|
|
54
|
+
/// The platform adapter identifies the application and passes the matching
|
|
55
|
+
/// `Platform` plus its system session and optional private candidate. The
|
|
56
|
+
/// vendor decides how to combine them:
|
|
57
|
+
///
|
|
58
|
+
/// * NetEase: system metadata/status/cover + CDP progress/liked; with no system
|
|
59
|
+
/// session CDP supplies the complete media/transport; accessibility fallback;
|
|
60
|
+
/// * other known clients: complete system session, else private title/UIA;
|
|
61
|
+
/// * browser/unknown: the platform's system API only (handled by the caller).
|
|
62
|
+
#[allow(dead_code)] // used by every platform adapter; kept cross-platform.
|
|
63
|
+
pub fn resolve(
|
|
64
|
+
platform: Platform,
|
|
65
|
+
app: App,
|
|
66
|
+
system: Option<Media>,
|
|
67
|
+
private: Option<Media>,
|
|
68
|
+
fallback: impl FnOnce() -> Option<(f64, f64)>,
|
|
69
|
+
) -> Option<Media> {
|
|
70
|
+
match app {
|
|
71
|
+
App::Netease => netease::merge(platform, system, private, fallback),
|
|
72
|
+
App::Browser => system,
|
|
73
|
+
_ => common::resolve_system_or_private(app, system, private),
|
|
74
|
+
}
|
|
75
|
+
}
|