Files
qbz/crates/qbz-media-controls/Cargo.toml
T
vicrodh c0664030ee feat(windows): keep the system awake while a track plays
Linux holds a logind inhibitor fd; Windows has SetThreadExecutionState, and
nothing was calling it.

The plan asked for the call to sit in `set_playback`. It cannot: that API is
PER-THREAD -- Windows drops a thread's requests when it exits -- and this
module's own contract says outbound calls are "free from any thread"
(media_controls_qt.rs:34). In practice they arrive on the mpris-server thread,
a tokio worker, or the GUI thread, whichever pushed. A request made from a pool
worker would evaporate with it, silently. So the state is owned by one thread
that lives as long as the process and receives a bool.

ES_SYSTEM_REQUIRED without ES_DISPLAY_REQUIRED: an audio player keeps the
MACHINE awake, not the screen. No ES_AWAYMODE_REQUIRED either -- that is for
media-centre appliances and it suppresses the sleep the user asked for.

Exposed as a free function rather than a `PlatformHandle` method, and called
BEFORE the `handle()` early return on both publishers. Hanging it off the
handle tied it to SMTC: a failed souvlaki init, or an HWND that never arrived,
would have left playback working and the machine falling asleep mid-track --
two unrelated features failing together for no reason.

`held` is updated only after the API succeeds, so a failed release cannot leave
Windows inhibited while later `false` messages are skipped as no-ops.

cc
2026-08-27 13:55:45 -06:00

61 lines
2.7 KiB
TOML

[package]
name = "qbz-media-controls"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Frontend-agnostic system media controls: MPRIS (mpris-server) on Linux with a DesktopEntry so GNOME shows the app icon; SMTC/MediaRemote (souvlaki) on Windows/macOS"
[dependencies]
log = { workspace = true }
# fs_url::local_path -- the ONE file-URL helper (W2). SMTC needs a native
# Windows path where MPRIS needs a file:// URL, and hand-rolling that
# conversion is exactly the bug class that helper exists to prevent.
qbz-models = { path = "../qbz-models" }
# Notifications (all platforms): the async entry point spawns the HTTP download
# + image resize on `spawn_blocking`, so a tokio runtime must be present.
tokio = { workspace = true }
# Artwork download + cache + (Linux) resize for the notification icon. Matches
# the Tauri notification helpers so output is identical.
reqwest = { workspace = true, features = ["blocking"] }
image = { version = "0.25", default-features = false, features = [
"png",
"jpeg",
"webp",
] }
dirs = "6"
md-5 = { workspace = true }
urlencoding = "2"
# Linux: mpris-server publishes the full MPRIS interface INCLUDING the
# org.mpris.MediaPlayer2.DesktopEntry property (RootInterface::desktop_entry),
# which is the only way GNOME Shell resolves the app icon. souvlaki never sets
# it. tokio feature so the server shares the app's tokio runtime.
[target.'cfg(target_os = "windows")'.dependencies]
# SetThreadExecutionState: keep the machine awake while a track plays.
# 0.61 is already in the workspace lock.
windows-sys = { version = "0.61", features = ["Win32_System_Power"] }
[target.'cfg(target_os = "linux")'.dependencies]
mpris-server = "0.8"
async-channel = "2"
# XDG notification portal (goes over D-Bus). async-io, NOT tokio: zbus 5's
# "tokio" feature unifies graph-wide and its executor then requires a tokio
# reactor on EVERY thread that opens a zbus connection — accesskit_unix/atspi
# (Slint accessibility) and ksni run theirs on plain threads and panicked at
# startup ("there is no reactor running"). async-io futures still await fine
# inside our tokio runtime. Keep in sync with qbz-credentials/Cargo.toml.
ashpd = { version = "0.13", default-features = false, features = [
"async-io",
"notification",
] }
# macOS (MediaRemote / MPNowPlayingInfoCenter) + Windows (SMTC) via souvlaki —
# there is no DesktopEntry concept there (macOS keys the icon off the app
# bundle), so souvlaki stays the right tool off-Linux.
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
souvlaki = "0.8"
# macOS desktop notifications (NSUserNotification / UserNotifications). Windows
# is a no-op for now (parity with Tauri, which never implemented it).
notify-rust = "4"