Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(resize): clamp interactive resizes to the client's min/max size hint
The overview resize op fed `snap::resize_axis`'s result straight into a
configure, floored only at 50px, so a drag could push a toplevel far
below the min size it declared through xdg-shell. cce-ui applies a
configure as-is, and cce-data-editor (min 600x450) panicked in the
tessellator once its layout had less room than its fixed parts — the
window simply vanished mid-drag. A window restored from state below its
minimum then sat there unresizable.
`DimensionsHint::clamp` applies the declared range (0 = unset, xdg's
convention; min wins over a contradictory max) in both places that
derive the in-flight size — the seat op's Resize arm and
`get_active_resize_dimensions` for the arrange snapshot — so they cannot
diverge. Verified in headless shadows at output scale 1 and 2: a right-
edge drag stops at 600 wide, a bottom-right corner drag at 600x450, a
left-edge drag past the minimum holds the right edge anchored and grows
again on the way back, and the client stays alive throughout.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/server/seat.rs | 5 +++++
src/server/window.rs | 24 ++++++++++++++++++++++++
src/server/window_manager.rs | 3 ++-
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/src/server/seat.rs b/src/server/seat.rs
index cff4058..8bde122 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -1411,6 +1411,11 @@ impl Seat {
op.start_win_virtual_y, op.start_win_h as f64, virtual_dy,
edges.top, edges.bottom, 50.0, &sp.y(),
) as u32;
+ // The client's xdg min/max size is a contract, not a
+ // suggestion: a configure below it is applied by
+ // cce-ui as-is, and a layout with less room than its
+ // fixed parts panicked cce-data-editor mid-drag.
+ let (new_w, new_h) = (*win).wm_scheduled.dimensions_hint.clamp(new_w, new_h);
(*win).virtual_x = vx;
(*win).virtual_y = vy;
diff --git a/src/server/window.rs b/src/server/window.rs
index 42f0a72..9f4d3c7 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -50,6 +50,30 @@ pub struct DimensionsHint {
pub max_height: u32,
}
+impl DimensionsHint {
+ /// Clamp a requested content size to the client's declared range; a
+ /// zero bound is "unset" (xdg-shell's convention) and leaves that side
+ /// alone. A max below the min is the client's own contradiction and
+ /// the min wins.
+ pub fn clamp(&self, width: u32, height: u32) -> (u32, u32) {
+ let mut w = width;
+ let mut h = height;
+ if self.max_width > 0 {
+ w = w.min(self.max_width);
+ }
+ if self.max_height > 0 {
+ h = h.min(self.max_height);
+ }
+ if self.min_width > 0 {
+ w = w.max(self.min_width);
+ }
+ if self.min_height > 0 {
+ h = h.max(self.min_height);
+ }
+ (w, h)
+ }
+}
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Edges {
pub top: bool,
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 714be91..e9bd5b2 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -2104,7 +2104,8 @@ impl WindowManager {
op.start_win_virtual_y, op.start_win_h as f64, virtual_dy,
edges.top, edges.bottom, 50.0, &sp.y(),
) as u32;
- return Some((new_w, new_h));
+ // Same clamp as the seat op (see its Resize arm).
+ return Some((*win_ptr).wm_scheduled.dimensions_hint.clamp(new_w, new_h));
}
}
}