From 8459e5394ad9a7714ef9889cf259042e845a7469 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Tue, 17 Dec 2024 10:03:27 +0300 Subject: [PATCH] floating: Request size only once Let floating windows resize themselves and keep that size. --- src/layout/floating.rs | 35 ++++++------- src/layout/mod.rs | 26 +++++++++- src/window/mapped.rs | 112 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 151 insertions(+), 22 deletions(-) diff --git a/src/layout/floating.rs b/src/layout/floating.rs index ee9c70131..4565e2f22 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -367,7 +367,7 @@ impl FloatingSpace { tile.update_config(self.scale, self.options.clone()); let win = tile.window_mut(); - if win.is_pending_fullscreen() { + let size = if win.is_pending_fullscreen() { let mut size = Size::from((0, 0)); // Make sure fixed-size through window rules keeps working. @@ -380,8 +380,11 @@ impl FloatingSpace { size.h = min_size.h; } - win.request_size(size, true, None); - } + size + } else { + win.size() + }; + win.request_size_once(size, true); if activate || self.tiles.is_empty() { self.active_window_id = Some(win.id().clone()); @@ -591,16 +594,15 @@ impl FloatingSpace { win_width = ensure_min_max_size(win_width, min_size.w, max_size.w); win_width = max(1, win_width); - let win_height = win - .requested_size() - .map(|size| size.h) - // If we requested height = 0, then switch to the current height. - .filter(|h| *h != 0) - .unwrap_or_else(|| win.size().h); + let mut win_height = win.size_to_request().h; + // If we requested height = 0, then switch to the current height. + if win_height == 0 { + win_height = win.size().h; + } let win_height = ensure_min_max_size(win_height, min_size.h, max_size.h); let win_size = Size::from((win_width, win_height)); - win.request_size(win_size, animate, None); + win.request_size_once(win_size, animate); } pub fn set_window_height(&mut self, id: Option<&W::Id>, change: SizeChange, animate: bool) { @@ -622,16 +624,15 @@ impl FloatingSpace { win_height = ensure_min_max_size(win_height, min_size.h, max_size.h); win_height = max(1, win_height); - let win_width = win - .requested_size() - .map(|size| size.w) - // If we requested width = 0, then switch to the current width. - .filter(|w| *w != 0) - .unwrap_or_else(|| win.size().w); + let mut win_width = win.size_to_request().w; + // If we requested width = 0, then switch to the current width. + if win_width == 0 { + win_width = win.size().w; + } let win_width = ensure_min_max_size(win_width, min_size.w, max_size.w); let win_size = Size::from((win_width, win_height)); - win.request_size(win_size, animate, None); + win.request_size_once(win_size, animate); } fn focus_directional( diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 7378aa60a..acc5257f7 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -151,13 +151,24 @@ pub trait LayoutElement { self.render(renderer, location, scale, alpha, target).popups } + /// Requests the element to change its size. + /// + /// The size request is stored and will be continuously sent to the element on any further + /// state changes. fn request_size( &mut self, size: Size, animate: bool, transaction: Option, ); + + /// Requests the element to change size once, clearing the request afterwards. + fn request_size_once(&mut self, size: Size, animate: bool) { + self.request_size(size, animate, None); + } + fn request_fullscreen(&mut self, size: Size); + fn min_size(&self) -> Size; fn max_size(&self) -> Size; fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool; @@ -186,6 +197,17 @@ pub trait LayoutElement { /// Size previously requested through [`LayoutElement::request_size()`]. fn requested_size(&self) -> Option>; + /// Size that we will request of this window. + /// + /// This can be different from [`requested_size()`](LayoutElement::requested_size()). For + /// example, for floating windows this will generally return the current window size, rather + /// than the last size that we requested, since we want floating windows to be able to change + /// size freely. But not always: if we just requested a floating window to resize and it hasn't + /// responded to it yet, this will return the newly requested size. + fn size_to_request(&self) -> Size { + self.requested_size().unwrap_or_else(|| self.size()) + } + fn is_child_of(&self, parent: &Self) -> bool; fn rules(&self) -> &ResolvedWindowRules; @@ -3255,10 +3277,12 @@ impl Layout { size.h = min_size.h; } - win.request_size(size, true, None); + win.request_size_once(size, true); // If we're unfullscreening to floating, default to the floating layout. is_floating = tile.unfullscreen_to_floating(); + } else { + win.request_size_once(win.size(), true); } let mut data = InteractiveMoveData { diff --git a/src/window/mapped.rs b/src/window/mapped.rs index fb780fe6e..97fc95968 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -78,6 +78,8 @@ pub struct Mapped { /// Snapshot right before an animated commit. animation_snapshot: Option, + request_size_once: Option, + /// Transaction that the next configure should take part in, if any. transaction_for_next_configure: Option, @@ -140,6 +142,13 @@ impl InteractiveResize { } } +#[derive(Debug, Clone, Copy)] +enum RequestSizeOnce { + WaitingForConfigure, + WaitingForCommit(Serial), + UseWindowSize, +} + impl Mapped { pub fn new(window: Window, rules: ResolvedWindowRules, hook: HookId) -> Self { let surface = window.wl_surface().expect("no X11 support"); @@ -158,6 +167,7 @@ impl Mapped { animate_next_configure: false, animate_serials: Vec::new(), animation_snapshot: None, + request_size_once: None, transaction_for_next_configure: None, pending_transactions: Vec::new(), interactive_resize: None, @@ -516,6 +526,8 @@ impl LayoutElement for Mapped { self.animate_next_configure = true; } + self.request_size_once = None; + // Store the transaction regardless of whether the size changed. This is because with 3+ // windows in a column, the size may change among windows 1 and 2 and then right away among // windows 2 and 3, and we want all windows 1, 2 and 3 to use the last transaction, rather @@ -526,11 +538,76 @@ impl LayoutElement for Mapped { } } + fn request_size_once(&mut self, size: Size, animate: bool) { + // Assume that when calling this function, the window is going floating, so it can no + // longer participate in any transactions with other windows. + self.transaction_for_next_configure = None; + + // If our last requested size already matches the size we want to request-once, clear the + // size request right away. However, we must also check if we're unfullscreening, because + // in that case the window itself will restore its previous size upon receiving a (0, 0) + // configure, whereas what we potentially want is to unfullscreen the window into its + // fullscreen size. + let already_sent = with_toplevel_role(self.toplevel(), |role| { + let (last_sent, last_serial) = if let Some(configure) = role.pending_configures().last() + { + // FIXME: it would be more optimal to find the *oldest* pending configure that + // has the same size and fullscreen state to the last pending configure. + (&configure.state, configure.serial) + } else { + ( + role.last_acked.as_ref().unwrap(), + role.configure_serial.unwrap(), + ) + }; + + let same_size = last_sent.size.unwrap_or_default() == size; + let has_fullscreen = last_sent.states.contains(xdg_toplevel::State::Fullscreen); + (same_size && !has_fullscreen).then_some(last_serial) + }); + + if let Some(serial) = already_sent { + if let Some(current_serial) = + with_toplevel_role(self.toplevel(), |role| role.current_serial) + { + // God this triple negative... + if !current_serial.is_no_older_than(&serial) { + // We have already sent a request for the new size, but the surface has not + // committed in response yet, so we will wait for that commit. + self.request_size_once = Some(RequestSizeOnce::WaitingForCommit(serial)); + } else { + // We have already sent a request for the new size, and the surface has + // committed in response, so we will start using the current size right away. + self.request_size_once = Some(RequestSizeOnce::UseWindowSize); + } + } else { + warn!("no current serial; did the surface not ack the initial configure?"); + self.request_size_once = Some(RequestSizeOnce::UseWindowSize); + }; + return; + } + + let changed = self.toplevel().with_pending_state(|state| { + let changed = state.size != Some(size); + state.size = Some(size); + state.states.unset(xdg_toplevel::State::Fullscreen); + changed + }); + + if changed && animate { + self.animate_next_configure = true; + } + + self.request_size_once = Some(RequestSizeOnce::WaitingForConfigure); + } + fn request_fullscreen(&mut self, size: Size) { self.toplevel().with_pending_state(|state| { state.size = Some(size); state.states.set(xdg_toplevel::State::Fullscreen); }); + + self.request_size_once = None; } fn min_size(&self) -> Size { @@ -669,11 +746,19 @@ impl LayoutElement for Mapped { } fn send_pending_configure(&mut self) { + let toplevel = self.toplevel(); let _span = - trace_span!("send_pending_configure", surface = ?self.toplevel().wl_surface().id()) - .entered(); + trace_span!("send_pending_configure", surface = ?toplevel.wl_surface().id()).entered(); + + if toplevel.has_pending_changes() { + if let Some(RequestSizeOnce::UseWindowSize) = self.request_size_once { + let size = self.window.geometry().size; + toplevel.with_pending_state(|state| { + state.size = Some(size); + }); + } - if let Some(serial) = self.toplevel().send_pending_configure() { + let serial = toplevel.send_configure(); trace!(?serial, "sending configure"); if self.animate_next_configure { @@ -689,6 +774,10 @@ impl LayoutElement for Mapped { Some(InteractiveResize::WaitingForLastCommit { data, serial }) } x => x, + }; + + if let Some(RequestSizeOnce::WaitingForConfigure) = self.request_size_once { + self.request_size_once = Some(RequestSizeOnce::WaitingForCommit(serial)); } } else { self.interactive_resize = match self.interactive_resize.take() { @@ -696,7 +785,7 @@ impl LayoutElement for Mapped { // changing. Some(InteractiveResize::WaitingForLastConfigure { .. }) => None, x => x, - } + }; } self.animate_next_configure = false; @@ -720,6 +809,16 @@ impl LayoutElement for Mapped { self.toplevel().with_pending_state(|state| state.size) } + fn size_to_request(&self) -> Size { + let current_size = self.window.geometry().size; + + if let Some(RequestSizeOnce::UseWindowSize) = self.request_size_once { + return current_size; + } + + self.requested_size().unwrap_or(current_size) + } + fn is_child_of(&self, parent: &Self) -> bool { self.toplevel().parent().as_ref() == Some(parent.toplevel().wl_surface()) } @@ -779,5 +878,10 @@ impl LayoutElement for Mapped { } } + if let Some(RequestSizeOnce::WaitingForCommit(serial)) = &self.request_size_once { + if commit_serial.is_no_older_than(serial) { + self.request_size_once = Some(RequestSizeOnce::UseWindowSize); + } + } } }