diff --git a/CHANGELOG.md b/CHANGELOG.md index 55662f44..bb1fc6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.17.2] - 2026-03-26 + +### Fixed + +- **Player bar disappears when window is resized small**: On Linux (and some Windows configurations), the window manager ignores the `minHeight` constraint, allowing the window to be dragged smaller than intended. The CSS grid's `1fr` row has an implicit `min-height: auto`, meaning it refuses to shrink below the min-content height of the sidebar/main/queue children — this pushed the total grid height beyond `100vh` and scrolled the player bar out of view. Fixed by adding `min-height: 0` to `.sidebar`, `.main-content`, and `.queue-panel`, and `overflow: hidden` to `.app-shell` as a safety net. +- **Media keys on Windows (SMTC)**: souvlaki's Windows backend requires a valid Win32 HWND to hook into the existing message loop rather than spinning up its own. Passing `hwnd: None` caused a crash on startup (v1.17.0). Now retrieves the main window's HWND via `app.get_webview_window("main").hwnd()` and passes it to `PlatformConfig`. Falls back to disabled gracefully if the HWND cannot be obtained. + +--- + ## [1.17.1] - 2026-03-25 ### Fixed diff --git a/package.json b/package.json index 35b88341..596adf80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psysonic", - "version": "1.17.1", + "version": "1.17.2", "private": true, "scripts": { "dev": "vite", diff --git a/packages/aur/PKGBUILD b/packages/aur/PKGBUILD index 3bafe26d..cefa3783 100644 --- a/packages/aur/PKGBUILD +++ b/packages/aur/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Psychotoxic pkgname=psysonic -pkgver=1.17.1 +pkgver=1.17.2 pkgrel=1 pkgdesc="Desktop music player for Subsonic API-compatible servers (Navidrome, Gonic, etc.)" arch=('x86_64') diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ae728c25..2b271675 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -252,61 +252,76 @@ pub fn run() { .build(app)?; // ── MPRIS2 / OS media controls via souvlaki ────────────────── - // Windows: souvlaki SMTC init requires a valid HWND and a running - // COM message loop, neither of which is available this early in - // setup(). Disabled on Windows until we can defer init post-window. - // mpris_set_metadata / mpris_set_playback no-op via the None branch. - #[cfg(target_os = "windows")] - app.manage(MprisControls::new(None)); - - #[cfg(not(target_os = "windows"))] { use souvlaki::{MediaControlEvent, MediaControls, PlatformConfig}; - // On Linux, souvlaki requires a live D-Bus session. - #[cfg(target_os = "linux")] - let dbus_ok = std::env::var("DBUS_SESSION_BUS_ADDRESS") - .map(|v| !v.is_empty()) - .unwrap_or(false); - #[cfg(not(target_os = "linux"))] - let dbus_ok = true; - - if !dbus_ok { - eprintln!("[Psysonic] No D-Bus session — MPRIS media controls disabled"); - app.manage(MprisControls::new(None)); - } else { - - let config = PlatformConfig { - dbus_name: "psysonic", - display_name: "Psysonic", - hwnd: None, - }; - - let maybe_controls = match MediaControls::new(config) { - Ok(mut controls) => { - let app_handle = app.handle().clone(); - if let Err(e) = controls.attach(move |event: MediaControlEvent| { - let event_name = match event { - MediaControlEvent::Toggle => "media:play-pause", - MediaControlEvent::Play => "media:play-pause", - MediaControlEvent::Pause => "media:play-pause", - MediaControlEvent::Next => "media:next", - MediaControlEvent::Previous => "media:prev", - _ => return, - }; - let _ = app_handle.emit(event_name, ()); - }) { - eprintln!("[Psysonic] Failed to attach media controls: {e:?}"); + // Collect pre-conditions and the platform-specific HWND. + // Returns None early (with a log) on any unrecoverable condition + // so app.manage() always executes exactly once at the bottom. + let maybe_controls: Option = (|| { + // Linux: requires a live D-Bus session. + #[cfg(target_os = "linux")] + { + let dbus_ok = std::env::var("DBUS_SESSION_BUS_ADDRESS") + .map(|v| !v.is_empty()) + .unwrap_or(false); + if !dbus_ok { + eprintln!("[Psysonic] No D-Bus session — MPRIS media controls disabled"); + return None; } - Some(controls) } - Err(e) => { - eprintln!("[Psysonic] Could not create media controls: {e:?}"); - None + + // Windows: souvlaki SMTC must hook into the existing Win32 + // message loop rather than spinning up its own. Pass the + // main window's HWND so it can do so. If we can't get one, + // skip init (no crash, just no media overlay). + #[cfg(target_os = "windows")] + let hwnd = { + use tauri::Manager; + let h = app.get_webview_window("main") + .and_then(|w| w.hwnd().ok()) + .map(|h| h.0 as *mut std::ffi::c_void); + if h.is_none() { + eprintln!("[Psysonic] Could not get HWND — Windows media controls disabled"); + return None; + } + h + }; + #[cfg(not(target_os = "windows"))] + let hwnd: Option<*mut std::ffi::c_void> = None; + + let config = PlatformConfig { + dbus_name: "psysonic", + display_name: "Psysonic", + hwnd, + }; + + match MediaControls::new(config) { + Ok(mut controls) => { + let app_handle = app.handle().clone(); + if let Err(e) = controls.attach(move |event: MediaControlEvent| { + let event_name = match event { + MediaControlEvent::Toggle => "media:play-pause", + MediaControlEvent::Play => "media:play-pause", + MediaControlEvent::Pause => "media:play-pause", + MediaControlEvent::Next => "media:next", + MediaControlEvent::Previous => "media:prev", + _ => return, + }; + let _ = app_handle.emit(event_name, ()); + }) { + eprintln!("[Psysonic] Failed to attach media controls: {e:?}"); + } + Some(controls) + } + Err(e) => { + eprintln!("[Psysonic] Could not create media controls: {e:?}"); + None + } } - }; + })(); + app.manage(MprisControls::new(maybe_controls)); - } // end dbus_ok } Ok(()) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f744018b..a787149d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Psysonic", - "version": "1.17.1", + "version": "1.17.2", "identifier": "dev.psysonic.player", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/styles/layout.css b/src/styles/layout.css index 0f0675ee..aa4958bc 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -9,6 +9,12 @@ "sidebar main queue" "player player player"; height: 100vh; + /* overflow: hidden keeps the player bar pinned at the bottom even when the + window is dragged below the OS minHeight constraint (ignored on some + Linux WMs/compositors). Without this, the 1fr row's implicit auto + min-height can push the grid taller than 100vh, scrolling the player + bar out of view. */ + overflow: hidden; background: var(--bg-app); } @@ -39,6 +45,7 @@ border-right: 1px solid var(--border-subtle); position: relative; z-index: 2; + min-height: 0; /* allow 1fr row to shrink freely */ } .sidebar-brand { @@ -301,6 +308,7 @@ flex-direction: column; overflow: hidden; min-width: 0; + min-height: 0; /* allow 1fr row to shrink freely */ background: var(--bg-app); z-index: 1; } @@ -635,6 +643,7 @@ flex-direction: column; overflow: hidden; min-width: 0; + min-height: 0; /* allow 1fr row to shrink freely */ } .queue-header {