From 67c5df0c42eb034ab28a74c9da9345b43c50c73e Mon Sep 17 00:00:00 2001 From: juafeis Date: Tue, 14 Jul 2026 16:29:41 +0200 Subject: [PATCH 1/3] Fix FloatBar minimized geometry restore --- .../src-tauri/src/floatbar/window.rs | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/desktop-tauri/src-tauri/src/floatbar/window.rs b/apps/desktop-tauri/src-tauri/src/floatbar/window.rs index 8b722ffbef..bab3031ad9 100644 --- a/apps/desktop-tauri/src-tauri/src/floatbar/window.rs +++ b/apps/desktop-tauri/src-tauri/src/floatbar/window.rs @@ -13,6 +13,22 @@ const FLOATBAR_DEFAULT_WIDTH_H: f64 = 360.0; const FLOATBAR_DEFAULT_HEIGHT_H: f64 = 36.0; const FLOATBAR_DEFAULT_WIDTH_V: f64 = 80.0; const FLOATBAR_DEFAULT_HEIGHT_V: f64 = 280.0; +const WINDOWS_MINIMIZED_COORDINATE: i32 = -32_000; +const WINDOWS_MAX_SCALE_FACTOR: i32 = 5; + +/// Windows parks minimized windows at (-32000, -32000). Geometry is stored in +/// logical pixels, so HiDPI scaling can turn that sentinel into an equal pair +/// as small as (-6400, -6400) at the maximum supported 500% scale. +fn is_windows_minimized_position(x: i32, y: i32) -> bool { + let scaled_limit = WINDOWS_MINIMIZED_COORDINATE / WINDOWS_MAX_SCALE_FACTOR; + (WINDOWS_MINIMIZED_COORDINATE..=scaled_limit).contains(&x) + && (WINDOWS_MINIMIZED_COORDINATE..=scaled_limit).contains(&y) + && x.abs_diff(y) <= 1 +} + +fn should_remember_position(is_minimized: bool, x: i32, y: i32) -> bool { + !is_minimized && !is_windows_minimized_position(x, y) +} /// Initial dimensions (logical pixels) for the floating bar given an /// orientation string. Unknown values fall back to horizontal so callers @@ -83,7 +99,9 @@ pub fn show( // Restore prior geometry if we have one. Otherwise, taskbar style opens // near the bottom while the original floating style keeps its top-center // placement. - if let Some(g) = geometry_store::load_entry(FLOATBAR_LABEL) { + if let Some(g) = geometry_store::load_entry(FLOATBAR_LABEL) + .filter(|g| !is_windows_minimized_position(g.x, g.y)) + { let _ = win.set_position(LogicalPosition::new(g.x as f64, g.y as f64)); if let (Some(w), Some(h)) = (g.width, g.height) { let _ = win.set_size(LogicalSize::new(w as f64, h as f64)); @@ -132,6 +150,10 @@ pub fn remember_geometry>(window: &M) { let Ok(pos) = window.outer_position() else { return; }; + let is_minimized = window.is_minimized().unwrap_or(false); + if !should_remember_position(is_minimized, pos.x, pos.y) { + return; + } let Ok(size) = window.outer_size() else { return; }; @@ -156,6 +178,7 @@ pub trait WindowGeometry { fn outer_position(&self) -> tauri::Result>; fn outer_size(&self) -> tauri::Result>; fn scale_factor(&self) -> tauri::Result; + fn is_minimized(&self) -> tauri::Result; } impl WindowGeometry for tauri::WebviewWindow { @@ -168,6 +191,9 @@ impl WindowGeometry for tauri::WebviewWindow { fn scale_factor(&self) -> tauri::Result { tauri::WebviewWindow::scale_factor(self) } + fn is_minimized(&self) -> tauri::Result { + tauri::WebviewWindow::is_minimized(self) + } } impl WindowGeometry for tauri::Window { @@ -180,6 +206,9 @@ impl WindowGeometry for tauri::Window { fn scale_factor(&self) -> tauri::Result { tauri::Window::scale_factor(self) } + fn is_minimized(&self) -> tauri::Result { + tauri::Window::is_minimized(self) + } } /// Resize the floatbar to the given logical dimensions and re-assert the @@ -396,4 +425,25 @@ mod tests { (FLOATBAR_DEFAULT_WIDTH_H, FLOATBAR_DEFAULT_HEIGHT_H) ); } + + #[test] + fn windows_minimized_positions_are_not_restored() { + assert!(is_windows_minimized_position(-32_000, -32_000)); + assert!(is_windows_minimized_position(-16_000, -16_000)); + assert!(is_windows_minimized_position(-6_400, -6_400)); + } + + #[test] + fn legitimate_negative_monitor_positions_are_preserved() { + assert!(!is_windows_minimized_position(-3_840, 0)); + assert!(!is_windows_minimized_position(-8_000, -4_000)); + assert!(!is_windows_minimized_position(-5_000, -5_000)); + } + + #[test] + fn minimized_or_parked_positions_are_not_remembered() { + assert!(!should_remember_position(true, 100, 100)); + assert!(!should_remember_position(false, -32_000, -32_000)); + assert!(should_remember_position(false, -3_840, 100)); + } } From b64feff6e53ef2f0df159409c1c3a2df401ba075 Mon Sep 17 00:00:00 2001 From: juafeis Date: Tue, 14 Jul 2026 23:49:49 +0200 Subject: [PATCH 2/3] Use physical coordinates for FloatBar guard --- .../src-tauri/src/floatbar/window.rs | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/desktop-tauri/src-tauri/src/floatbar/window.rs b/apps/desktop-tauri/src-tauri/src/floatbar/window.rs index bab3031ad9..26163a4228 100644 --- a/apps/desktop-tauri/src-tauri/src/floatbar/window.rs +++ b/apps/desktop-tauri/src-tauri/src/floatbar/window.rs @@ -14,19 +14,15 @@ const FLOATBAR_DEFAULT_HEIGHT_H: f64 = 36.0; const FLOATBAR_DEFAULT_WIDTH_V: f64 = 80.0; const FLOATBAR_DEFAULT_HEIGHT_V: f64 = 280.0; const WINDOWS_MINIMIZED_COORDINATE: i32 = -32_000; -const WINDOWS_MAX_SCALE_FACTOR: i32 = 5; -/// Windows parks minimized windows at (-32000, -32000). Geometry is stored in -/// logical pixels, so HiDPI scaling can turn that sentinel into an equal pair -/// as small as (-6400, -6400) at the maximum supported 500% scale. +/// Windows parks minimized windows at the exact physical coordinate pair +/// (-32000, -32000). Keep this strict so legitimate negative monitor origins +/// are never mistaken for minimized geometry. fn is_windows_minimized_position(x: i32, y: i32) -> bool { - let scaled_limit = WINDOWS_MINIMIZED_COORDINATE / WINDOWS_MAX_SCALE_FACTOR; - (WINDOWS_MINIMIZED_COORDINATE..=scaled_limit).contains(&x) - && (WINDOWS_MINIMIZED_COORDINATE..=scaled_limit).contains(&y) - && x.abs_diff(y) <= 1 + x == WINDOWS_MINIMIZED_COORDINATE && y == WINDOWS_MINIMIZED_COORDINATE } -fn should_remember_position(is_minimized: bool, x: i32, y: i32) -> bool { +fn should_remember_physical_position(is_minimized: bool, x: i32, y: i32) -> bool { !is_minimized && !is_windows_minimized_position(x, y) } @@ -151,7 +147,7 @@ pub fn remember_geometry>(window: &M) { return; }; let is_minimized = window.is_minimized().unwrap_or(false); - if !should_remember_position(is_minimized, pos.x, pos.y) { + if !should_remember_physical_position(is_minimized, pos.x, pos.y) { return; } let Ok(size) = window.outer_size() else { @@ -427,23 +423,26 @@ mod tests { } #[test] - fn windows_minimized_positions_are_not_restored() { + fn windows_minimized_position_is_not_restored() { assert!(is_windows_minimized_position(-32_000, -32_000)); - assert!(is_windows_minimized_position(-16_000, -16_000)); - assert!(is_windows_minimized_position(-6_400, -6_400)); } #[test] fn legitimate_negative_monitor_positions_are_preserved() { assert!(!is_windows_minimized_position(-3_840, 0)); - assert!(!is_windows_minimized_position(-8_000, -4_000)); - assert!(!is_windows_minimized_position(-5_000, -5_000)); + assert!(!is_windows_minimized_position(-8_000, -8_000)); + assert!(!is_windows_minimized_position(-16_000, -16_000)); } #[test] - fn minimized_or_parked_positions_are_not_remembered() { - assert!(!should_remember_position(true, 100, 100)); - assert!(!should_remember_position(false, -32_000, -32_000)); - assert!(should_remember_position(false, -3_840, 100)); + fn minimized_or_parked_physical_positions_are_not_remembered() { + assert!(!should_remember_physical_position(true, 100, 100)); + assert!(!should_remember_physical_position(false, -32_000, -32_000)); + assert!(should_remember_physical_position(false, -3_840, 100)); + } + + #[test] + fn physical_multi_monitor_origin_is_remembered() { + assert!(should_remember_physical_position(false, -8_000, -8_000)); } } From f4ed1db20decd33d4b6f8795c0aa4684e5d9dfbe Mon Sep 17 00:00:00 2001 From: juafeis Date: Wed, 15 Jul 2026 01:57:03 +0200 Subject: [PATCH 3/3] Recover FloatBar after monitor disconnect --- .../src-tauri/src/floatbar/mod.rs | 10 +- .../src-tauri/src/floatbar/window.rs | 186 +++++++++++++++++- 2 files changed, 192 insertions(+), 4 deletions(-) diff --git a/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs b/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs index ea38db93f2..2cafd76a2a 100644 --- a/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs +++ b/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs @@ -39,11 +39,14 @@ pub fn handle_window_event(window: &tauri::Window, event: &tauri::WindowEvent) - return false; } match event { - tauri::WindowEvent::Moved(_) - | tauri::WindowEvent::Resized(_) - | tauri::WindowEvent::CloseRequested { .. } => { + tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => { + let style = Settings::load().float_bar_style; + if window::ensure_visible_on_active_monitor(window, &style) { + return true; + } window::remember_geometry(window); } + tauri::WindowEvent::CloseRequested { .. } => window::remember_geometry(window), _ => {} } true @@ -84,6 +87,7 @@ pub fn apply_state(app: &tauri::AppHandle, settings: &Settings) { } else if !settings.float_bar_enabled && open { let _ = window::hide(app); } else if let Some(w) = app.get_webview_window(FLOATBAR_LABEL) { + window::ensure_visible_on_active_monitor(&w, &settings.float_bar_style); window::apply_no_activate(&w); window::apply_opacity(&w, settings.float_bar_opacity); window::apply_click_through(&w, settings.float_bar_click_through); diff --git a/apps/desktop-tauri/src-tauri/src/floatbar/window.rs b/apps/desktop-tauri/src-tauri/src/floatbar/window.rs index 26163a4228..82057264ff 100644 --- a/apps/desktop-tauri/src-tauri/src/floatbar/window.rs +++ b/apps/desktop-tauri/src-tauri/src/floatbar/window.rs @@ -3,7 +3,7 @@ //! Tauri window labeled `floatbar`, independent of the main surface //! state machine. -use tauri::{LogicalPosition, LogicalSize, Manager, WebviewUrl}; +use tauri::{LogicalPosition, LogicalSize, Manager, PhysicalPosition, PhysicalSize, WebviewUrl}; use crate::geometry_store; @@ -26,6 +26,106 @@ fn should_remember_physical_position(is_minimized: bool, x: i32, y: i32) -> bool !is_minimized && !is_windows_minimized_position(x, y) } +fn physical_rects_intersect( + first_position: PhysicalPosition, + first_size: PhysicalSize, + second_position: PhysicalPosition, + second_size: PhysicalSize, +) -> bool { + let first_left = i64::from(first_position.x); + let first_top = i64::from(first_position.y); + let first_right = first_left + i64::from(first_size.width); + let first_bottom = first_top + i64::from(first_size.height); + let second_left = i64::from(second_position.x); + let second_top = i64::from(second_position.y); + let second_right = second_left + i64::from(second_size.width); + let second_bottom = second_top + i64::from(second_size.height); + + first_left < second_right + && first_right > second_left + && first_top < second_bottom + && first_bottom > second_top +} + +fn fallback_physical_position( + work_area_position: PhysicalPosition, + work_area_size: PhysicalSize, + window_size: PhysicalSize, + scale_factor: f64, + style: &str, +) -> PhysicalPosition { + let available_width = work_area_size.width.saturating_sub(window_size.width); + let available_height = work_area_size.height.saturating_sub(window_size.height); + let scale_factor = if scale_factor.is_finite() && scale_factor > 0.0 { + scale_factor + } else { + 1.0 + }; + let edge_padding = (8.0 * scale_factor).round() as u32; + let x = work_area_position + .x + .saturating_add((available_width / 2).min(i32::MAX as u32) as i32); + let y_offset = if style == "taskbar" { + available_height.saturating_sub(edge_padding) + } else { + edge_padding.min(available_height) + }; + let y = work_area_position + .y + .saturating_add(y_offset.min(i32::MAX as u32) as i32); + + PhysicalPosition::new(x, y) +} + +/// Move a FloatBar that no longer intersects an active monitor onto the +/// primary monitor. This covers both stale geometry from a disconnected +/// display and Windows parking the live window at (-32000, -32000) when a +/// monitor is powered off. +pub(super) fn ensure_visible_on_active_monitor>( + window: &M, + style: &str, +) -> bool { + let Ok(position) = window.outer_position() else { + return false; + }; + let Ok(size) = window.outer_size() else { + return false; + }; + let Ok(monitors) = window.available_monitors() else { + return false; + }; + if monitors.iter().any(|monitor| { + let work_area = monitor.work_area(); + physical_rects_intersect(position, size, work_area.position, work_area.size) + }) { + return false; + } + + let target_monitor = window + .primary_monitor() + .ok() + .flatten() + .or_else(|| monitors.into_iter().next()); + let Some(target_monitor) = target_monitor else { + return false; + }; + let work_area = target_monitor.work_area(); + let target = fallback_physical_position( + work_area.position, + work_area.size, + size, + target_monitor.scale_factor(), + style, + ); + + if window.is_minimized().unwrap_or(false) + || is_windows_minimized_position(position.x, position.y) + { + let _ = window.unminimize(); + } + window.set_physical_position(target).is_ok() +} + /// Initial dimensions (logical pixels) for the floating bar given an /// orientation string. Unknown values fall back to horizontal so callers /// don't have to pre-validate. @@ -61,6 +161,7 @@ pub fn show( apply_opacity(&window, opacity); apply_click_through(&window, click_through); apply_always_on_top(&window); + ensure_visible_on_active_monitor(&window, style); window.show().map_err(|e| e.to_string())?; apply_always_on_top(&window); return Ok(()); @@ -117,6 +218,8 @@ pub fn show( let _ = win.set_position(LogicalPosition::new(x.max(mon_x), y.max(mon_y))); } + ensure_visible_on_active_monitor(&win, style); + apply_no_activate(&win); apply_opacity(&win, opacity); apply_click_through(&win, click_through); @@ -175,6 +278,10 @@ pub trait WindowGeometry { fn outer_size(&self) -> tauri::Result>; fn scale_factor(&self) -> tauri::Result; fn is_minimized(&self) -> tauri::Result; + fn primary_monitor(&self) -> tauri::Result>; + fn available_monitors(&self) -> tauri::Result>; + fn unminimize(&self) -> tauri::Result<()>; + fn set_physical_position(&self, position: PhysicalPosition) -> tauri::Result<()>; } impl WindowGeometry for tauri::WebviewWindow { @@ -190,6 +297,18 @@ impl WindowGeometry for tauri::WebviewWindow { fn is_minimized(&self) -> tauri::Result { tauri::WebviewWindow::is_minimized(self) } + fn primary_monitor(&self) -> tauri::Result> { + tauri::WebviewWindow::primary_monitor(self) + } + fn available_monitors(&self) -> tauri::Result> { + tauri::WebviewWindow::available_monitors(self) + } + fn unminimize(&self) -> tauri::Result<()> { + tauri::WebviewWindow::unminimize(self) + } + fn set_physical_position(&self, position: PhysicalPosition) -> tauri::Result<()> { + tauri::WebviewWindow::set_position(self, position) + } } impl WindowGeometry for tauri::Window { @@ -205,6 +324,18 @@ impl WindowGeometry for tauri::Window { fn is_minimized(&self) -> tauri::Result { tauri::Window::is_minimized(self) } + fn primary_monitor(&self) -> tauri::Result> { + tauri::Window::primary_monitor(self) + } + fn available_monitors(&self) -> tauri::Result> { + tauri::Window::available_monitors(self) + } + fn unminimize(&self) -> tauri::Result<()> { + tauri::Window::unminimize(self) + } + fn set_physical_position(&self, position: PhysicalPosition) -> tauri::Result<()> { + tauri::Window::set_position(self, position) + } } /// Resize the floatbar to the given logical dimensions and re-assert the @@ -445,4 +576,57 @@ mod tests { fn physical_multi_monitor_origin_is_remembered() { assert!(should_remember_physical_position(false, -8_000, -8_000)); } + + #[test] + fn window_must_intersect_an_active_monitor_to_be_visible() { + let window_size = PhysicalSize::new(211, 40); + let monitor_size = PhysicalSize::new(1_920, 1_032); + + assert!(physical_rects_intersect( + PhysicalPosition::new(780, 8), + window_size, + PhysicalPosition::new(0, 0), + monitor_size, + )); + assert!(!physical_rects_intersect( + PhysicalPosition::new(-32_000, -32_000), + window_size, + PhysicalPosition::new(0, 0), + monitor_size, + )); + assert!(physical_rects_intersect( + PhysicalPosition::new(-3_840, 8), + window_size, + PhysicalPosition::new(-3_840, 0), + monitor_size, + )); + } + + #[test] + fn disconnected_monitor_position_falls_back_to_primary_work_area() { + let work_area_position = PhysicalPosition::new(0, 0); + let work_area_size = PhysicalSize::new(1_920, 1_032); + let window_size = PhysicalSize::new(211, 40); + + assert_eq!( + fallback_physical_position( + work_area_position, + work_area_size, + window_size, + 1.0, + "floating", + ), + PhysicalPosition::new(854, 8), + ); + assert_eq!( + fallback_physical_position( + work_area_position, + work_area_size, + window_size, + 1.0, + "taskbar", + ), + PhysicalPosition::new(854, 984), + ); + } }