Files
qbz/crates/qbzd/Cargo.toml
T
vicrodh bdbdf4e0a1 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
2026-07-19 22:24:41 -06:00

58 lines
2.6 KiB
TOML

[package]
name = "qbzd"
version.workspace = true
edition = "2021"
description = "QBZ headless Qobuz playback daemon (daemon + CLI + setup TUI in one binary)"
license = "MIT"
[[bin]]
name = "qbzd"
path = "src/main.rs"
[dependencies]
# workspace crates — the slint-free column ONLY (01-architecture.md §1.2)
qbz-models = { path = "../qbz-models" }
qbz-app = { path = "../qbz-app" }
qbz-core = { path = "../qbz-core" }
qbz-player = { path = "../qbz-player" }
qbz-audio = { path = "../qbz-audio" } # PROTECTED — read-only consumer
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" }
qconnect-protocol = { path = "../qconnect-protocol" }
qconnect-transport-ws = { path = "../qconnect-transport-ws" }
qconnect-app = { path = "../qconnect-app" }
# runtime / wire
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
reqwest = { workspace = true }
dirs = "6"
rand = { workspace = true }
log = { workspace = true }
async-trait = { workspace = true }
# QConnect glue copies (T9) — mirror the desktop copies' direct imports
uuid = { version = "1", features = ["v4"] } # renderer-report action uuids
rusqlite = { version = "0.31", features = ["bundled"] } # daemon-root qconnect_settings.db KV
futures-util = { workspace = true } # remote_stream.rs feeder (byte-exact)
# new direct deps (versions already in lock where noted)
tiny_http = "0.12" # in lock via qbz-cast
toml = "0.9" # in lock transitively
libc = "0.2" # flock
urlencoding = "2" # OAuth redirect_url encode + callback decode (in lock via qbz)
open = "5" # system-browser launch for `qbzd login` (in lock via qbz)
clap = { version = "4", features = ["derive"] }
clap_complete = "4"
# T13 setup TUI. ratatui 0.30 (NEVER 0.29 — 0.29 pins unicode-width "=0.2.0",
# unresolvable against Slint's annotate-snippets 0.12.16 which needs >=0.2.2;
# ratatui-core 0.30 accepts >=0.2.0,<=0.2.2 → unifies on the lock's 0.2.2, IV5).
# The `crossterm` backend is a DEFAULT feature; re-exported as ratatui::crossterm.
ratatui = "0.30"
[dev-dependencies]
tempfile = "3"