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

commit71fdd8820ede2879a79bad3e8c674c96ff6efedc
parentfeab593730
authorLucas Galante <[email protected]>
date2026-09-02 12:14
feat(ipc): set-mode — set a window's tiling mode outright

ccectl set-mode <floating|tiled|fullscreen> [app_id|id] sets one window's
mode directly (the focused window when unnamed), applying the same
SetWindowMode + Relayout command pair the mode_next action produces, so
the arrange-pass geometry transitions (tiled grid snap, fullscreen
sizing, floating restore) come along. Internal roles stay unsettable
here; mode rules cover those. Built for the status bar's window-mode
dropdown.

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

 src/cce_ctl.rs               |  1 +
 src/server/window_manager.rs | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/src/cce_ctl.rs b/src/cce_ctl.rs
index 41e69e9..0577d31 100644
--- a/src/cce_ctl.rs
+++ b/src/cce_ctl.rs
@@ -41,6 +41,7 @@ fn usage(name: &str, to_stderr: bool) {
     print("  fullscreen");
     print("  mode-next");
     print("  mode-next-shared");
+    print("  set-mode <floating|tiled|fullscreen> [app_id|id]  # set a window's mode (focused if omitted)");
     print("  zoom-in");
     print("  zoom-out");
     print("  zoom-reset");
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 93eea1e..2ac5827 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -3861,6 +3861,40 @@ impl WindowManager {
                 self.execute_action(&crate::config::Action::ModeNextShared, None);
                 "ok\n".to_string()
             }
+            "set-mode" => {
+                // set-mode <floating|tiled|fullscreen> [app_id|id] — set one
+                // window's mode outright (the focused window when unnamed),
+                // through the same SetWindowMode + Relayout pair the
+                // mode_next action produces, so the geometry transitions
+                // (tiled grid snap, fullscreen sizing, floating restore)
+                // come from the arrange pass exactly as they do for the key.
+                // The internal roles (popup/overlay/status/utility) are
+                // deliberately not settable here; `mode` rules cover those.
+                if parts.len() < 2 {
+                    return "error: usage: set-mode <floating|tiled|fullscreen> [app_id|id]\n".to_string();
+                }
+                let mode = match parts[1].to_lowercase().as_str() {
+                    "floating" => crate::tiling::TilingMode::Floating,
+                    "tiled" => crate::tiling::TilingMode::Tiled,
+                    "fullscreen" => crate::tiling::TilingMode::Fullscreen,
+                    other => return format!("error: unknown mode: {} (floating|tiled|fullscreen)\n", other),
+                };
+                let target = if parts.len() >= 3 {
+                    self.find_window_by_query(&parts[2..].join(" "))
+                } else {
+                    self.focused_window()
+                };
+                if target.is_null() {
+                    return "error: no target window\n".to_string();
+                }
+                {
+                    use crate::policy::api::{Command, Compositor, WindowId};
+                    let id = WindowId((*target).ref_key);
+                    self.apply(&Command::SetWindowMode { id, mode, locked: true });
+                    self.apply(&Command::Relayout);
+                }
+                "ok\n".to_string()
+            }
             "focus-window" => {
                 if parts.len() < 2 { return "error: missing app_id/id\n".to_string(); }
                 if let Some(seat) = self.first_seat() {