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
This commit is contained in:
1 file changed
+92
-6
+92
-6
@@ -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::<f64>(), parts[1].parse::<f64>()) {
|
||||
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::<f64>(), parts[1].parse::<f64>()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user