srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/manager
diff options
context:
space:
mode:
Diffstat (limited to 'crates/core/src/manager')
-rw-r--r--crates/core/src/manager/tests.rs40
-rw-r--r--crates/core/src/manager/windows.rs53
2 files changed, 93 insertions, 0 deletions
diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs
index e20bb74..b5a54c0 100644
--- a/crates/core/src/manager/tests.rs
+++ b/crates/core/src/manager/tests.rs
@@ -116,6 +116,46 @@
assert!(!win.size_is_provisional, "a deliberately remembered size must never be second-guessed by the client's own default");
}
+ /// A client that accepts the size placement assumed must not be moved
+ /// again: re-placing would consume another cascade step for nothing,
+ /// and the cascade wraps - measured, two windows landing on exactly
+ /// the same spot because the extra steps wrapped one back to the origin.
+ #[test]
+ fn a_window_that_keeps_the_assumed_size_is_not_placed_twice() {
+ let mut wm = wm_with_monitor();
+ let id = wm.alloc_window_id();
+ wm.add_window(Window::new(id, "w"));
+ let placed = wm.window(id).unwrap().geometry;
+ assert!(!wm.replace_with_real_size(id, (placed.width, placed.height)));
+ assert_eq!(wm.window(id).unwrap().geometry, placed);
+ }
+
+ /// And a client that chooses a different size gets placed for the size
+ /// it really is, not for the placeholder a backend guessed.
+ #[test]
+ fn a_window_that_chooses_its_own_size_is_placed_again_for_it() {
+ let mut wm = wm_with_monitor();
+ let first = wm.alloc_window_id();
+ wm.add_window(Window::new(first, "first"));
+ let second = wm.alloc_window_id();
+ wm.add_window(Window::new(second, "second"));
+ let assumed = wm.window(second).unwrap().geometry;
+ // The client turns out to be much smaller than the placeholder.
+ {
+ let w = wm.window_mut(second).unwrap();
+ w.geometry.width = 300;
+ w.geometry.height = 200;
+ }
+ // The return value is an implementation detail - it says whether
+ // anything moved, and the right position may be the one it already
+ // had. What matters is where it ends up.
+ wm.replace_with_real_size(second, (assumed.width, assumed.height));
+ let placed = wm.window(second).unwrap().geometry;
+ assert_eq!((placed.width, placed.height), (300, 200), "the real size must be kept");
+ let other = wm.window(first).unwrap().geometry;
+ assert!(!other.overlaps(&placed), "a window small enough to fit clear should not be stacked: {other:?} vs {placed:?}");
+ }
+
/// The real shape of the bug: a Wayland toplevel exists before its
/// client sends `set_app_id`, so `add_window` searched the store for the
/// empty string and every window fell through to a fresh cascade. That
diff --git a/crates/core/src/manager/windows.rs b/crates/core/src/manager/windows.rs
index 1e8a6cb..69d5e9e 100644
--- a/crates/core/src/manager/windows.rs
+++ b/crates/core/src/manager/windows.rs
@@ -411,6 +411,59 @@ impl WindowManager {
self.order.iter().filter_map(|id| self.windows.get(id))
}
+ /// Re-runs smart placement for a window that has just learned its real
+ /// size.
+ ///
+ /// `add_window` has to place a window before its client has committed
+ /// anything, so it places the placeholder size a backend guessed --
+ /// `800x600` - and not the size the window actually turns out to be.
+ /// Every placement decision was therefore made about the wrong
+ /// rectangle: measured on a 1280x800 screen, four small terminals were
+ /// all told they were 800x600, no two of those fit side by side, so
+ /// every one of them fell through to the cascade and landed on top of
+ /// the last. The windows themselves were a quarter of that size and
+ /// would have fitted four abreast.
+ ///
+ /// Called from the backend at the moment the client's real size is
+ /// adopted, which is still before its first buffer, so nothing has been
+ /// drawn at the placeholder-sized position.
+ ///
+ /// Only for a window still sitting where placement put it: a remembered
+ /// geometry, a rule, a dialog and a maximize have all already cleared
+ /// `size_is_provisional` by this point, and each is a more deliberate
+ /// decision than smart placement.
+ pub fn replace_with_real_size(&mut self, id: WindowId, placed_size: (u32, u32)) -> bool {
+ let Some(w) = self.windows.get(&id) else { return false };
+ if w.is_dialog || w.maximized || w.fullscreen {
+ return false;
+ }
+ // The client accepted the size placement already assumed, so the
+ // decision it made was about the right rectangle after all. Leaving
+ // it alone matters: re-running placement here would consume another
+ // cascade step for no reason, and the cascade wraps - measured, two
+ // windows landing on exactly the same spot because the extra steps
+ // wrapped one of them back to the origin.
+ if placed_size == (w.geometry.width, w.geometry.height) {
+ return false;
+ }
+ let (workspace, monitor_id, size) = (w.workspace, w.monitor, (w.geometry.width, w.geometry.height));
+ let layout_name = self.workspace(workspace).map(|ws| ws.layout.clone()).unwrap_or_default();
+ if layout_name == "tiling" {
+ return false;
+ }
+ let Some(monitor) = self.monitors.iter().find(|m| m.id == monitor_id).cloned() else { return false };
+ let existing: Vec<Rect> = self.windows_on_workspace(workspace).filter(|other| other.id != id).map(|other| other.geometry).collect();
+ let step = self.next_cascade_step.get();
+ self.next_cascade_step.set(step.wrapping_add(1));
+ let placed = SmartPlacement::place(&monitor, &existing, size, &self.placement, step);
+ let Some(w) = self.windows.get_mut(&id) else { return false };
+ if w.geometry == placed {
+ return false;
+ }
+ w.geometry = placed;
+ true
+ }
+
pub(super) fn windows_on_workspace(&self, workspace: WorkspaceId) -> impl Iterator<Item = &Window> {
self.windows.values().filter(move |w| w.workspace == workspace)
}