fix(windows): do not panic in souvlaki without an HWND; open folders via open::that

souvlaki's Windows backend does config.hwnd.expect() at
platform/windows/mod.rs:57, so the None we passed was a panic inside a
tokio task on the first session, not the Err the match was written for.
The comment on that field claimed init would merely fail; it was read from
the comment, not the mechanism. No HWND is wired yet, so spawn() bows out
on Windows and the body moves to spawn_native, which is compiled out
there. Phase A ships without media keys and says so.

The offline-folder opener spawned xdg-open on every non-macOS host,
Windows included, where it does not exist. Route it through the open crate
like the other eleven sites in this crate; Linux and macOS reach the same
two binaries as before.

cc
This commit is contained in:
vicrodh committed 2026-08-26 23:07:11 -06:00
1 parent f06ffc7141
commit 42bc4ba1ec
2 files changed
+39 -15

No files matched your search

+32 -6
View File
@@ -8,14 +8,17 @@
use std::sync::{Arc, Mutex};
use std::time::Duration;
use souvlaki::{
MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback, MediaPosition, PlatformConfig,
SeekDirection,
};
use souvlaki::{MediaControls, MediaMetadata, MediaPlayback, MediaPosition};
// Only `spawn_native` and `map_event` touch these, and both are compiled out
// on Windows (souvlaki's SMTC backend panics without an HWND — see `spawn`).
#[cfg(not(target_os = "windows"))]
use souvlaki::{MediaControlEvent, PlatformConfig, SeekDirection};
use crate::types::{MediaEvent, MediaIntegration, PlaybackStatus, TrackMeta};
/// Default step for a magnitude-less MPRIS-style `Seek` (5 seconds, micros).
// Only `map_event` reads it, and that is compiled out on Windows.
#[cfg(not(target_os = "windows"))]
const SEEK_STEP_MICROS: i64 = 5_000_000;
type EventCb = Arc<dyn Fn(MediaEvent) + Send + Sync>;
@@ -60,6 +63,8 @@ impl MediaIntegration for PlatformHandle {
}
}
// Only `spawn_native` installs it, and that is compiled out on Windows.
#[cfg(not(target_os = "windows"))]
fn map_event(e: MediaControlEvent) -> Option<MediaEvent> {
Some(match e {
MediaControlEvent::Play => MediaEvent::Play,
@@ -86,11 +91,32 @@ fn map_event(e: MediaControlEvent) -> Option<MediaEvent> {
}
pub fn spawn(on_event: EventCb) -> Option<PlatformHandle> {
// souvlaki's Windows backend does NOT degrade gracefully without an HWND:
// `config.hwnd.expect(...)` at souvlaki-0.8.3/src/platform/windows/mod.rs:57-59
// PANICS, and `MediaControls::new` runs inside a tokio task, so the first
// session would abort the task rather than return the Err the match below
// is written for. (The old comment on `hwnd: None` claimed the opposite;
// it was read from the comment, not the mechanism.) No HWND is wired yet
// — that is plan A-2's "SMTC real" task — so bow out here on Windows.
#[cfg(target_os = "windows")]
{
let _ = on_event;
log::warn!("[media-controls] SMTC disabled: no window handle wired yet (Phase A)");
return None;
}
#[cfg(not(target_os = "windows"))]
{
return spawn_native(on_event);
}
}
#[cfg(not(target_os = "windows"))]
fn spawn_native(on_event: EventCb) -> Option<PlatformHandle> {
let config = PlatformConfig {
dbus_name: "com.blitzfc.qbz",
display_name: "QBZ",
// macOS: unused. Windows SMTC needs the window HWND; not shipped/tested,
// so left None (init may fail there → None handle, no media controls).
// macOS: unused. Windows: unreachable — see the early return in
// `spawn` above.
hwnd: None,
};
+7 -9
View File
@@ -377,21 +377,19 @@ pub fn remove_album(album_id: String) {
/// Open the cache directory in the desktop file manager (Slint `open_folder`).
///
/// `xdg-open` / `open` by hand rather than the `rfd` used elsewhere in the
/// port: rfd opens a PICKER, and this row is "show me the folder". Same two
/// binaries the reference shells out to.
/// Not the `rfd` used elsewhere in the port: rfd opens a PICKER, and this row
/// is "show me the folder". Routed through the `open` crate like the other
/// eleven sites in this crate — the hand-rolled `xdg-open` fallback ran on
/// every non-macOS host, Windows included, where it does not exist. `open`
/// uses ShellExecuteW there and xdg-open/open on Linux/macOS, so the two
/// supported platforms behave exactly as before.
pub fn open_folder() {
crate::spawn(async move {
let Some(off) = offline_qt::get().await else {
return;
};
let path = off.get_cache_path();
let opener = if cfg!(target_os = "macos") {
"open"
} else {
"xdg-open"
};
if let Err(e) = std::process::Command::new(opener).arg(&path).spawn() {
if let Err(e) = open::that(&path) {
log::warn!("[qbz-qt] open offline folder failed: {e}");
}
});