git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitfa7be27448882a857c83b14d3ecfa25d04ba70c8
parent32203d1f79
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 17:44
feat(ipc): resize-window sets a window's size

`ccectl resize-window <w> <h> [app_id|id]` sets the focused or named
window's content size in logical px, top-left fixed, clamped to the
client's min/max hints as a drag is. A Tiled window covers the cells
that size touches. Fullscreen, Utility and Status windows size
themselves and are refused.

Until now a window could only be resized by a pointer drag in adjust
mode, which a script driving the live session cannot do safely: the
user moving the camera between the focus and the press turns the drag
into a move of whatever window is under the pointer by then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/cce_ctl.rs               |  2 ++
 src/server/window_manager.rs | 43 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/src/cce_ctl.rs b/src/cce_ctl.rs
index afa0cce4..d5e589d2 100644
--- a/src/cce_ctl.rs
+++ b/src/cce_ctl.rs
@@ -55,6 +55,8 @@ fn usage(name: &str, to_stderr: bool) {
     print("  close-window <app_id|id> [title-substring]  # close a specific window");
     print("  center-window [<app_id>]   # pan focused/named window on-screen; replies x= y= w= h=");
     print("  move-window <square> [<app_id>] # put focused/named window on a desktop square (e.g. C-9)");
+    print("  resize-window <w> <h> [<app_id|id>] # set focused/named window's size (logical px),");
+    print("                              # top-left fixed, clamped to its min/max hints");
     print("  move-window-left|-right|-up|-down    # step the focused window one grid cell;");
     print("                              # tiled swaps with the tiled window already there,");
     print("                              # floating just moves");
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 4d79fafb..a9c4ccee 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -5957,6 +5957,49 @@ impl WindowManager {
                 );
                 format!("ok cell={} vx={:.1} vy={:.1}\n", cell, x, y)
             }
+            "resize-window" => {
+                // resize-window <w> <h> [app_id|id] — set a window's content
+                // size in logical px, top-left fixed; the focused window with
+                // no query. Clamped to the client's min/max hints like a drag
+                // (`DimensionsHint::clamp`). A Tiled window then covers the
+                // cells that size touches, as a client maximize does. The
+                // reply is the size asked for: the client answers in its own
+                // time (an X11 app may insist on its minimum), so read the
+                // outcome back with `windows`.
+                let usage = "error: usage: resize-window <w> <h> [app_id|id]\n";
+                let (Some(w), Some(h)) = (
+                    parts.get(1).and_then(|s| s.parse::<u32>().ok()).filter(|&v| v > 0),
+                    parts.get(2).and_then(|s| s.parse::<u32>().ok()).filter(|&v| v > 0),
+                ) else {
+                    return usage.to_string();
+                };
+                let target: *mut Window = if parts.len() >= 4 {
+                    self.find_window_by_query(&parts[3..].join(" "))
+                } else if let Some(seat) = self.first_seat() {
+                    match (*seat).focused {
+                        crate::seat::Focus::Window(w) => w,
+                        _ => std::ptr::null_mut(),
+                    }
+                } else {
+                    std::ptr::null_mut()
+                };
+                if target.is_null() {
+                    return "error: window not found\n".to_string();
+                }
+                if matches!(
+                    (*target).tiling_mode,
+                    crate::tiling::TilingMode::Fullscreen
+                        | crate::tiling::TilingMode::Utility
+                        | crate::tiling::TilingMode::Status
+                ) {
+                    return "error: a fullscreen, utility or status window sizes itself\n".to_string();
+                }
+                let (w, h) = (*target).wm_scheduled.dimensions_hint.clamp(w, h);
+                (*target).box_geom.width = w as i32;
+                (*target).box_geom.height = h as i32;
+                self.dirty_windowing();
+                format!("ok w={} h={}\n", w, h)
+            }
             "center-window" | "bring-window" => {
                 // Pan the desktop so the target window (given app_id/id, or the
                 // focused window if omitted) is centered in the output, then focus