mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(mini-player): drop saved position when its monitor is gone (#280)
The mini player window persists its top-left position to `mini_player_pos.json` and re-applies it on every open. With multiple monitors that breaks in three failure modes: - Second monitor not yet enumerated when the window opens for the day (hot-plug detection race during early boot, especially on Windows). - Monitor reorder / resolution change since the last save. - Monitor unplugged. In all three the saved coords land in the void and the window appears off-screen. Validate the persisted position against `available_monitors()` before applying it: require the saved top-left plus an 80 px corner to fit inside any current monitor. If not, fall back to `default_mini_position` (bottom-right of the main window's monitor). The persisted file is left untouched so the position comes back the next time the missing monitor is present again. Validation runs in both `build_mini_player_window` (initial creation) and `open_mini_player` (re-show, where Linux WMs may re-centre).
This commit is contained in:
committed by
GitHub
parent
5c87a94170
commit
73a04e4c00
+32
-2
@@ -3012,6 +3012,27 @@ fn persist_mini_pos_throttled(app: &tauri::AppHandle, x: i32, y: i32) {
|
|||||||
write_mini_pos(app, MiniPlayerPosition { x, y });
|
write_mini_pos(app, MiniPlayerPosition { x, y });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true when the saved top-left lands inside an available monitor
|
||||||
|
/// with enough room (≥ 80 px in each axis) to leave a draggable corner of
|
||||||
|
/// the window on-screen. Used to drop persisted positions that point at a
|
||||||
|
/// monitor that is no longer enumerated (unplugged, hot-plug detection
|
||||||
|
/// race during early boot, resolution change, monitor reorder).
|
||||||
|
fn mini_position_visible(app: &tauri::AppHandle, x: i32, y: i32) -> bool {
|
||||||
|
const MIN_VISIBLE: i32 = 80;
|
||||||
|
let monitors = match app.available_monitors() {
|
||||||
|
Ok(m) if !m.is_empty() => m,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
monitors.iter().any(|m| {
|
||||||
|
let mp = m.position();
|
||||||
|
let ms = m.size();
|
||||||
|
x >= mp.x
|
||||||
|
&& y >= mp.y
|
||||||
|
&& x + MIN_VISIBLE <= mp.x + ms.width as i32
|
||||||
|
&& y + MIN_VISIBLE <= mp.y + ms.height as i32
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Default position when nothing is persisted: bottom-right of the monitor
|
/// Default position when nothing is persisted: bottom-right of the monitor
|
||||||
/// the main window sits on (falls back to primary). A 24 px logical margin
|
/// the main window sits on (falls back to primary). A 24 px logical margin
|
||||||
/// keeps it off the screen edge; +56 px on the bottom margin avoids most
|
/// keeps it off the screen edge; +56 px on the bottom margin avoids most
|
||||||
@@ -3103,7 +3124,11 @@ fn build_mini_player_window(
|
|||||||
// Resolve target position BEFORE building so the WM places the window
|
// Resolve target position BEFORE building so the WM places the window
|
||||||
// correctly from creation. Calling `set_position` after `build()` is
|
// correctly from creation. Calling `set_position` after `build()` is
|
||||||
// unreliable on several Linux WMs which re-centre hidden windows.
|
// unreliable on several Linux WMs which re-centre hidden windows.
|
||||||
|
// Drop the persisted position if it would land on a monitor that is
|
||||||
|
// no longer enumerated (unplugged second monitor, hot-plug race during
|
||||||
|
// early boot, resolution change) — fall back to the default placement.
|
||||||
let target_physical = read_mini_pos(app)
|
let target_physical = read_mini_pos(app)
|
||||||
|
.filter(|p| mini_position_visible(app, p.x, p.y))
|
||||||
.map(|p| tauri::PhysicalPosition::new(p.x, p.y))
|
.map(|p| tauri::PhysicalPosition::new(p.x, p.y))
|
||||||
.or_else(|| default_mini_position(app));
|
.or_else(|| default_mini_position(app));
|
||||||
let scale = app
|
let scale = app
|
||||||
@@ -3207,12 +3232,17 @@ fn open_mini_player(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
// again, ignoring any earlier set_position. Mark the move as
|
// again, ignoring any earlier set_position. Mark the move as
|
||||||
// programmatic so the Moved-event handler doesn't echo the
|
// programmatic so the Moved-event handler doesn't echo the
|
||||||
// intermediate centre coords back to disk.
|
// intermediate centre coords back to disk.
|
||||||
let target = read_mini_pos(&app);
|
// Drop the persisted position if its monitor is gone and fall
|
||||||
|
// back to the default placement so we don't open off-screen.
|
||||||
|
let target = read_mini_pos(&app)
|
||||||
|
.filter(|p| mini_position_visible(&app, p.x, p.y))
|
||||||
|
.map(|p| tauri::PhysicalPosition::new(p.x, p.y))
|
||||||
|
.or_else(|| default_mini_position(&app));
|
||||||
mark_mini_pos_programmatic();
|
mark_mini_pos_programmatic();
|
||||||
win.show().map_err(|e| e.to_string())?;
|
win.show().map_err(|e| e.to_string())?;
|
||||||
let _ = win.set_focus();
|
let _ = win.set_focus();
|
||||||
if let Some(p) = target {
|
if let Some(p) = target {
|
||||||
let _ = win.set_position(tauri::PhysicalPosition::new(p.x, p.y));
|
let _ = win.set_position(p);
|
||||||
}
|
}
|
||||||
if let Some(main) = app.get_webview_window("main") {
|
if let Some(main) = app.get_webview_window("main") {
|
||||||
let _ = main.minimize();
|
let _ = main.minimize();
|
||||||
|
|||||||
Reference in New Issue
Block a user