srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state/geometry.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-12-01 19:28:00 +0200
committersrdusr <[email protected]>2025-12-01 19:28:00 +0200
commit9e88d3e3f86a1248b72fdb9bab59d9936a3323a6 (patch)
tree4e96d180377b7b450fc60d236ceb5090d19efa0e /crates/wayland/src/state/geometry.rs
parentd48dfec46a6502d23648053916c2af91ee2a6f2e (diff)
downloadsrdwm-9e88d3e3f86a1248b72fdb9bab59d9936a3323a6.tar.gz
srdwm-9e88d3e3f86a1248b72fdb9bab59d9936a3323a6.zip
Let a new window pick its own size instead of forcing a guessed placeholder
Root-caused "windows spawn small and square, not remembering placement or size": new_managed_window hardcoded a fresh toplevel's geometry to 800x632 before the client had said anything about its own preferred size, and sync_geometry forced that guess onto the client's very first xdg_toplevel::configure unconditionally. Per xdg-shell, size: None on that first configure is how every mainstream compositor lets a client pick its own natural size instead; this one never did, so every app converged on the same placeholder rectangle regardless of what it would have chosen. Window::size_is_provisional marks a size that really was just the guess (not a remembered geometry, a rule's explicit geometry action, or a maximize/phone-mode fill, none of which are guesses). sync_geometry sends size: None for such a window's first configure; a new adopt_provisional_size, called from the commit handler, adopts the client's own real first size into Window::geometry the moment it commits one, clamping only position so a bigger-than-guessed window can't hang off its monitor's edge. Live-verified in a nested compositor: a zenity dialog now renders at its own compact natural size instead of being stretched to the old guess.
Diffstat (limited to 'crates/wayland/src/state/geometry.rs')
-rw-r--r--crates/wayland/src/state/geometry.rs58
1 files changed, 57 insertions, 1 deletions
diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs
index 3d201dd..b8629bc 100644
--- a/crates/wayland/src/state/geometry.rs
+++ b/crates/wayland/src/state/geometry.rs
@@ -392,10 +392,19 @@ impl CompState {
!caught_up && sent_at.elapsed() < CONFIGURE_THROTTLE_TIMEOUT
});
if size_changed && !throttled {
+ // See `Window::size_is_provisional`'s own doc comment:
+ // the very first configure for a window whose size was
+ // never a real decision - just `Window::new`'s/a
+ // backend's own hardcoded guess - tells the client to
+ // pick its own size (`state.size = None`) instead of
+ // forcing this one on it. `last_synced_size` having no
+ // entry yet is exactly "this is that first configure";
+ // checked before the `insert` just below overwrites it.
+ let let_client_choose = self.provisional_size.contains(&id) && !self.last_synced_size.contains_key(&id);
self.last_synced_size.insert(id, size);
self.pending_size_configure.insert(id, (size, Instant::now()));
top.with_pending_state(|state| {
- state.size = Some(size.into());
+ state.size = if let_client_choose { None } else { Some(size.into()) };
// No configure from this compositor, ever, set any
// `xdg_toplevel` state bit at all before this --
// confirmed by grepping the whole crate for
@@ -507,4 +516,51 @@ impl CompState {
self.resync_stacking_order();
}
}
+
+ /// Called from `CompositorHandler::commit`, right after `w.on_commit()`
+ /// - the first time a window still in `provisional_size` commits a
+ /// real, non-empty buffer, adopts the client's own chosen content size
+ /// into `Window::geometry` instead of leaving `add_window`'s guessed
+ /// placeholder in place. A no-op once `provisional_size` no longer
+ /// names this window (the ordinary case, checked first, so every other
+ /// commit pays only one `HashSet` lookup).
+ ///
+ /// Position is left exactly where `SmartPlacement` put it - only
+ /// clamped so a client that picked a bigger size than the guess can't
+ /// end up hanging off its monitor's right/bottom edge - since the
+ /// guessed size was only ever wrong about *size*; the cascade/grid
+ /// position it computed is still a perfectly good place for a window
+ /// of any size to open.
+ pub(crate) fn adopt_provisional_size(&mut self, id: WindowId) {
+ if !self.provisional_size.contains(&id) {
+ return;
+ }
+ let Some(dwindow) = self.id_to_window.get(&id) else { return };
+ // Logical points, same as `xdg_toplevel::configure`'s own `size`
+ // (see `sync_geometry`'s doc comment on that) - converted to this
+ // compositor's physical-pixel `Rect` space below via the window's
+ // own monitor scale, the same conversion `sync_geometry` does in
+ // reverse.
+ let content = dwindow.geometry();
+ if content.size.w <= 0 || content.size.h <= 0 {
+ // Compositor/role-only commit, no real buffer attached yet --
+ // wait for the commit that actually has one.
+ return;
+ }
+ self.provisional_size.remove(&id);
+ let mut wm = self.wm.borrow_mut();
+ let Some(w) = wm.window(id) else { return };
+ let scale = wm.monitors().iter().find(|m| m.id == w.monitor).map(|m| m.scale).unwrap_or(1.0);
+ let monitor_geometry = wm.monitors().iter().find(|m| m.id == w.monitor).map(|m| m.geometry);
+ let band = if w.decorated { TITLEBAR_HEIGHT } else { 0 };
+ let width = ((content.size.w as f64 * scale).round() as u32).max(srdwm_core::placement::MIN_WINDOW_WIDTH);
+ let height = ((content.size.h as f64 * scale).round() as u32).max(srdwm_core::placement::MIN_WINDOW_HEIGHT) + band;
+ let Some(w) = wm.window_mut(id) else { return };
+ w.geometry.width = width;
+ w.geometry.height = height;
+ if let Some(monitor) = monitor_geometry {
+ w.geometry.x = w.geometry.x.min(monitor.right() - width as i32).max(monitor.x);
+ w.geometry.y = w.geometry.y.min(monitor.bottom() - height as i32).max(monitor.y);
+ }
+ }
}