feat(qbzd): publish MPRIS media controls (KDE/GNOME widget + media keys)

Add a daemon MPRIS service (CONSOLE ext) via qbz-media-controls (mpris-server on
Linux, incl. the DesktopEntry that lets GNOME/KDE resolve the app icon). A KDE
Plasma media widget, a plasmoid, or hardware media keys now drive the daemon
with NO custom client code — the marquee third-party-integration surface.

Two halves in crate::mpris:
- OUTBOUND: a CoreEvent-bus subscriber pushes now-playing metadata, play/pause,
  live position, and volume into the OS controls (seeded once from live state).
- INBOUND: the qbz-media-controls callback maps MediaEvent (Play/Pause/Toggle/
  Next/Previous/Stop/Seek/SetPosition/SetVolume) onto core transport commands;
  seek/volume honor the DSD-direct guard; Next/Previous fire the full advance
  ritual off the D-Bus thread; Raise/Quit are ignored (headless).

The inbound callback holds only a Weak<AppRuntime> (upgraded per event) and the
updater task upgrades a Weak once to seed then drops it, so the integration never
pins the runtime — the #521 audio-release-before-drop(booted) ordering is
unaffected. Spawned next to the scrobbler in run(); torn down (updater abort +
handle drop) in shutdown. On by default where a session bus exists; QBZD_MPRIS in
{0,false,off,no} disables it, and spawn returns None gracefully on a headless
box with no D-Bus. 280 tests green.

cc
This commit is contained in:
vicrodh committed 2026-07-19 22:24:41 -06:00
1 parent 903acdb044
commit bdbdf4e0a1
5 files changed
+290

No files matched your search

+1
View File
@@ -19,6 +19,7 @@ qbz-audio = { path = "../qbz-audio" } # PROTECTED — read-only consume
qbz-qobuz = { path = "../qbz-qobuz" }
qbz-reco = { path = "../qbz-reco" } # playlist recommendations (ArtistVectorStore); already in-tree via qbz-core
qbz-integrations = { path = "../qbz-integrations" } # Last.fm / ListenBrainz scrobbling; already in-tree via qbz-core
qbz-media-controls = { path = "../qbz-media-controls" } # MPRIS/media-key control (CONSOLE ext); mpris-server on Linux
qbz-credentials = { path = "../qbz-credentials" }
qbz-log = { path = "../qbz-log" }
qconnect-core = { path = "../qconnect-core" }