From 833903e5c1a701edf55b94ba25cbff946d6ec93e Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 24 Apr 2026 11:30:34 +0200 Subject: [PATCH] Fix min size hint with Wayland CSD frame If a window is created with a minimum size hint and then needs client-side decorations on Wayland, the frame will actually reduce the real available size of the client application. This fix takes this into account and causes the window to correctly increase its size based on the height that the frame needs. Closes #4559 --- winit-wayland/src/window/state.rs | 64 +++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/winit-wayland/src/window/state.rs b/winit-wayland/src/window/state.rs index dcac4b69d1..4f0bc32932 100644 --- a/winit-wayland/src/window/state.rs +++ b/winit-wayland/src/window/state.rs @@ -290,12 +290,14 @@ impl WindowState { // NOTE: when using fractional scaling or wl_compositor@v6 the scaling // should be delivered before the first configure, thus apply it to // properly scale the physical sizes provided by the users. + let is_initial_configure = self.last_configure.is_none(); + if let Some(initial_size) = self.initial_size.take() { self.size = initial_size.to_logical(self.scale_factor()); self.stateless_size = self.size; } - if let Some(subcompositor) = subcompositor.as_ref().filter(|_| { + let frame_just_created = if let Some(subcompositor) = subcompositor.as_ref().filter(|_| { configure.decoration_mode == DecorationMode::Client && self.frame.is_none() && !self.csd_fails @@ -316,32 +318,61 @@ impl WindowState { // Hide the frame if we were asked to not decorate. frame.set_hidden(!self.decorate); self.frame = Some(frame); + // After the frame has been loaded, the active area may be reduced. + // Reload the min/max size hints so that they respect the frame. + self.reload_min_max_hints(); + true }, Err(err) => { warn!("Failed to create client side decorations frame: {err}"); self.csd_fails = true; + false }, } } else if configure.decoration_mode == DecorationMode::Server { // Drop the frame for server side decorations to save resources. - self.frame = None; - } + let frame_just_dropped = self.frame.take().is_some(); + if frame_just_dropped { + // Without a frame, min/max hints must no longer include border sizes. + self.reload_min_max_hints(); + } + false + } else { + false + }; let stateless = Self::is_stateless(&configure); + let current_surface_size = self.surface_size(); + // On the initial stateless configure, the compositor may echo the requested + // surface size from before the frame existed. Preserve that size instead of + // treating it as window geometry and subtracting the frame borders again. + let preserve_initial_surface_size = frame_just_created + && is_initial_configure + && stateless + && matches!( + configure.new_size, + (Some(width), Some(height)) + if width.get() == current_surface_size.width + && height.get() == current_surface_size.height + ); let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() { // Configure the window states. frame.update_state(configure.state); - match configure.new_size { - (Some(width), Some(height)) => { - let (width, height) = frame.subtract_borders(width, height); - let width = width.map(|w| w.get()).unwrap_or(1); - let height = height.map(|h| h.get()).unwrap_or(1); - ((width, height).into(), false) - }, - (..) if stateless => (self.stateless_size, true), - _ => (self.size, true), + if preserve_initial_surface_size { + (current_surface_size, false) + } else { + match configure.new_size { + (Some(width), Some(height)) => { + let (width, height) = frame.subtract_borders(width, height); + let width = width.map(|w| w.get()).unwrap_or(1); + let height = height.map(|h| h.get()).unwrap_or(1); + ((width, height).into(), false) + }, + (..) if stateless => (self.stateless_size, true), + _ => (current_surface_size, true), + } } } else { match configure.new_size { @@ -848,20 +879,19 @@ impl WindowState { size.width = size.width.max(MIN_WINDOW_SIZE.width); size.height = size.height.max(MIN_WINDOW_SIZE.height); - // Add the borders. - let size = self + let applied_size = self .frame .as_ref() .map(|frame| frame.add_borders(size.width, size.height).into()) .unwrap_or(size); self.min_surface_size = size; - self.window.set_min_size(Some(size.into())); + self.window.set_min_size(Some(applied_size.into())); } /// Set maximum inner window size. pub fn set_max_surface_size(&mut self, size: Option>) { - let size = size.map(|size| { + let applied_size = size.map(|size| { self.frame .as_ref() .map(|frame| frame.add_borders(size.width, size.height).into()) @@ -869,7 +899,7 @@ impl WindowState { }); self.max_surface_size = size; - self.window.set_max_size(size.map(Into::into)); + self.window.set_max_size(applied_size.map(Into::into)); } /// Set the CSD theme.