git.lucas.co / cce-window-manager
window management library
git clone https://git.lucas.co/cce-window-manager.git

commit8c9178e5750c7bd00089ec2409cf5497d07647c0
parente1d2029556
authorLucas Galante <[email protected]>
date2026-08-11 21:52
arrange: let fresh floating windows choose their own size

A newly spawned floating window has no established box, and
place_normal_window guessed one from the min-size hint (else 800x600).
Self-sizing clients (every cce-ui app) obey any nonzero configure, so
the guess became the box forever — cce-preview requested 900x700 and
mapped locked at its 320x240 minimum.

Plan 0x0 for a fresh floating spawn instead: the xdg 'you choose'
configure lets the client map at its natural size, which the
acked-commit path then adopts as the box; every later arrange keeps
it. XWayland's configure already substitutes the X surface's own size
for Some(0). Tiled modes keep the min-size fallback — their sizes are
dictated, not chosen.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/arrange.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/src/arrange.rs b/src/arrange.rs
index c7167df..21316b6 100644
--- a/src/arrange.rs
+++ b/src/arrange.rs
@@ -476,6 +476,30 @@ pub fn place_normal_window(
             }
         }
         _ => {
+            // A fresh floating spawn (no established box, no resize in
+            // flight) gets the xdg "you choose" size 0x0 instead of a guess:
+            // the client maps at its natural size and the acked-commit path
+            // adopts that as the box. Guessing from the min-size hint locked
+            // self-sizing clients (every cce-ui app) to their minimum — they
+            // obey any nonzero configure, so the guess became the box forever.
+            if snap.mode == TilingMode::Floating
+                && snap.active_resize.is_none()
+                && snap.box_geom.width <= 0
+                && snap.box_geom.height <= 0
+            {
+                let (final_x, final_y) = ctx.virtual_to_screen(snap.virtual_pos.0, snap.virtual_pos.1);
+                return NormalPlacement {
+                    pos: (final_x, final_y),
+                    scale: ctx.zoom,
+                    size: (0, 0),
+                    tiled_all_edges: false,
+                    // Unmapped until its first buffer; sizeless culling would
+                    // be meaningless, so leave the flag untouched.
+                    hidden: None,
+                    virtual_write: None,
+                };
+            }
+
             // Regular pannable window on the virtual surface.
             let fw = if let Some(resize_size) = snap.active_resize {
                 resize_size.0 as i32
@@ -1621,6 +1645,44 @@ mod tests {
         assert_eq!(placement.hidden, None);
     }
 
+    #[test]
+    fn fresh_floating_window_gets_client_chosen_size() {
+        // No established box, no resize in flight: the placement is the xdg
+        // "you choose" 0x0, NOT the min-size hint — a self-sizing client
+        // obeys any nonzero configure, so a min-size guess would become the
+        // box forever.
+        let snap = NormalSnapshot {
+            mode: TilingMode::Floating,
+            box_geom: Rect { x: 0, y: 0, width: 0, height: 0 },
+            min_size: (320, 240),
+            virtual_pos: (100.0, 200.0),
+            active_resize: None,
+            is_cloud: false,
+            saved_maximized_size: (0, 0),
+            saved_maximized_virtual: (0.0, 0.0),
+        };
+        let p = NormalParams { gap_right: 10, gap_top: 6, cloud_position_default: None, desktop_grid_scale: 100.0, desktop_gap_width: 0.0, desktop_cell_inset: 0.0 };
+        let placement = place_normal_window(&snap, &p, &ctx());
+        assert_eq!(placement.size, (0, 0));
+        assert_eq!(placement.hidden, None);
+
+        // Once the box is established (the acked commit adopted the client's
+        // geometry), the placement keeps it.
+        let established = NormalSnapshot {
+            box_geom: Rect { x: 0, y: 0, width: 900, height: 700 },
+            ..snap
+        };
+        let placement = place_normal_window(&established, &p, &ctx());
+        assert_eq!(placement.size, (900, 700));
+
+        // Non-floating modes keep the min-size fallback: their sizes are
+        // dictated by tiling, not chosen by the client.
+        let tiled = NormalSnapshot { mode: TilingMode::Cascade, ..established };
+        let tiled = NormalSnapshot { box_geom: Rect { x: 0, y: 0, width: 0, height: 0 }, ..tiled };
+        let placement = place_normal_window(&tiled, &p, &ctx());
+        assert_eq!(placement.size, (320, 240));
+    }
+
     #[test]
     fn pannable_window_follows_viewport() {
         let snap = NormalSnapshot {