From 421f993f82fea0260dc808969ebc06f25101105b Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:56:42 -0600 Subject: [PATCH] fix: clamp window size to screen resolution and fix physical/logical pixel mismatch Two fixes for issue #139 (window opens larger than screen): 1. Screen resolution clamping at startup: detect monitor size via xdpyinfo/xrandr and clamp saved window dimensions to 95% of screen. Prevents windows from opening larger than the display even if the DB has valid-but-too-large values. 2. Physical-to-logical pixel conversion in resize handler: the Resized event gives physical pixels but inner_size() expects logical pixels. With scale factor > 1.0, the window would grow each restart because physical values were saved and restored as logical. Now divides by scale_factor before persisting. Closes #139 --- src-tauri/src/lib.rs | 98 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fe11e366e..c2443bb16 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -331,6 +331,66 @@ fn remove_kwin_window_rule() { } } +/// Get the primary screen resolution in logical pixels. +/// Tries multiple methods: Wayland (wlr-randr, wayland-info), X11 (xdpyinfo). +/// Returns (width, height) or None if detection fails. +#[cfg(target_os = "linux")] +fn get_screen_resolution() -> Option<(f64, f64)> { + use std::process::Command; + + // Method 1: Try xdpyinfo (works on X11 and XWayland) + if let Ok(output) = Command::new("xdpyinfo").output() { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + // Look for "dimensions: WIDTHxHEIGHT pixels" + for line in stdout.lines() { + let trimmed = line.trim(); + if trimmed.starts_with("dimensions:") { + // Parse "dimensions: 3840x2160 pixels (...)" + if let Some(dims) = trimmed.split_whitespace().nth(1) { + let parts: Vec<&str> = dims.split('x').collect(); + if parts.len() == 2 { + if let (Ok(w), Ok(h)) = (parts[0].parse::(), parts[1].parse::()) { + log::info!("Screen resolution detected via xdpyinfo: {}x{}", w as u32, h as u32); + return Some((w, h)); + } + } + } + } + } + } + } + + // Method 2: Try xrandr (widely available) + if let Ok(output) = Command::new("xrandr").arg("--current").output() { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + // Look for line with " connected " and a resolution like "2560x1440+0+0" + for line in stdout.lines() { + if line.contains(" connected ") { + // Parse "DP-1 connected primary 2560x1440+0+0 ..." + for token in line.split_whitespace() { + if token.contains('x') && token.contains('+') { + // "2560x1440+0+0" + let res_part = token.split('+').next().unwrap_or(""); + let parts: Vec<&str> = res_part.split('x').collect(); + if parts.len() == 2 { + if let (Ok(w), Ok(h)) = (parts[0].parse::(), parts[1].parse::()) { + log::info!("Screen resolution detected via xrandr: {}x{}", w as u32, h as u32); + return Some((w, h)); + } + } + } + } + } + } + } + } + + log::warn!("Could not detect screen resolution"); + None +} + pub fn run() { // Load .env file if present (for development) // Silently ignore if not found (production builds use compile-time env vars) @@ -419,10 +479,34 @@ pub fn run() { window_settings.is_maximized, ); let use_system_titlebar = window_settings.use_system_titlebar; - let saved_win_width = window_settings.window_width; - let saved_win_height = window_settings.window_height; + let mut saved_win_width = window_settings.window_width; + let mut saved_win_height = window_settings.window_height; let saved_win_maximized = window_settings.is_maximized; + // Safety: clamp window size to screen resolution. + // Prevents the window from opening larger than the display (issue #139). + // This also handles corrupt DB values that pass the 200..32767 validation + // but exceed the actual monitor dimensions. + #[cfg(target_os = "linux")] + { + // Read current screen resolution from xdpyinfo or Wayland + if let Some((screen_w, screen_h)) = get_screen_resolution() { + // Leave room for taskbar/panels (90% of screen) + let max_w = screen_w * 0.95; + let max_h = screen_h * 0.95; + if saved_win_width > max_w || saved_win_height > max_h { + log::warn!( + "Window size {}x{} exceeds screen {}x{}, clamping to {}x{}", + saved_win_width as u32, saved_win_height as u32, + screen_w as u32, screen_h as u32, + max_w as u32, max_h as u32 + ); + saved_win_width = saved_win_width.min(max_w); + saved_win_height = saved_win_height.min(max_h); + } + } + } + // One-time cleanup: remove the KWin SSD window rule written by QBZ 1.1.14. // That version wrote a kwinrulesrc entry forcing server-side decorations; it // was removed in 1.1.15 due to a GTK3/WebKit heap corruption bug. We silently @@ -646,12 +730,14 @@ pub fn run() { if size.width > 0 && size.height > 0 { let maximized = win_for_events.is_maximized().unwrap_or(false); if !maximized { + // Convert physical pixels to logical pixels + // inner_size() expects logical, Resized gives physical + let scale = win_for_events.scale_factor().unwrap_or(1.0); + let logical_w = size.width as f64 / scale; + let logical_h = size.height as f64 / scale; if let Ok(guard) = store.lock() { if let Some(s) = guard.as_ref() { - let _ = s.set_window_size( - size.width as f64, - size.height as f64, - ); + let _ = s.set_window_size(logical_w, logical_h); } } }