mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix: player bar CSS + Windows media keys HWND (v1.17.2)
- layout: overflow:hidden on .app-shell + min-height:0 on sidebar/main/queue prevents player bar from being pushed off-screen when window is resized below OS minHeight constraint (ignored by some Linux WMs) - lib.rs: pass main window HWND to souvlaki PlatformConfig on Windows so SMTC hooks into the existing Win32 message loop instead of creating its own — fixes media keys on Windows without crashing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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/),
|
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).
|
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
|
## [1.17.1] - 2026-03-25
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "psysonic",
|
"name": "psysonic",
|
||||||
"version": "1.17.1",
|
"version": "1.17.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Psychotoxic <psychotoxic@gmx.de>
|
# Maintainer: Psychotoxic <psychotoxic@gmx.de>
|
||||||
pkgname=psysonic
|
pkgname=psysonic
|
||||||
pkgver=1.17.1
|
pkgver=1.17.2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Desktop music player for Subsonic API-compatible servers (Navidrome, Gonic, etc.)"
|
pkgdesc="Desktop music player for Subsonic API-compatible servers (Navidrome, Gonic, etc.)"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
|
|||||||
+63
-48
@@ -252,61 +252,76 @@ pub fn run() {
|
|||||||
.build(app)?;
|
.build(app)?;
|
||||||
|
|
||||||
// ── MPRIS2 / OS media controls via souvlaki ──────────────────
|
// ── 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};
|
use souvlaki::{MediaControlEvent, MediaControls, PlatformConfig};
|
||||||
|
|
||||||
// On Linux, souvlaki requires a live D-Bus session.
|
// Collect pre-conditions and the platform-specific HWND.
|
||||||
#[cfg(target_os = "linux")]
|
// Returns None early (with a log) on any unrecoverable condition
|
||||||
let dbus_ok = std::env::var("DBUS_SESSION_BUS_ADDRESS")
|
// so app.manage() always executes exactly once at the bottom.
|
||||||
.map(|v| !v.is_empty())
|
let maybe_controls: Option<MediaControls> = (|| {
|
||||||
.unwrap_or(false);
|
// Linux: requires a live D-Bus session.
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(target_os = "linux")]
|
||||||
let dbus_ok = true;
|
{
|
||||||
|
let dbus_ok = std::env::var("DBUS_SESSION_BUS_ADDRESS")
|
||||||
if !dbus_ok {
|
.map(|v| !v.is_empty())
|
||||||
eprintln!("[Psysonic] No D-Bus session — MPRIS media controls disabled");
|
.unwrap_or(false);
|
||||||
app.manage(MprisControls::new(None));
|
if !dbus_ok {
|
||||||
} else {
|
eprintln!("[Psysonic] No D-Bus session — MPRIS media controls disabled");
|
||||||
|
return None;
|
||||||
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:?}");
|
|
||||||
}
|
}
|
||||||
Some(controls)
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
eprintln!("[Psysonic] Could not create media controls: {e:?}");
|
// Windows: souvlaki SMTC must hook into the existing Win32
|
||||||
None
|
// 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));
|
app.manage(MprisControls::new(maybe_controls));
|
||||||
} // end dbus_ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Psysonic",
|
"productName": "Psysonic",
|
||||||
"version": "1.17.1",
|
"version": "1.17.2",
|
||||||
"identifier": "dev.psysonic.player",
|
"identifier": "dev.psysonic.player",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
"sidebar main queue"
|
"sidebar main queue"
|
||||||
"player player player";
|
"player player player";
|
||||||
height: 100vh;
|
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);
|
background: var(--bg-app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +45,7 @@
|
|||||||
border-right: 1px solid var(--border-subtle);
|
border-right: 1px solid var(--border-subtle);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
min-height: 0; /* allow 1fr row to shrink freely */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-brand {
|
.sidebar-brand {
|
||||||
@@ -301,6 +308,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0; /* allow 1fr row to shrink freely */
|
||||||
background: var(--bg-app);
|
background: var(--bg-app);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
@@ -635,6 +643,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0; /* allow 1fr row to shrink freely */
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-header {
|
.queue-header {
|
||||||
|
|||||||
Reference in New Issue
Block a user