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

commitea6dd74dfecf955bd09db0d0b6a49e348e0eb472
parent12cc5b08e5
authorLucas Galante <[email protected]>
date2026-06-01 06:55
Implement clear-inspector-v1 protocol and add inspector CLI client

 Cargo.lock                              |  26 +--
 Cargo.toml                              |   8 +-
 protocol/clear-inspector-v1.xml         |  52 +++++
 protocol/river-window-management-v1.xml |   9 +
 src/borders.rs                          |   6 +-
 src/clearctl.rs                         |  25 ++-
 src/config.rs                           |  95 +++++----
 src/decorations.rs                      |  14 +-
 src/input.rs                            | 146 +++++++++++---
 src/inspector_cli.rs                    | 140 +++++++++++++
 src/ipc.rs                              | 346 ++++++++++++++++++++++++++------
 src/ipc_server.rs                       |  37 +++-
 src/lib.rs                              |   2 +-
 src/main.rs                             | 103 ++++++----
 src/paths.rs                            |  44 ++--
 src/protocol.rs                         |   4 +
 src/restart.rs                          |  31 +--
 src/state.rs                            |  21 +-
 src/status.rs                           |  10 +-
 src/status_server.rs                    |   8 +-
 src/tiling.rs                           | 182 +----------------
 src/types.rs                            |  40 ++--
 src/wayland.rs                          | 312 ++++++++++++++++++----------
 src/wm.rs                               | 263 +++++++++++++++++++++---
 start-river.sh                          |  20 +-
 25 files changed, 1331 insertions(+), 613 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2f3cfd6..83920d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -31,19 +31,7 @@ dependencies = [
 ]
 
 [[package]]
-name = "cfg-if"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-
-[[package]]
-name = "cfg_aliases"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
-
-[[package]]
-name = "clearwm"
+name = "ccec"
 version = "0.1.0"
 dependencies = [
  "bitflags",
@@ -61,6 +49,18 @@ dependencies = [
  "xkbcommon",
 ]
 
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
 [[package]]
 name = "downcast-rs"
 version = "1.2.1"
diff --git a/Cargo.toml b/Cargo.toml
index 413bd84..5b2aa02 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,16 +1,20 @@
 [package]
-name = "clearwm"
+name = "ccec"
 version = "0.1.0"
 edition = "2021"
 
 [[bin]]
-name = "clearwm"
+name = "ccec"
 path = "src/main.rs"
 
 [[bin]]
 name = "clearctl"
 path = "src/clearctl.rs"
 
+[[bin]]
+name = "clear-inspector"
+path = "src/inspector_cli.rs"
+
 [dependencies]
 wayland-client = "0.31"
 wayland-backend = "0.3"
diff --git a/protocol/clear-inspector-v1.xml b/protocol/clear-inspector-v1.xml
new file mode 100644
index 0000000..b321965
--- /dev/null
+++ b/protocol/clear-inspector-v1.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="clear_inspector_v1">
+  <copyright>
+    Copyright © 2026 Antigravity
+  </copyright>
+
+  <interface name="zclear_inspector_v1" version="1">
+    <description summary="query information about client surface states and layouts">
+      This interface allows clear-ui client applications to register their surfaces
+      and publish their internal widget trees and state to the compositor.
+      It also allows inspector tools to request the list of active surfaces and their states.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the inspector object"/>
+    </request>
+
+    <request name="register_client">
+      <description summary="register a wl_surface as a clear-ui client window">
+        Associate a client wl_surface with this inspector.
+      </description>
+      <arg name="surface" type="object" interface="wl_surface"/>
+    </request>
+
+    <request name="update_state">
+      <description summary="update the published state of a registered client surface">
+        The state is serialized as a JSON string containing widget layout and state data.
+      </description>
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="state" type="string" summary="JSON serialized state of the application"/>
+    </request>
+
+    <request name="get_inspected_surfaces">
+      <description summary="request the list of all registered surfaces from the compositor"/>
+    </request>
+
+    <event name="inspected_surface">
+      <description summary="reports a registered surface's state and absolute coordinates"/>
+      <arg name="title" type="string" summary="window title"/>
+      <arg name="app_id" type="string" summary="application ID"/>
+      <arg name="x" type="int" summary="absolute screen X coordinate"/>
+      <arg name="y" type="int" summary="absolute screen Y coordinate"/>
+      <arg name="width" type="int" summary="surface width"/>
+      <arg name="height" type="int" summary="surface height"/>
+      <arg name="state" type="string" summary="JSON state of the application"/>
+    </event>
+
+    <event name="inspected_surface_done">
+      <description summary="indicates all inspected surfaces have been reported"/>
+    </event>
+  </interface>
+</protocol>
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index f835971..32d6db3 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -1121,6 +1121,15 @@
       <arg name="max_width" type="int" summary="maximum width"/>
       <arg name="max_height" type="int" summary="maximum height"/>
     </request>
+
+    <request name="set_opacity" since="4">
+      <description summary="set the window opacity">
+        Set the window opacity, from 0 (fully transparent) to 0xffffffff (fully opaque).
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="opacity" type="uint" summary="opacity value from 0 to 0xffffffff"/>
+    </request>
   </interface>
 
   <interface name="river_decoration_v1" version="4">
diff --git a/src/borders.rs b/src/borders.rs
index 4f916c5..53d0657 100644
--- a/src/borders.rs
+++ b/src/borders.rs
@@ -100,7 +100,7 @@ pub fn compute_border_colors(state: &WindowManager) -> Vec<WindowBorders> {
             let visible_mode: Vec<usize> = visible
                 .iter()
                 .cloned()
-                .filter(|&i| state.expose_active || state.windows[i].tiling_mode == win.tiling_mode)
+                .filter(|&i| state.expose_visual_active || state.windows[i].tiling_mode == win.tiling_mode)
                 .collect();
             let n_visible_mode = visible_mode.len();
             let pos = visible_mode.iter().position(|&i| i == idx).unwrap_or(0);
@@ -115,15 +115,13 @@ pub fn compute_border_colors(state: &WindowManager) -> Vec<WindowBorders> {
             (r, g, b, ALPHA)
         };
 
-        let mut width = if state.expose_active && win.tiling_mode != TilingMode::Popup {
+        let mut width = if state.expose_visual_active && win.tiling_mode != TilingMode::Popup {
             state.layout.grid_border_width
         } else {
             match win.tiling_mode {
                 TilingMode::Cascade => state.layout.cascade_border_width,
                 TilingMode::Fullscreen => state.layout.fullscreen_border_width,
                 TilingMode::Grid => state.layout.grid_border_width,
-                TilingMode::Vsplit => state.layout.vsplit_border_width,
-                TilingMode::Hsplit => state.layout.hsplit_border_width,
                 TilingMode::Floating => state.layout.floating_border_width,
                 TilingMode::Popup => state.layout.border_width,
             }
diff --git a/src/clearctl.rs b/src/clearctl.rs
index ede805a..4129218 100644
--- a/src/clearctl.rs
+++ b/src/clearctl.rs
@@ -1,4 +1,4 @@
-// clearctl — IPC client for clearwm
+// clearctl — IPC client for ccec
 
 use std::env;
 use std::fs;
@@ -8,15 +8,15 @@ use std::process;
 
 fn get_socket_path() -> String {
     match env::var("WAYLAND_DISPLAY") {
-        Ok(display) => format!("/tmp/clearwm-{}.sock", display),
-        Err(_) => "/tmp/clearwm.sock".to_string(),
+        Ok(display) => format!("/tmp/ccec-{}.sock", display),
+        Err(_) => "/tmp/ccec.sock".to_string(),
     }
 }
 
 fn get_windows_path() -> String {
     match env::var("WAYLAND_DISPLAY") {
-        Ok(display) => format!("/tmp/clearwm-windows-{}", display),
-        Err(_) => "/tmp/clearwm-windows".to_string(),
+        Ok(display) => format!("/tmp/ccec-windows-{}", display),
+        Err(_) => "/tmp/ccec-windows".to_string(),
     }
 }
 
@@ -49,8 +49,17 @@ fn usage(name: &str, to_stderr: bool) {
     print("  pbind <mods> <button> <action>");
     print("  retile");
     print("  set-tag <1-4>");
-    print("  mode <cascade|grid|vsplit|hsplit|fullscreen|floating|popup> <app_id> [title]");
-    print("  tag-layout <1-4> <cascade|grid|vsplit|hsplit|fullscreen|floating|popup>");
+    print("  mode <cascade|grid|fullscreen|floating|popup> <app_id> [title]");
+    print("  tag-layout <1-4> <cascade|grid|fullscreen|floating|popup>");
+    print("  pointer-location");
+    print("  pointer-move-to <x> <y>");
+    print("  pointer-move-by <dx> <dy>");
+    print("  pointer-click <button>");
+    print("  pointer-press <button>");
+    print("  pointer-release <button>");
+    print("  keypress <key>");
+    print("  key-press <key>");
+    print("  key-release <key>");
 }
 
 fn main() {
@@ -69,7 +78,7 @@ fn main() {
     if args[1] == "windows" {
         match fs::read_to_string(get_windows_path()) {
             Ok(content) => print!("{}", content),
-            Err(_) => eprintln!("No windows info (clearwm may not be running)"),
+            Err(_) => eprintln!("No windows info (ccec may not be running)"),
         }
         return;
     }
diff --git a/src/config.rs b/src/config.rs
index 78a8ba8..1d6690b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,4 +1,4 @@
-// TOML config parsing for clearwm
+// TOML config parsing for ccec
 
 use serde::Deserialize;
 use std::collections::HashMap;
@@ -68,10 +68,6 @@ pub struct LayoutConfig {
     pub cascade_border_width: i64,
     #[serde(default = "default_grid_border_width")]
     pub grid_border_width: i64,
-    #[serde(default = "default_vsplit_border_width")]
-    pub vsplit_border_width: i64,
-    #[serde(default = "default_hsplit_border_width")]
-    pub hsplit_border_width: i64,
     #[serde(default = "default_floating_border_width")]
     pub floating_border_width: i64,
     #[serde(default = "default_border_color", alias = "high_color")]
@@ -80,6 +76,8 @@ pub struct LayoutConfig {
     pub background_color: String,
     #[serde(default = "default_border_font_size")]
     pub border_font_size: i64,
+    #[serde(default = "default_transition_duration")]
+    pub transition_duration: i64,
 }
 
 impl Default for LayoutConfig {
@@ -96,12 +94,11 @@ impl Default for LayoutConfig {
             fullscreen_border_width: default_fullscreen_border_width(),
             cascade_border_width: default_cascade_border_width(),
             grid_border_width: default_grid_border_width(),
-            vsplit_border_width: default_vsplit_border_width(),
-            hsplit_border_width: default_hsplit_border_width(),
             floating_border_width: default_floating_border_width(),
             border_color: default_border_color(),
             background_color: default_background_color(),
             border_font_size: default_border_font_size(),
+            transition_duration: default_transition_duration(),
         }
     }
 }
@@ -139,12 +136,6 @@ fn default_cascade_border_width() -> i64 {
 fn default_grid_border_width() -> i64 {
     6
 }
-fn default_vsplit_border_width() -> i64 {
-    6
-}
-fn default_hsplit_border_width() -> i64 {
-    6
-}
 fn default_floating_border_width() -> i64 {
     6
 }
@@ -157,6 +148,9 @@ fn default_background_color() -> String {
 fn default_border_font_size() -> i64 {
     11
 }
+fn default_transition_duration() -> i64 {
+    300
+}
 
 #[derive(Debug, Deserialize, Default)]
 pub struct OutputConfig {
@@ -303,10 +297,9 @@ pub fn parse_config(path: &str, cold_start: bool, state: &mut WindowManager) ->
     state.layout.fullscreen_border_width = config.layout.fullscreen_border_width as i32;
     state.layout.cascade_border_width = config.layout.cascade_border_width as i32;
     state.layout.grid_border_width = config.layout.grid_border_width as i32;
-    state.layout.vsplit_border_width = config.layout.vsplit_border_width as i32;
-    state.layout.hsplit_border_width = config.layout.hsplit_border_width as i32;
     state.layout.floating_border_width = config.layout.floating_border_width as i32;
     state.layout.border_font_size = config.layout.border_font_size as i32;
+    state.layout.transition_duration = config.layout.transition_duration as i32;
     if let Some((r, g, b, a)) = parse_hex_color(&config.layout.border_color) {
         state.layout.border_r = r;
         state.layout.border_g = g;
@@ -421,6 +414,15 @@ pub fn parse_config(path: &str, cold_start: bool, state: &mut WindowManager) ->
             match pkill_cmd.status() {
                 Ok(status) => {
                     eprintln!("[config] pkill status: {}", status);
+                    let start = std::time::Instant::now();
+                    while process_running(&name) && start.elapsed().as_secs_f64() < 1.0 {
+                        std::thread::sleep(std::time::Duration::from_millis(50));
+                    }
+                    eprintln!(
+                        "[config] process {} exited after pkill: {}",
+                        name,
+                        !process_running(&name)
+                    );
                 }
                 Err(e) => {
                     eprintln!("[config] failed to execute pkill: {:?}", e);
@@ -483,7 +485,7 @@ pub fn parse_config(path: &str, cold_start: bool, state: &mut WindowManager) ->
         }
     });
     if let Some(ref controller) = state.input_controller {
-        let _ = controller.send((inertial_cfg, config.input.tap_to_click));
+        let _ = controller.send(crate::input::InputDaemonMsg::UpdateConfig(inertial_cfg, config.input.tap_to_click));
     }
 
     // [notifications]
@@ -571,22 +573,43 @@ pub fn parse_keysym(key_str: &str) -> u32 {
 ///
 /// Closes all inherited FDs > 2 in the child via pre_exec so that
 /// spawned Wayland clients (fuzzel, foot, etc.) never accidentally
-/// read from clearwm's Wayland socket fd. Also redirects stdout/stderr
-/// to /dev/null so child output doesn't pollute clearwm's log, and
-/// calls setsid() to detach from clearwm's process group.
+/// read from ccec's Wayland socket fd. Also redirects stdout/stderr
+/// to /dev/null so child output doesn't pollute ccec's log, and
+/// calls setsid() to detach from ccec's process group.
 pub fn spawn_command_bg(cmd: &str) {
     use std::os::unix::process::CommandExt;
     let cmd = cmd.to_string();
+    
+    let stdout_cfg = if let Ok(f) = std::fs::OpenOptions::new()
+        .create(true)
+        .append(true)
+        .open("/tmp/ccec-spawned-apps.log")
+    {
+        std::process::Stdio::from(f)
+    } else {
+        std::process::Stdio::null()
+    };
+
+    let stderr_cfg = if let Ok(f) = std::fs::OpenOptions::new()
+        .create(true)
+        .append(true)
+        .open("/tmp/ccec-spawned-apps.log")
+    {
+        std::process::Stdio::from(f)
+    } else {
+        std::process::Stdio::null()
+    };
+
     let _ = unsafe {
         std::process::Command::new("sh")
             .arg("-c")
             .arg(&cmd)
             .env_remove("WAYLAND_DEBUG")
-            .stdout(std::process::Stdio::null())
-            .stderr(std::process::Stdio::null())
+            .stdout(stdout_cfg)
+            .stderr(stderr_cfg)
             .pre_exec(|| {
                 // Close all inherited FDs > 2 to prevent the child from
-                // accidentally reading clearwm's Wayland socket or status
+                // accidentally reading ccec's Wayland socket or status
                 // socket FDs. close() and setsid() are async-signal-safe.
                 let max_fd = libc::sysconf(libc::_SC_OPEN_MAX) as libc::c_int;
                 for fd in 3..max_fd {
@@ -615,7 +638,7 @@ pub fn process_running(name: &str) -> bool {
 pub fn show_notification(title: &str, body: &str) {
     let title_escaped = title.replace('\'', "'\\''");
     let body_escaped = body.replace('\'', "'\\''");
-    let cmd = format!("notify-send -a clearwm '{}' '{}'", title_escaped, body_escaped);
+    let cmd = format!("notify-send -a ccec '{}' '{}'", title_escaped, body_escaped);
     spawn_command_bg(&cmd);
 }
 
@@ -652,31 +675,31 @@ mod tests {
 
     #[test]
     fn test_expand_env_vars_simple() {
-        std::env::set_var("CLEARWM_TEST_VAR", "hello");
-        assert_eq!(expand_env_vars("$CLEARWM_TEST_VAR"), "hello");
-        std::env::remove_var("CLEARWM_TEST_VAR");
+        std::env::set_var("CCEC_TEST_VAR", "hello");
+        assert_eq!(expand_env_vars("$CCEC_TEST_VAR"), "hello");
+        std::env::remove_var("CCEC_TEST_VAR");
     }
 
     #[test]
     fn test_expand_env_vars_braces() {
-        std::env::set_var("CLEARWM_TEST_VAR", "world");
-        assert_eq!(expand_env_vars("${CLEARWM_TEST_VAR}!"), "world!");
-        std::env::remove_var("CLEARWM_TEST_VAR");
+        std::env::set_var("CCEC_TEST_VAR", "world");
+        assert_eq!(expand_env_vars("${CCEC_TEST_VAR}!"), "world!");
+        std::env::remove_var("CCEC_TEST_VAR");
     }
 
     #[test]
     fn test_expand_env_vars_mid_string() {
-        std::env::set_var("CLEARWM_TEST_HOME", "/home/user");
+        std::env::set_var("CCEC_TEST_HOME", "/home/user");
         assert_eq!(
-            expand_env_vars("$CLEARWM_TEST_HOME/.local/bin:$CLEARWM_TEST_HOME/bin"),
+            expand_env_vars("$CCEC_TEST_HOME/.local/bin:$CCEC_TEST_HOME/bin"),
             "/home/user/.local/bin:/home/user/bin"
         );
-        std::env::remove_var("CLEARWM_TEST_HOME");
+        std::env::remove_var("CCEC_TEST_HOME");
     }
 
     #[test]
     fn test_expand_env_vars_unset() {
-        assert_eq!(expand_env_vars("$CLEARWM_NONEXISTENT_VAR"), "");
+        assert_eq!(expand_env_vars("$CCEC_NONEXISTENT_VAR"), "");
     }
 
     #[test]
@@ -770,9 +793,9 @@ exec = "echo reloaded"
         assert_eq!(config.reload[0].exec, "pkill clear-input-daemon");
         assert_eq!(config.reload[1].exec, "echo reloaded");
 
-        // Also test integration via parse_config (we can write to a temporary file in /tmp or mock it,
-        // but wait! we can write to a temporary file inside the workspace)
-        let temp_path = "/home/lsgalante/Dropbox/Clear/clear-window-manager/scratch_config_test.toml";
+        // Also test integration via parse_config by writing to a temporary file
+        let temp_path_buf = std::env::temp_dir().join("scratch_config_test.toml");
+        let temp_path = temp_path_buf.to_str().unwrap();
         std::fs::write(temp_path, toml_str).unwrap();
 
         let mut wm = WindowManager::default();
diff --git a/src/decorations.rs b/src/decorations.rs
index 5219988..ec48de7 100644
--- a/src/decorations.rs
+++ b/src/decorations.rs
@@ -1,4 +1,4 @@
-// Wayland decoration surface drawing and management for clearwm
+// Wayland decoration surface drawing and management for ccec
 
 use std::ffi::CString;
 use std::os::fd::RawFd;
@@ -188,7 +188,7 @@ fn draw_char(
 
 /// Create a temporary shared memory file descriptor.
 fn create_memfd(size: usize) -> Option<RawFd> {
-    let name = CString::new("clearwm-decoration").ok()?;
+    let name = CString::new("ccec-decoration").ok()?;
     let fd = unsafe { libc::memfd_create(name.as_ptr(), libc::MFD_CLOEXEC) };
     if fd < 0 {
         return None;
@@ -381,7 +381,7 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
             let title = w.title.clone().unwrap_or_else(|| {
                 w.app_id.clone().unwrap_or_else(|| "Window".to_string())
             });
-            let mode_idx = if state.wm.expose_active && w.tiling_mode != crate::types::TilingMode::Popup {
+            let mode_idx = if state.wm.expose_visual_active && w.tiling_mode != crate::types::TilingMode::Popup {
                 state.wm.windows
                     .iter()
                     .filter(|win| !win.closed && win.app_id.as_deref() != Some("clear-status-interface") && win.tiling_mode != crate::types::TilingMode::Popup && (win.tags & active_tags) != 0)
@@ -394,15 +394,13 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
                     .position(|win| win.id == w.id)
                     .unwrap_or(0)
             };
-            let indicator = if state.wm.expose_active && w.tiling_mode != crate::types::TilingMode::Popup {
+            let indicator = if state.wm.expose_visual_active && w.tiling_mode != crate::types::TilingMode::Popup {
                 "EX"
             } else {
                 match w.tiling_mode {
                     crate::types::TilingMode::Floating => "F",
                     crate::types::TilingMode::Cascade => "C",
                     crate::types::TilingMode::Grid => "G",
-                    crate::types::TilingMode::Vsplit => "V",
-                    crate::types::TilingMode::Hsplit => "H",
                     crate::types::TilingMode::Fullscreen => "S",
                     crate::types::TilingMode::Popup => "P",
                 }
@@ -428,15 +426,13 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
             let bg_color = ((border_a as u32) << 24) | ((border_r as u32) << 16) | ((border_g as u32) << 8) | (border_b as u32);
 
             // Border width is mode-specific
-            let border_w = if state.wm.expose_active && w.tiling_mode != crate::types::TilingMode::Popup {
+            let border_w = if state.wm.expose_visual_active && w.tiling_mode != crate::types::TilingMode::Popup {
                 state.wm.layout.grid_border_width
             } else {
                 match w.tiling_mode {
                     crate::types::TilingMode::Cascade => state.wm.layout.cascade_border_width,
                     crate::types::TilingMode::Fullscreen => state.wm.layout.fullscreen_border_width,
                     crate::types::TilingMode::Grid => state.wm.layout.grid_border_width,
-                    crate::types::TilingMode::Vsplit => state.wm.layout.vsplit_border_width,
-                    crate::types::TilingMode::Hsplit => state.wm.layout.hsplit_border_width,
                     crate::types::TilingMode::Floating => state.wm.layout.floating_border_width,
                     crate::types::TilingMode::Popup => 0,
                 }
diff --git a/src/input.rs b/src/input.rs
index 53b5887..4d4656d 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -4,16 +4,36 @@ use std::os::unix::fs::OpenOptionsExt;
 use std::os::unix::io::AsRawFd;
 use std::path::PathBuf;
 use std::time::Instant;
+use std::sync::atomic::{AtomicI32, Ordering};
 use tokio::io::AsyncReadExt;
 use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
 use tokio::time::{sleep, Duration};
 use crate::config::InertialConfig;
 
+pub static POINTER_X: AtomicI32 = AtomicI32::new(0);
+pub static POINTER_Y: AtomicI32 = AtomicI32::new(0);
+pub static SCREEN_WIDTH: AtomicI32 = AtomicI32::new(1920);
+pub static SCREEN_HEIGHT: AtomicI32 = AtomicI32::new(1080);
+
+pub fn update_pointer_coords(dx: i32, dy: i32) {
+    let screen_w = SCREEN_WIDTH.load(Ordering::SeqCst);
+    let screen_h = SCREEN_HEIGHT.load(Ordering::SeqCst);
+    let mut current_x = POINTER_X.load(Ordering::SeqCst);
+    let mut current_y = POINTER_Y.load(Ordering::SeqCst);
+
+    current_x = (current_x + dx).clamp(0, screen_w);
+    current_y = (current_y + dy).clamp(0, screen_h);
+
+    POINTER_X.store(current_x, Ordering::SeqCst);
+    POINTER_Y.store(current_y, Ordering::SeqCst);
+}
+
 // IOCTL and Event constants
 const UI_DEV_CREATE: libc::c_ulong = 0x5501;
 const UI_DEV_SETUP: libc::c_ulong = 0x405C5503;
 const UI_SET_EVBIT: libc::c_ulong = 0x40045564;
 const UI_SET_RELBIT: libc::c_ulong = 0x40045566;
+const UI_SET_KEYBIT: libc::c_ulong = 0x40045565;
 
 // Linux input event codes
 const EV_SYN: u16 = 0x00;
@@ -86,14 +106,26 @@ fn eviocgabs(abs: u32) -> libc::c_ulong {
 
 const ABS_MT_SLOT: u16 = 0x2f;
 
+#[derive(Debug, Clone)]
+pub enum InputDaemonMsg {
+    UpdateConfig(InertialConfig, bool),
+    SimulateMove { dx: i32, dy: i32 },
+    SimulateButton { button: u16, press: bool },
+    SimulateKey { keycode: u16, press: bool },
+    SimulateClick { button: u16 },
+    SimulateKeyPress { keycode: u16 },
+}
+
 #[derive(Debug)]
 enum CoordinatorMsg {
     PhysicalMove { dx: i32, dy: i32, timestamp: Instant },
     PhysicalTrackpadMove { dx: i32, dy: i32, timestamp: Instant },
     PhysicalTrackpadLift { timestamp: Instant },
     PhysicalScroll { dwx: i32, dwy: i32, timestamp: Instant },
-    UpdateConfig((InertialConfig, bool)),
     FingersReport(Vec<FingerState>),
+    DaemonMsg(InputDaemonMsg),
+    InternalReleaseButton { button: u16 },
+    InternalReleaseKey { keycode: u16 },
 }
 
 fn setup_uinput() -> std::io::Result<std::fs::File> {
@@ -120,6 +152,19 @@ fn setup_uinput() -> std::io::Result<std::fs::File> {
         if libc::ioctl(fd, UI_SET_RELBIT, REL_HWHEEL as libc::c_int) < 0 {
             return Err(std::io::Error::last_os_error());
         }
+        if libc::ioctl(fd, UI_SET_EVBIT, EV_KEY as libc::c_int) < 0 {
+            return Err(std::io::Error::last_os_error());
+        }
+        for key in 1..=511 {
+            if libc::ioctl(fd, UI_SET_KEYBIT, key as libc::c_int) < 0 {
+                return Err(std::io::Error::last_os_error());
+            }
+        }
+        for btn in 272..=276 {
+            if libc::ioctl(fd, UI_SET_KEYBIT, btn as libc::c_int) < 0 {
+                return Err(std::io::Error::last_os_error());
+            }
+        }
     }
 
     let mut setup = UinputSetup {
@@ -378,57 +423,62 @@ struct PhysicsState {
     three_finger_gesture_triggered: bool,
 }
 
-fn trigger_tap_to_click_ipc(tap: bool, ipc_tx: &std::sync::mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn send_ipc_cmd(ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, cmd: String) {
+    let (reply_tx, _) = std::sync::mpsc::channel();
+    let _ = ipc_tx.send(crate::ipc_server::IpcRequest { command: cmd, reply_tx });
+}
+
+fn trigger_tap_to_click_ipc(tap: bool, ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, pipe_write: libc::c_int) {
     let cmd = format!("input tap-to-click {}", tap);
-    let _ = ipc_tx.send(cmd);
+    send_ipc_cmd(ipc_tx, cmd);
     unsafe {
         libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
     }
 }
 
-fn trigger_trackpad_disabled_ipc(disabled: bool, ipc_tx: &std::sync::mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn trigger_trackpad_disabled_ipc(disabled: bool, ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, pipe_write: libc::c_int) {
     let cmd = format!("input trackpad-disabled {}", disabled);
-    let _ = ipc_tx.send(cmd);
+    send_ipc_cmd(ipc_tx, cmd);
     unsafe {
         libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
     }
 }
 
-fn trigger_expose_ipc(ipc_tx: &std::sync::mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn trigger_expose_ipc(ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, pipe_write: libc::c_int) {
     let cmd = "expose".to_string();
-    let _ = ipc_tx.send(cmd);
+    send_ipc_cmd(ipc_tx, cmd);
     unsafe {
         libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
     }
 }
 
-fn trigger_expose_exit_ipc(ipc_tx: &std::sync::mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn trigger_expose_exit_ipc(ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, pipe_write: libc::c_int) {
     let cmd = "expose-exit".to_string();
-    let _ = ipc_tx.send(cmd);
+    send_ipc_cmd(ipc_tx, cmd);
     unsafe {
         libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
     }
 }
 
-fn trigger_view_next_ipc(ipc_tx: &std::sync::mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn trigger_view_next_ipc(ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, pipe_write: libc::c_int) {
     let cmd = "view-next".to_string();
-    let _ = ipc_tx.send(cmd);
+    send_ipc_cmd(ipc_tx, cmd);
     unsafe {
         libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
     }
 }
 
-fn trigger_view_prev_ipc(ipc_tx: &std::sync::mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn trigger_view_prev_ipc(ipc_tx: &std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>, pipe_write: libc::c_int) {
     let cmd = "view-prev".to_string();
-    let _ = ipc_tx.send(cmd);
+    send_ipc_cmd(ipc_tx, cmd);
     unsafe {
         libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
     }
 }
 
 pub fn run_input_daemon(
-    mut event_queue_rx: tokio::sync::mpsc::UnboundedReceiver<(InertialConfig, bool)>,
-    ipc_tx: std::sync::mpsc::Sender<String>,
+    mut event_queue_rx: tokio::sync::mpsc::UnboundedReceiver<InputDaemonMsg>,
+    ipc_tx: std::sync::mpsc::Sender<crate::ipc_server::IpcRequest>,
     pipe_write: libc::c_int,
 ) -> Result<(), Box<dyn std::error::Error>> {
     let rt = tokio::runtime::Builder::new_current_thread()
@@ -472,8 +522,8 @@ pub fn run_input_daemon(
 
         // Wait for initial config
         let (initial_config, tap_to_click) = match event_queue_rx.recv().await {
-            Some(res) => res,
-            None => {
+            Some(InputDaemonMsg::UpdateConfig(res, tap)) => (res, tap),
+            _ => {
                 return Err(Box::from("Failed to receive initial input configuration") as Box<dyn std::error::Error>);
             }
         };
@@ -481,8 +531,8 @@ pub fn run_input_daemon(
         // Config listener task
         let tx_config = tx.clone();
         tokio::spawn(async move {
-            while let Some((config, tap)) = event_queue_rx.recv().await {
-                let _ = tx_config.send(CoordinatorMsg::UpdateConfig((config, tap)));
+            while let Some(msg) = event_queue_rx.recv().await {
+                let _ = tx_config.send(CoordinatorMsg::DaemonMsg(msg));
             }
         });
 
@@ -559,14 +609,59 @@ pub fn run_input_daemon(
             tokio::select! {
                 Some(msg) = rx.recv() => {
                     match msg {
-                        CoordinatorMsg::UpdateConfig((cfg, tap)) => {
-                            state.config = cfg;
-                            if state.tap_to_click != tap {
-                                state.tap_to_click = tap;
-                                trigger_tap_to_click_ipc(tap, &ipc_tx, pipe_write);
+                        CoordinatorMsg::DaemonMsg(daemon_msg) => {
+                            match daemon_msg {
+                                InputDaemonMsg::UpdateConfig(cfg, tap) => {
+                                    state.config = cfg;
+                                    if state.tap_to_click != tap {
+                                        state.tap_to_click = tap;
+                                        trigger_tap_to_click_ipc(tap, &ipc_tx, pipe_write);
+                                    }
+                                }
+                                InputDaemonMsg::SimulateMove { dx, dy } => {
+                                    let _ = write_mouse_move(&mut uinput_file, dx, dy);
+                                    update_pointer_coords(dx, dy);
+                                }
+                                InputDaemonMsg::SimulateButton { button, press } => {
+                                    let val = if press { 1 } else { 0 };
+                                    let _ = write_raw_event(&mut uinput_file, EV_KEY, button, val);
+                                    let _ = write_raw_event(&mut uinput_file, EV_SYN, SYN_REPORT, 0);
+                                }
+                                InputDaemonMsg::SimulateKey { keycode, press } => {
+                                    let val = if press { 1 } else { 0 };
+                                    let _ = write_raw_event(&mut uinput_file, EV_KEY, keycode, val);
+                                    let _ = write_raw_event(&mut uinput_file, EV_SYN, SYN_REPORT, 0);
+                                }
+                                InputDaemonMsg::SimulateClick { button } => {
+                                    let _ = write_raw_event(&mut uinput_file, EV_KEY, button, 1);
+                                    let _ = write_raw_event(&mut uinput_file, EV_SYN, SYN_REPORT, 0);
+                                    let tx_clone = tx.clone();
+                                    tokio::spawn(async move {
+                                        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
+                                        let _ = tx_clone.send(CoordinatorMsg::InternalReleaseButton { button });
+                                    });
+                                }
+                                InputDaemonMsg::SimulateKeyPress { keycode } => {
+                                    let _ = write_raw_event(&mut uinput_file, EV_KEY, keycode, 1);
+                                    let _ = write_raw_event(&mut uinput_file, EV_SYN, SYN_REPORT, 0);
+                                    let tx_clone = tx.clone();
+                                    tokio::spawn(async move {
+                                        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
+                                        let _ = tx_clone.send(CoordinatorMsg::InternalReleaseKey { keycode });
+                                    });
+                                }
                             }
                         }
+                        CoordinatorMsg::InternalReleaseButton { button } => {
+                            let _ = write_raw_event(&mut uinput_file, EV_KEY, button, 0);
+                            let _ = write_raw_event(&mut uinput_file, EV_SYN, SYN_REPORT, 0);
+                        }
+                        CoordinatorMsg::InternalReleaseKey { keycode } => {
+                            let _ = write_raw_event(&mut uinput_file, EV_KEY, keycode, 0);
+                            let _ = write_raw_event(&mut uinput_file, EV_SYN, SYN_REPORT, 0);
+                        }
                         CoordinatorMsg::PhysicalMove { dx, dy, timestamp } => {
+                            update_pointer_coords(dx, dy);
                             let dt = timestamp.duration_since(state.last_move_time).as_secs_f32();
                             state.last_move_time = timestamp;
 
@@ -586,6 +681,7 @@ pub fn run_input_daemon(
                             }
                         }
                         CoordinatorMsg::PhysicalTrackpadMove { dx, dy, timestamp } => {
+                            update_pointer_coords(dx, dy);
                             let dt = timestamp.duration_since(state.last_trackpad_move_time).as_secs_f32();
                             state.last_trackpad_move_time = timestamp;
 
@@ -739,6 +835,7 @@ pub fn run_input_daemon(
 
                                 if steps_x != 0 || steps_y != 0 {
                                     let _ = write_mouse_move(&mut uinput_file, steps_x, steps_y);
+                                    update_pointer_coords(steps_x, steps_y);
                                 }
                             }
                         }
@@ -764,6 +861,7 @@ pub fn run_input_daemon(
 
                             if steps_x != 0 || steps_y != 0 {
                                 let _ = write_mouse_move(&mut uinput_file, steps_x, steps_y);
+                                update_pointer_coords(steps_x, steps_y);
                             }
                         }
                     }
diff --git a/src/inspector_cli.rs b/src/inspector_cli.rs
new file mode 100644
index 0000000..47bb034
--- /dev/null
+++ b/src/inspector_cli.rs
@@ -0,0 +1,140 @@
+use wayland_client::{
+    globals::{registry_queue_init, GlobalListContents},
+    protocol::wl_registry,
+    Connection, Dispatch, QueueHandle,
+};
+use serde_json::Value;
+
+// Import generated client protocols from ccec crate
+mod protocol;
+use protocol::clear_inspector::client::zclear_inspector_v1::{self, ZclearInspectorV1};
+
+struct InspectorState {
+    inspector: Option<ZclearInspectorV1>,
+    done: bool,
+    surfaces: Vec<InspectedSurface>,
+}
+
+struct InspectedSurface {
+    title: String,
+    app_id: String,
+    x: i32,
+    y: i32,
+    width: i32,
+    height: i32,
+    state: String,
+}
+
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for InspectorState {
+    fn event(
+        state: &mut Self,
+        registry: &wl_registry::WlRegistry,
+        event: wl_registry::Event,
+        _data: &GlobalListContents,
+        _conn: &Connection,
+        qh: &QueueHandle<Self>,
+    ) {
+        if let wl_registry::Event::Global { name, interface, version } = event {
+            if interface == "zclear_inspector_v1" {
+                state.inspector = Some(registry.bind::<ZclearInspectorV1, _, _>(name, version, qh, ()));
+            }
+        }
+    }
+}
+
+impl Dispatch<ZclearInspectorV1, ()> for InspectorState {
+    fn event(
+        state: &mut Self,
+        _proxy: &ZclearInspectorV1,
+        event: zclear_inspector_v1::Event,
+        _data: &(),
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+    ) {
+        match event {
+            zclear_inspector_v1::Event::InspectedSurface { title, app_id, x, y, width, height, state: surface_state } => {
+                state.surfaces.push(InspectedSurface {
+                    title,
+                    app_id,
+                    x,
+                    y,
+                    width,
+                    height,
+                    state: surface_state,
+                });
+            }
+            zclear_inspector_v1::Event::InspectedSurfaceDone => {
+                state.done = true;
+            }
+        }
+    }
+}
+
+fn main() {
+    let conn = match Connection::connect_to_env() {
+        Ok(c) => c,
+        Err(e) => {
+            eprintln!("Failed to connect to Wayland display socket: {:?}", e);
+            std::process::exit(1);
+        }
+    };
+
+    let (_globals, mut event_queue) = registry_queue_init(&conn).unwrap();
+    let _qh = event_queue.handle();
+
+    let mut state = InspectorState {
+        inspector: None,
+        done: false,
+        surfaces: Vec::new(),
+    };
+
+    // Populate registry globals
+    event_queue.roundtrip(&mut state).unwrap();
+
+    let inspector = match state.inspector.take() {
+        Some(ins) => ins,
+        None => {
+            eprintln!("Error: zclear_inspector_v1 global protocol not found on Wayland registry.");
+            eprintln!("Ensure clear-river is running and supports the inspector protocol.");
+            std::process::exit(1);
+        }
+    };
+
+    // Request active inspected surfaces
+    inspector.get_inspected_surfaces();
+
+    // Dispatch until completed
+    while !state.done {
+        event_queue.blocking_dispatch(&mut state).unwrap();
+    }
+
+    // Print results
+    println!("Found {} active inspected surface(s):", state.surfaces.len());
+    println!("{}", "=".repeat(60));
+
+    for (idx, surface) in state.surfaces.iter().enumerate() {
+        println!("Surface #{}:", idx + 1);
+        println!("  Title:       {}", surface.title);
+        println!("  App ID:      {}", surface.app_id);
+        println!("  Position:    (x: {}, y: {})", surface.x, surface.y);
+        println!("  Dimensions:  {}x{} px", surface.width, surface.height);
+        
+        // Pretty print widget state if it's JSON
+        print!("  Widget Tree: ");
+        if let Ok(json_val) = serde_json::from_str::<Value>(&surface.state) {
+            if let Ok(pretty_json) = serde_json::to_string_pretty(&json_val) {
+                // Indent pretty JSON for clean display
+                let indented = pretty_json.lines()
+                    .map(|line| format!("    {}", line))
+                    .collect::<Vec<String>>()
+                    .join("\n");
+                println!("\n{}", indented);
+            } else {
+                println!("{}", surface.state);
+            }
+        } else {
+            println!("{}", surface.state);
+        }
+        println!("{}", "=".repeat(60));
+    }
+}
diff --git a/src/ipc.rs b/src/ipc.rs
index 7326bdc..5cd0b12 100644
--- a/src/ipc.rs
+++ b/src/ipc.rs
@@ -1,19 +1,57 @@
-// IPC command parser for clearwm
-// Ported from handle_ipc_command in clearwm.c
+// IPC command parser for ccec
+// Ported from handle_ipc_command in ccec.c
 
 use crate::config::{parse_keysym, spawn_command_bg};
 use crate::types::{
     parse_action, parse_button, parse_hex_color, parse_modifiers, parse_tiling_mode, Action,
-    ModeRule, PendingPointerBinding, PendingXkbBinding, WindowManager, NUM_TAGS,
+    ModeRule, PendingPointerBinding, PendingXkbBinding, WindowManager, NUM_TAGS, TilingMode,
 };
 
+fn get_window_under_pointer(state: &WindowManager) -> Option<u64> {
+    // 1. Check seat.hovered_window_id first
+    for seat in &state.seats {
+        if !seat.removed {
+            if let Some(wid) = seat.hovered_window_id {
+                return Some(wid);
+            }
+        }
+    }
+
+    // 2. Fallback: find it using POINTER_X/POINTER_Y and window geometries
+    let px = crate::input::POINTER_X.load(std::sync::atomic::Ordering::SeqCst) as f64;
+    let py = crate::input::POINTER_Y.load(std::sync::atomic::Ordering::SeqCst) as f64;
+
+    let active_tags = state.active_tags;
+    let mut best_wid = None;
+
+    for win in &state.windows {
+        if win.closed
+            || (win.tags & active_tags) == 0
+            || win.app_id.as_deref() == Some("clear-status-interface")
+            || win.tiling_mode == TilingMode::Popup
+        {
+            continue;
+        }
+        let wx = win.anim_x.unwrap_or(win.x as f64);
+        let wy = win.anim_y.unwrap_or(win.y as f64);
+        let ww = win.anim_w.unwrap_or(win.width as f64);
+        let wh = win.anim_h.unwrap_or(win.height as f64);
+
+        if px >= wx && px <= wx + ww && py >= wy && py <= wy + wh {
+            best_wid = Some(win.id);
+        }
+    }
+
+    best_wid
+}
+
 /// Handle an IPC command string, modifying the window manager state.
 /// This is called from the Unix socket listener when clearctl sends a command.
-pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
+pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) -> String {
     // Strip trailing newlines/spaces
     let cmd = cmd.trim_end_matches(|c| c == '\n' || c == '\r' || c == ' ');
     if cmd.is_empty() {
-        return;
+        return "\n".to_string();
     }
 
     // Split the command into tokens
@@ -21,6 +59,8 @@ pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
     let tok = tokens[0];
     let rest = if tokens.len() > 1 { tokens[1] } else { "" };
 
+    let mut reply = "ok\n".to_string();
+
     match tok {
         "spawn" => {
             if !rest.is_empty() {
@@ -76,25 +116,32 @@ pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
             crate::restart::wm_restart();
         }
         "expose" => {
-            state.expose_active = !state.expose_active;
+            let active = !state.expose_active;
+            if !active {
+                // Exiting expose mode! Focus the window under pointer.
+                if let Some(wid) = get_window_under_pointer(state) {
+                    for seat in &mut state.seats {
+                        if !seat.removed {
+                            seat.focused_window_id = Some(wid);
+                        }
+                    }
+                    state.move_window_to_end(wid);
+                    state.needs_focus = true;
+                }
+            }
+            crate::wm::set_expose_active(state, active);
             state.needs_render = true;
             state.needs_status_update = true;
         }
         "expose-exit" => {
             if state.expose_active {
-                state.expose_active = false;
-                let mut windows_to_focus = Vec::new();
-                for seat in &state.seats {
-                    if seat.removed {
-                        continue;
-                    }
-                    if let Some(wid) = seat.hovered_window_id {
-                        windows_to_focus.push((seat.id, wid));
-                    }
-                }
-                for (seat_id, wid) in windows_to_focus {
-                    if let Some(seat) = state.seats.iter_mut().find(|s| s.id == seat_id) {
-                        seat.focused_window_id = Some(wid);
+                let hovered_id = get_window_under_pointer(state);
+                crate::wm::set_expose_active(state, false);
+                if let Some(wid) = hovered_id {
+                    for seat in &mut state.seats {
+                        if !seat.removed {
+                            seat.focused_window_id = Some(wid);
+                        }
                     }
                     state.move_window_to_end(wid);
                     state.needs_focus = true;
@@ -237,14 +284,110 @@ pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
                 }
             } else if parts.len() == 1 && !parts[0].is_empty() {
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", parts[0]);
+                    crate::config::show_notification("ccec", parts[0]);
+                }
+            }
+        }
+        "pointer-location" => {
+            let px = crate::input::POINTER_X.load(std::sync::atomic::Ordering::SeqCst);
+            let py = crate::input::POINTER_Y.load(std::sync::atomic::Ordering::SeqCst);
+            reply = format!("{} {}\n", px, py);
+        }
+        "pointer-move-to" => {
+            let parts: Vec<&str> = rest.split_whitespace().collect();
+            if parts.len() == 2 {
+                if let (Ok(target_x), Ok(target_y)) = (parts[0].parse::<i32>(), parts[1].parse::<i32>()) {
+                    let cur_x = crate::input::POINTER_X.load(std::sync::atomic::Ordering::SeqCst);
+                    let cur_y = crate::input::POINTER_Y.load(std::sync::atomic::Ordering::SeqCst);
+                    let dx = target_x - cur_x;
+                    let dy = target_y - cur_y;
+                    if let Some(ref controller) = state.input_controller {
+                        let _ = controller.send(crate::input::InputDaemonMsg::SimulateMove { dx, dy });
+                    }
+                } else {
+                    reply = "error: invalid coordinates\n".to_string();
+                }
+            } else {
+                reply = "error: usage: pointer-move-to <x> <y>\n".to_string();
+            }
+        }
+        "pointer-move-by" => {
+            let parts: Vec<&str> = rest.split_whitespace().collect();
+            if parts.len() == 2 {
+                if let (Ok(dx), Ok(dy)) = (parts[0].parse::<i32>(), parts[1].parse::<i32>()) {
+                    if let Some(ref controller) = state.input_controller {
+                        let _ = controller.send(crate::input::InputDaemonMsg::SimulateMove { dx, dy });
+                    }
+                } else {
+                    reply = "error: invalid deltas\n".to_string();
                 }
+            } else {
+                reply = "error: usage: pointer-move-by <dx> <dy>\n".to_string();
+            }
+        }
+        "pointer-click" => {
+            let btn = parse_button(rest) as u16;
+            if btn != 0 {
+                if let Some(ref controller) = state.input_controller {
+                    let _ = controller.send(crate::input::InputDaemonMsg::SimulateClick { button: btn });
+                }
+            } else {
+                reply = "error: invalid button\n".to_string();
+            }
+        }
+        "pointer-press" => {
+            let btn = parse_button(rest) as u16;
+            if btn != 0 {
+                if let Some(ref controller) = state.input_controller {
+                    let _ = controller.send(crate::input::InputDaemonMsg::SimulateButton { button: btn, press: true });
+                }
+            } else {
+                reply = "error: invalid button\n".to_string();
+            }
+        }
+        "pointer-release" => {
+            let btn = parse_button(rest) as u16;
+            if btn != 0 {
+                if let Some(ref controller) = state.input_controller {
+                    let _ = controller.send(crate::input::InputDaemonMsg::SimulateButton { button: btn, press: false });
+                }
+            } else {
+                reply = "error: invalid button\n".to_string();
+            }
+        }
+        "keypress" => {
+            if let Some(key) = parse_keycode(rest) {
+                if let Some(ref controller) = state.input_controller {
+                    let _ = controller.send(crate::input::InputDaemonMsg::SimulateKeyPress { keycode: key });
+                }
+            } else {
+                reply = "error: invalid key\n".to_string();
+            }
+        }
+        "key-press" => {
+            if let Some(key) = parse_keycode(rest) {
+                if let Some(ref controller) = state.input_controller {
+                    let _ = controller.send(crate::input::InputDaemonMsg::SimulateKey { keycode: key, press: true });
+                }
+            } else {
+                reply = "error: invalid key\n".to_string();
+            }
+        }
+        "key-release" => {
+            if let Some(key) = parse_keycode(rest) {
+                if let Some(ref controller) = state.input_controller {
+                    let _ = controller.send(crate::input::InputDaemonMsg::SimulateKey { keycode: key, press: false });
+                }
+            } else {
+                reply = "error: invalid key\n".to_string();
             }
         }
         _ => {
-            // Unknown command, ignore
+            reply = "error: unknown command\n".to_string();
         }
     }
+
+    reply
 }
 
 /// Parse a tag number from a command like "view-1" or "view 1"
@@ -276,7 +419,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.gap = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Gap set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Gap set to {}px", value));
                 }
             }
         }
@@ -284,7 +427,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.gap_top = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Top gap set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Top gap set to {}px", value));
                 }
             }
         }
@@ -292,7 +435,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.gap_left = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Left gap set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Left gap set to {}px", value));
                 }
             }
         }
@@ -300,7 +443,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.gap_right = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Right gap set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Right gap set to {}px", value));
                 }
             }
         }
@@ -308,7 +451,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.gap_bottom = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Bottom gap set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Bottom gap set to {}px", value));
                 }
             }
         }
@@ -316,7 +459,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.cascade_offset = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Cascade offset set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Cascade offset set to {}px", value));
                 }
             }
         }
@@ -324,7 +467,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.bar_height = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Bar height set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Bar height set to {}px", value));
                 }
             }
         }
@@ -332,7 +475,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.border_width = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Border width set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Border width set to {}px", value));
                 }
             }
         }
@@ -340,7 +483,15 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.border_font_size = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Border font size set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Border font size set to {}px", value));
+                }
+            }
+        }
+        "transition_duration" => {
+            if let Ok(value) = value_str.parse::<i32>() {
+                state.layout.transition_duration = value;
+                if state.notifications_enable {
+                    crate::config::show_notification("ccec", &format!("Transition duration set to {}ms", value));
                 }
             }
         }
@@ -348,7 +499,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.fullscreen_border_width = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Fullscreen border width set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Fullscreen border width set to {}px", value));
                 }
             }
         }
@@ -356,7 +507,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.cascade_border_width = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Cascade border width set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Cascade border width set to {}px", value));
                 }
             }
         }
@@ -364,23 +515,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.grid_border_width = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Grid border width set to {}px", value));
-                }
-            }
-        }
-        "vsplit_border_width" => {
-            if let Ok(value) = value_str.parse::<i32>() {
-                state.layout.vsplit_border_width = value;
-                if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Vsplit border width set to {}px", value));
-                }
-            }
-        }
-        "hsplit_border_width" => {
-            if let Ok(value) = value_str.parse::<i32>() {
-                state.layout.hsplit_border_width = value;
-                if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Hsplit border width set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Grid border width set to {}px", value));
                 }
             }
         }
@@ -388,7 +523,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
             if let Ok(value) = value_str.parse::<i32>() {
                 state.layout.floating_border_width = value;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Floating border width set to {}px", value));
+                    crate::config::show_notification("ccec", &format!("Floating border width set to {}px", value));
                 }
             }
         }
@@ -399,7 +534,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
                 state.layout.border_b = b;
                 state.layout.border_a = a;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Border color set to {}", value_str));
+                    crate::config::show_notification("ccec", &format!("Border color set to {}", value_str));
                 }
             }
         }
@@ -410,7 +545,7 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
                 state.layout.background_b = b;
                 state.layout.background_a = a;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Background color set to {}", value_str));
+                    crate::config::show_notification("ccec", &format!("Background color set to {}", value_str));
                 }
             }
         }
@@ -474,7 +609,7 @@ fn handle_set_mode_command(rest: &str, state: &mut WindowManager) {
         window.mode_locked = true;
         if notifications_enable {
             let win_title = window.title.as_deref().unwrap_or("Window");
-            crate::config::show_notification("clearwm", &format!("Tiling mode set to {} for: {}", mode.as_str(), win_title));
+            crate::config::show_notification("ccec", &format!("Tiling mode set to {} for: {}", mode.as_str(), win_title));
         }
         state.needs_render = true;
         state.needs_status_update = true;
@@ -551,7 +686,7 @@ fn handle_tag_layout_command(rest: &str, state: &mut WindowManager) {
             state.tag_layouts[tag as usize - 1] = mode;
             state.has_tag_layout[tag as usize - 1] = true;
             if state.notifications_enable {
-                crate::config::show_notification("clearwm", &format!("Tag {} layout set to {}", tag, mode.as_str()));
+                crate::config::show_notification("ccec", &format!("Tag {} layout set to {}", tag, mode.as_str()));
             }
             state.needs_render = true;
             state.needs_status_update = true;
@@ -632,7 +767,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
             }
             if state.tap_to_click != old_val && state.notifications_enable {
                 crate::config::show_notification(
-                    "clearwm",
+                    "ccec",
                     &format!(
                         "Tap-to-click {}",
                         if state.tap_to_click { "enabled" } else { "disabled" }
@@ -645,7 +780,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.accel_speed = Some(val);
                 state.tap_config_applied = false;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Acceleration speed set to {}", val));
+                    crate::config::show_notification("ccec", &format!("Acceleration speed set to {}", val));
                 }
             }
         }
@@ -655,7 +790,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.accel_profile = Some(val.clone());
                 state.tap_config_applied = false;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Acceleration profile set to {}", val));
+                    crate::config::show_notification("ccec", &format!("Acceleration profile set to {}", val));
                 }
             }
         }
@@ -677,7 +812,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.tap_config_applied = false;
                 if state.notifications_enable {
                     crate::config::show_notification(
-                        "clearwm",
+                        "ccec",
                         &format!(
                             "Natural scroll {}",
                             if state.natural_scroll.unwrap_or(false) { "enabled" } else { "disabled" }
@@ -704,7 +839,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.tap_config_applied = false;
                 if state.notifications_enable {
                     crate::config::show_notification(
-                        "clearwm",
+                        "ccec",
                         &format!(
                             "Disable-while-typing {}",
                             if state.dwt.unwrap_or(false) { "enabled" } else { "disabled" }
@@ -731,7 +866,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.tap_config_applied = false;
                 if state.notifications_enable {
                     crate::config::show_notification(
-                        "clearwm",
+                        "ccec",
                         &format!(
                             "Disable-while-trackpointing {}",
                             if state.dwtp.unwrap_or(false) { "enabled" } else { "disabled" }
@@ -760,7 +895,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.trackpoint_accel_speed = Some(val);
                 state.tap_config_applied = false;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Trackpoint acceleration speed set to {}", val));
+                    crate::config::show_notification("ccec", &format!("Trackpoint acceleration speed set to {}", val));
                 }
             }
         }
@@ -770,7 +905,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.trackpoint_accel_profile = Some(val.clone());
                 state.tap_config_applied = false;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Trackpoint acceleration profile set to {}", val));
+                    crate::config::show_notification("ccec", &format!("Trackpoint acceleration profile set to {}", val));
                 }
             }
         }
@@ -780,7 +915,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.cursor_theme = Some(val.clone());
                 state.cursor_theme_applied = false;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Cursor theme set to {}", val));
+                    crate::config::show_notification("ccec", &format!("Cursor theme set to {}", val));
                 }
             }
         }
@@ -789,7 +924,7 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
                 state.cursor_size = Some(val);
                 state.cursor_theme_applied = false;
                 if state.notifications_enable {
-                    crate::config::show_notification("clearwm", &format!("Cursor size set to {}", val));
+                    crate::config::show_notification("ccec", &format!("Cursor size set to {}", val));
                 }
             }
         }
@@ -797,6 +932,50 @@ fn handle_input_command(rest: &str, state: &mut WindowManager) {
     }
 }
 
+
+fn parse_keycode(key: &str) -> Option<u16> {
+    let key = key.trim();
+    if let Ok(val) = key.parse::<u16>() {
+        return Some(val);
+    }
+    match key.to_lowercase().as_str() {
+        "esc" | "escape" => Some(1),
+        "1" => Some(2), "2" => Some(3), "3" => Some(4), "4" => Some(5),
+        "5" => Some(6), "6" => Some(7), "7" => Some(8), "8" => Some(9),
+        "9" => Some(10), "0" => Some(11),
+        "minus" => Some(12), "equal" => Some(13), "backspace" => Some(14),
+        "tab" => Some(15),
+        "q" => Some(16), "w" => Some(17), "e" => Some(18), "r" => Some(19),
+        "t" => Some(20), "y" => Some(21), "u" => Some(22), "i" => Some(23),
+        "o" => Some(24), "p" => Some(25),
+        "leftbrace" | "[" => Some(26), "rightbrace" | "]" => Some(27),
+        "enter" | "return" => Some(28),
+        "ctrl" | "leftctrl" => Some(29),
+        "a" => Some(30), "s" => Some(31), "d" => Some(32), "f" => Some(33),
+        "g" => Some(34), "h" => Some(35), "j" => Some(36), "k" => Some(37),
+        "l" => Some(38), "semicolon" | ";" => Some(39), "apostrophe" | "'" => Some(40),
+        "grave" | "`" => Some(41), "shift" | "leftshift" => Some(42),
+        "backslash" | "\\" => Some(43),
+        "z" => Some(44), "x" => Some(45), "c" => Some(46), "v" => Some(47),
+        "b" => Some(48), "n" => Some(49), "m" => Some(50),
+        "comma" | "," => Some(51), "dot" | "." => Some(52), "slash" | "/" => Some(53),
+        "rightshift" => Some(54),
+        "alt" | "leftalt" => Some(56), "space" => Some(57), "capslock" => Some(58),
+        "f1" => Some(59), "f2" => Some(60), "f3" => Some(61), "f4" => Some(62),
+        "f5" => Some(63), "f6" => Some(64), "f7" => Some(65), "f8" => Some(66),
+        "f9" => Some(67), "f10" => Some(68),
+        "f11" => Some(87), "f12" => Some(88),
+        "rightctrl" => Some(97), "rightalt" => Some(100),
+        "home" => Some(102), "up" => Some(103), "pageup" => Some(104),
+        "left" => Some(105), "right" => Some(106), "end" => Some(107),
+        "down" => Some(108), "pagedown" => Some(109), "insert" => Some(110),
+        "delete" => Some(111),
+        "super" | "hyper" | "meta" | "logo" | "leftmeta" | "leftsuper" => Some(125),
+        "rightmeta" | "rightsuper" => Some(126),
+        _ => None,
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1037,4 +1216,43 @@ mod tests {
         handle_ipc_command("view-prev", &mut state);
         assert_eq!(state.active_tags, 1); // Tag 1
     }
+
+    #[test]
+    fn test_ipc_pointer_and_keys() {
+        let mut state = WindowManager::default();
+        
+        // Test pointer location query
+        let location = handle_ipc_command("pointer-location", &mut state);
+        assert!(location.ends_with("\n"));
+        let coords: Vec<&str> = location.trim().split_whitespace().collect();
+        assert_eq!(coords.len(), 2);
+        assert_eq!(coords[0].parse::<i32>().is_ok(), true);
+        assert_eq!(coords[1].parse::<i32>().is_ok(), true);
+
+        // Test button parsing helpers
+        assert_eq!(parse_button("left"), 272);
+        assert_eq!(parse_button("right"), 273);
+        assert_eq!(parse_button("middle"), 274);
+        assert_eq!(parse_button("side"), 275);
+        assert_eq!(parse_button("extra"), 276);
+        assert_eq!(parse_button("280"), 280);
+        assert_eq!(parse_button("invalid"), 0);
+
+        // Test keycode parsing helpers
+        assert_eq!(parse_keycode("escape"), Some(1));
+        assert_eq!(parse_keycode("enter"), Some(28));
+        assert_eq!(parse_keycode("a"), Some(30));
+        assert_eq!(parse_keycode("30"), Some(30));
+        assert_eq!(parse_keycode("invalid"), None);
+
+        // Test some simulation commands return error if invalid or ok (without input controller it shouldn't crash)
+        let r1 = handle_ipc_command("pointer-move-to invalid", &mut state);
+        assert!(r1.starts_with("error:"));
+        let r2 = handle_ipc_command("pointer-move-by 10", &mut state);
+        assert!(r2.starts_with("error:"));
+        let r3 = handle_ipc_command("pointer-click invalid", &mut state);
+        assert!(r3.starts_with("error:"));
+        let r4 = handle_ipc_command("keypress invalid", &mut state);
+        assert!(r4.starts_with("error:"));
+    }
 }
diff --git a/src/ipc_server.rs b/src/ipc_server.rs
index 2ad5b6d..caece55 100644
--- a/src/ipc_server.rs
+++ b/src/ipc_server.rs
@@ -4,17 +4,22 @@ use std::sync::mpsc;
 
 use crate::paths;
 
+pub struct IpcRequest {
+    pub command: String,
+    pub reply_tx: mpsc::Sender<String>,
+}
+
 pub struct IpcReceiver {
-    pub rx: mpsc::Receiver<String>,
-    pub tx: mpsc::Sender<String>,
+    pub rx: mpsc::Receiver<IpcRequest>,
+    pub tx: mpsc::Sender<IpcRequest>,
 }
 
 pub fn spawn_ipc_server(pipe_write: libc::c_int) -> IpcReceiver {
-    let (tx, rx) = mpsc::channel::<String>();
+    let (tx, rx) = mpsc::channel::<IpcRequest>();
     let tx_clone = tx.clone();
 
     std::thread::Builder::new()
-        .name("clearwm-ipc".into())
+        .name("ccec-ipc".into())
         .spawn(move || {
             ipc_server_main(tx, pipe_write);
         })
@@ -23,7 +28,7 @@ pub fn spawn_ipc_server(pipe_write: libc::c_int) -> IpcReceiver {
     IpcReceiver { rx, tx: tx_clone }
 }
 
-fn ipc_server_main(tx: mpsc::Sender<String>, pipe_write: libc::c_int) {
+fn ipc_server_main(tx: mpsc::Sender<IpcRequest>, pipe_write: libc::c_int) {
     let socket_path = paths::get_socket_path();
     let _ = std::fs::remove_file(&socket_path);
 
@@ -75,19 +80,35 @@ fn ipc_server_main(tx: mpsc::Sender<String>, pipe_write: libc::c_int) {
                 Ok(n) => {
                     let s = String::from_utf8_lossy(&buf[..n]);
                     let mut sent = false;
+                    let (reply_tx, reply_rx) = mpsc::channel();
+                    let mut cmd_count = 0;
                     for line in s.lines() {
                         let cmd = line.trim().to_string();
                         if !cmd.is_empty() {
-                            let _ = tx.send(cmd);
+                            let _ = tx.send(IpcRequest {
+                                command: cmd,
+                                reply_tx: reply_tx.clone(),
+                            });
                             sent = true;
+                            cmd_count += 1;
                         }
                     }
                     if sent {
-                        let _ = stream.write_all(b"ok\n");
-                        dead.push(i);
+                        // Wake up main thread
                         unsafe {
                             libc::write(pipe_write, &1u8 as *const u8 as *const libc::c_void, 1);
                         }
+                        
+                        let mut response = String::new();
+                        for _ in 0..cmd_count {
+                            if let Ok(res) = reply_rx.recv_timeout(std::time::Duration::from_millis(1000)) {
+                                response.push_str(&res);
+                            } else {
+                                response.push_str("error: timeout or no response\n");
+                            }
+                        }
+                        let _ = stream.write_all(response.as_bytes());
+                        dead.push(i);
                     }
                     activity = true;
                 }
diff --git a/src/lib.rs b/src/lib.rs
index 8d13552..df105d1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-// clearwm — Wayland window manager for river, written in Rust
+// ccec — Wayland window manager for river, written in Rust
 
 pub mod borders;
 pub mod config;
diff --git a/src/main.rs b/src/main.rs
index 83c813d..af90874 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,18 +1,18 @@
-// clearwm — Wayland window manager for river
-
-use clearwm::config::parse_config;
-use clearwm::ipc;
-use clearwm::ipc_server;
-use clearwm::restart;
-use clearwm::status_server;
-use clearwm::wayland::wayland_init;
+// ccec — Wayland window manager for river
+
+use ccec::config::parse_config;
+use ccec::ipc;
+use ccec::ipc_server;
+use ccec::restart;
+use ccec::status_server;
+use ccec::wayland::wayland_init;
 use std::env;
 use std::fs;
 
-use clearwm::paths;
+use ccec::paths;
 
-/// Write a crash/exit trace to /tmp/clearwm-death.log so we can diagnose
-/// why clearwm dies even when the normal log gets overwritten on restart.
+/// Write a crash/exit trace to /tmp/ccec-death.log so we can diagnose
+/// why ccec dies even when the normal log gets overwritten on restart.
 fn log_death(msg: &str) {
     use std::io::Write;
     if let Ok(mut f) = std::fs::OpenOptions::new()
@@ -40,7 +40,7 @@ fn main() {
         }
     }));
 
-    eprintln!("clearwm starting...");
+    eprintln!("ccec starting...");
 
     // Start the status socket server thread (for waybar integration)
     let status_sender = status_server::spawn_status_server();
@@ -66,7 +66,7 @@ fn main() {
     }
 
     // Connect to Wayland display and get initial state.
-    // SIGUSR2 handler: dump backtrace to /tmp/clearwm-bt.txt for debugging busy loops
+    // SIGUSR2 handler: dump backtrace to /tmp/ccec-bt.txt for debugging busy loops
     unsafe {
         nix::sys::signal::sigaction(
             nix::sys::signal::SIGUSR2,
@@ -101,16 +101,16 @@ fn main() {
     let ipc_tx = ipc_server.tx;
 
     // Setup channel for configuration updates:
-    let (config_tx, config_rx) = tokio::sync::mpsc::unbounded_channel::<(clearwm::config::InertialConfig, bool)>();
+    let (config_tx, config_rx) = tokio::sync::mpsc::unbounded_channel::<ccec::input::InputDaemonMsg>();
     state.wm.input_controller = Some(config_tx);
 
     // Spawn the input subsystem background thread:
     let pipe_write_clone = pipe_write;
     let ipc_tx_clone = ipc_tx.clone();
     std::thread::Builder::new()
-        .name("clearwm-input-subsystem".into())
+        .name("ccec-input-subsystem".into())
         .spawn(move || {
-            if let Err(e) = clearwm::input::run_input_daemon(config_rx, ipc_tx_clone, pipe_write_clone) {
+            if let Err(e) = ccec::input::run_input_daemon(config_rx, ipc_tx_clone, pipe_write_clone) {
                 eprintln!("[input-subsystem] Fatal error: {:?}", e);
             }
         })
@@ -120,8 +120,8 @@ fn main() {
     state.status_sender = Some(status_sender);
 
     // Check if this is a restart
-    let cold_start = if env::var("CLEARWM_RESTARTING").as_deref() == Ok("1") {
-        env::remove_var("CLEARWM_RESTARTING");
+    let cold_start = if env::var("CCEC_RESTARTING").as_deref() == Ok("1") {
+        env::remove_var("CCEC_RESTARTING");
         false
     } else {
         true
@@ -139,7 +139,7 @@ fn main() {
     );
     let config_start = std::time::Instant::now();
     if let Ok(home) = env::var("HOME") {
-        let config_path = format!("{}/.config/clearwm/config.toml", home);
+        let config_path = format!("{}/.config/ccec/config.toml", home);
         if fs::metadata(&config_path).is_ok() {
             if let Err(e) = parse_config(&config_path, cold_start, &mut state.wm) {
                 eprintln!("[init] failed to load config: {}", e);
@@ -156,7 +156,7 @@ fn main() {
     if state.wm.pending_scale_apply && state.wm.output_scale > 0.0 && !state.output_heads.is_empty()
     {
         let qh = event_queue.handle();
-        clearwm::wayland::apply_output_scale(&mut state, &qh);
+        ccec::wayland::apply_output_scale(&mut state, &qh);
     }
 
     // Flush any queued requests from config loading (bindings, etc.)
@@ -165,6 +165,7 @@ fn main() {
     // Main loop — using poll to block on both Wayland socket and IPC wake-up pipe.
     // All work (including spawning) happens inside Dispatch callbacks.
     let mut loop_count: u64 = 0;
+    let mut last_animation_tick = std::time::Instant::now();
     loop {
         loop_count += 1;
         if loop_count % 10000 == 0 {
@@ -202,22 +203,39 @@ fn main() {
             nix::poll::PollFd::new(unsafe { std::os::fd::BorrowedFd::borrow_raw(pipe_read) }, nix::poll::PollFlags::POLLIN),
         ];
 
-        match nix::poll::poll(&mut poll_fds, nix::poll::PollTimeout::NONE) {
-            Ok(_) => {
-                // If Wayland FD is readable, read the events
-                if poll_fds[0].revents().unwrap_or(nix::poll::PollFlags::empty()).contains(nix::poll::PollFlags::POLLIN) {
-                    let _ = read_guard.read();
-                } else {
-                    // Otherwise drop the read guard to release the lock
-                    std::mem::drop(read_guard);
-                }
+        let timeout = if state.wm.animating {
+            let elapsed = last_animation_tick.elapsed();
+            let timeout_duration = if elapsed >= std::time::Duration::from_millis(16) {
+                std::time::Duration::ZERO
+            } else {
+                std::time::Duration::from_millis(16) - elapsed
+            };
+            nix::poll::PollTimeout::try_from(timeout_duration).unwrap()
+        } else {
+            nix::poll::PollTimeout::NONE
+        };
 
-                // If IPC pipe is readable, drain it
-                if poll_fds[1].revents().unwrap_or(nix::poll::PollFlags::empty()).contains(nix::poll::PollFlags::POLLIN) {
-                    let mut buf = [0u8; 128];
-                    unsafe {
-                        libc::read(pipe_read, buf.as_mut_ptr() as *mut libc::c_void, buf.len());
+        match nix::poll::poll(&mut poll_fds, timeout) {
+            Ok(num_events) => {
+                if num_events > 0 {
+                    // If Wayland FD is readable, read the events
+                    if poll_fds[0].revents().unwrap_or(nix::poll::PollFlags::empty()).contains(nix::poll::PollFlags::POLLIN) {
+                        let _ = read_guard.read();
+                    } else {
+                        // Otherwise drop the read guard to release the lock
+                        std::mem::drop(read_guard);
                     }
+
+                    // If IPC pipe is readable, drain it
+                    if poll_fds[1].revents().unwrap_or(nix::poll::PollFlags::empty()).contains(nix::poll::PollFlags::POLLIN) {
+                        let mut buf = [0u8; 128];
+                        unsafe {
+                            libc::read(pipe_read, buf.as_mut_ptr() as *mut libc::c_void, buf.len());
+                        }
+                    }
+                } else {
+                    // Timeout (num_events == 0) — drop the read guard to release the lock
+                    std::mem::drop(read_guard);
                 }
             }
             Err(e) => {
@@ -232,13 +250,22 @@ fn main() {
         // 5. Dispatch read events
         let _ = event_queue.dispatch_pending(&mut state);
 
+        // 6. If animating and frame budget elapsed, request next frame
+        if state.wm.animating && last_animation_tick.elapsed() >= std::time::Duration::from_millis(16) {
+            if let Some(ref wm) = state.window_manager {
+                wm.manage_dirty();
+                last_animation_tick = std::time::Instant::now();
+            }
+        }
+
         // 6. Process pending IPC commands from the socket channel
         let mut ipc_commands = false;
         loop {
             match ipc_rx.try_recv() {
-                Ok(cmd) => {
-                    eprintln!("[main] IPC command: {}", cmd);
-                    ipc::handle_ipc_command(&cmd, &mut state.wm);
+                Ok(req) => {
+                    eprintln!("[main] IPC command: {}", req.command);
+                    let reply = ipc::handle_ipc_command(&req.command, &mut state.wm);
+                    let _ = req.reply_tx.send(reply);
                     ipc_commands = true;
                 }
                 Err(std::sync::mpsc::TryRecvError::Empty) => break,
@@ -255,7 +282,7 @@ fn main() {
             }
             if !state.wm.tap_config_applied && !state.libinput_devices.is_empty() {
                 let qh = event_queue.handle();
-                clearwm::wayland::apply_input_config(&mut state, &qh);
+                ccec::wayland::apply_input_config(&mut state, &qh);
             }
         }
 
diff --git a/src/paths.rs b/src/paths.rs
index cff52f3..de180f6 100644
--- a/src/paths.rs
+++ b/src/paths.rs
@@ -2,65 +2,65 @@ use std::env;
 
 pub fn get_socket_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-{}.sock", display)
+        format!("/tmp/ccec-{}.sock", display)
     } else {
-        "/tmp/clearwm.sock".to_string()
+        "/tmp/ccec.sock".to_string()
     }
 }
 
 pub fn get_status_socket_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-status-{}.sock", display)
+        format!("/tmp/ccec-status-{}.sock", display)
     } else {
-        "/tmp/clearwm-status.sock".to_string()
+        "/tmp/ccec-status.sock".to_string()
     }
 }
 
 pub fn get_windows_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-windows-{}", display)
+        format!("/tmp/ccec-windows-{}", display)
     } else {
-        "/tmp/clearwm-windows".to_string()
+        "/tmp/ccec-windows".to_string()
     }
 }
 
 pub fn get_tags_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-tags-{}", display)
+        format!("/tmp/ccec-tags-{}", display)
     } else {
-        "/tmp/clearwm-tags".to_string()
+        "/tmp/ccec-tags".to_string()
     }
 }
 
 pub fn get_layout_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-layout-{}", display)
+        format!("/tmp/ccec-layout-{}", display)
     } else {
-        "/tmp/clearwm-layout".to_string()
+        "/tmp/ccec-layout".to_string()
     }
 }
 
 pub fn get_title_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-title-{}", display)
+        format!("/tmp/ccec-title-{}", display)
     } else {
-        "/tmp/clearwm-title".to_string()
+        "/tmp/ccec-title".to_string()
     }
 }
 
 pub fn get_death_log_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-death-{}.log", display)
+        format!("/tmp/ccec-death-{}.log", display)
     } else {
-        "/tmp/clearwm-death.log".to_string()
+        "/tmp/ccec-death.log".to_string()
     }
 }
 
 pub fn get_bt_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-bt-{}.txt", display)
+        format!("/tmp/ccec-bt-{}.txt", display)
     } else {
-        "/tmp/clearwm-bt.txt".to_string()
+        "/tmp/ccec-bt.txt".to_string()
     }
 }
 
@@ -74,24 +74,24 @@ pub fn get_input_coords_socket_path() -> String {
 
 pub fn get_log_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-{}.log", display)
+        format!("/tmp/ccec-{}.log", display)
     } else {
-        "/tmp/clearwm.log".to_string()
+        "/tmp/ccec.log".to_string()
     }
 }
 
 pub fn get_prev_log_path() -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-{}-prev.log", display)
+        format!("/tmp/ccec-{}-prev.log", display)
     } else {
-        "/tmp/clearwm-prev.log".to_string()
+        "/tmp/ccec-prev.log".to_string()
     }
 }
 
 pub fn get_xprop_path(wid: u64) -> String {
     if let Ok(display) = env::var("WAYLAND_DISPLAY") {
-        format!("/tmp/clearwm-xprop-{}-{}", wid, display)
+        format!("/tmp/ccec-xprop-{}-{}", wid, display)
     } else {
-        format!("/tmp/clearwm-xprop-{}", wid)
+        format!("/tmp/ccec-xprop-{}", wid)
     }
 }
diff --git a/src/protocol.rs b/src/protocol.rs
index 6049a23..8fd1d59 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -59,3 +59,7 @@ pub mod river_libinput_config {
 pub mod wlr_output_management {
     river_protocol!("protocol/wlr-output-management-unstable-v1.xml", []);
 }
+
+pub mod clear_inspector {
+    river_protocol!("protocol/clear-inspector-v1.xml", []);
+}
diff --git a/src/restart.rs b/src/restart.rs
index 1d38e12..af84cc5 100644
--- a/src/restart.rs
+++ b/src/restart.rs
@@ -1,4 +1,4 @@
-// Restart and reload logic for clearwm
+// Restart and reload logic for ccec
 
 use crate::config::parse_config;
 use crate::types::WindowManager;
@@ -7,17 +7,17 @@ use crate::types::WindowManager;
 ///
 /// This forks a child process that waits briefly for the parent to die
 /// (so River cleans up the old Wayland connection), then execs a fresh
-/// clearwm instance. The parent (current process) exits immediately.
+/// ccec instance. The parent (current process) exits immediately.
 ///
-/// The CLEARWM_RESTARTING environment variable signals that this is a restart
+/// The CCEC_RESTARTING environment variable signals that this is a restart
 /// (not a cold start), so the new process skips cold_start_only apps.
 ///
 /// ## Why setsid() is required
 ///
-/// clearwm is typically a session leader (PID == SID, started by River's
+/// ccec is typically a session leader (PID == SID, started by River's
 /// `-c` launch script). When a session leader exits, the kernel sends SIGHUP
 /// to all processes in that session — including the forked child. Without
-/// `setsid()`, the child dies from SIGHUP before it can exec, and clearwm
+/// `setsid()`, the child dies from SIGHUP before it can exec, and ccec
 /// never comes back.
 pub fn wm_restart() {
     use std::time::Instant;
@@ -58,9 +58,9 @@ pub fn wm_restart() {
     }
 
     // Signal that this is a restart, not a cold start
-    std::env::set_var("CLEARWM_RESTARTING", "1");
+    std::env::set_var("CCEC_RESTARTING", "1");
 
-    // Persist state to ~/.cache/clearwm_state for the new instance to restore.
+    // Persist state to ~/.cache/ccec_state for the new instance to restore.
     // Note: we can't pass &WindowManager here since wm_restart() has no access
     // to it. The state file is kept fresh by RenderStart's needs_status_update
     // path, so it should be reasonably up-to-date already.
@@ -76,13 +76,13 @@ pub fn wm_restart() {
     // CString is required because execl() needs a null-terminated C string;
     // Rust's String::as_ptr() is NOT guaranteed to be null-terminated.
     //
-    // We prefer the symlink path (~/.local/bin/clearwm) over current_exe()
+    // We prefer the symlink path (~/.local/bin/ccec) over current_exe()
     // because current_exe() resolves through /proc/self/exe to the real path,
     // which may be on a sync filesystem (Dropbox) that temporarily moves files.
     // The symlink is on the root filesystem and always available.
     let home = std::env::var("HOME").unwrap_or_default();
     let exe_path = if !home.is_empty() {
-        let symlink = format!("{}/.local/bin/clearwm", home);
+        let symlink = format!("{}/.local/bin/ccec", home);
         if std::path::Path::new(&symlink).exists() {
             std::path::PathBuf::from(symlink)
         } else {
@@ -95,7 +95,7 @@ pub fn wm_restart() {
     let path_cstr = std::ffi::CString::new(exe_path.to_string_lossy().into_owned())
         .unwrap_or_else(|_| std::process::exit(1));
 
-    // Fork: child waits for parent to die, then execs fresh clearwm.
+    // Fork: child waits for parent to die, then execs fresh ccec.
     // Parent exits so River tears down the old Wayland connection.
     let pid = unsafe { libc::fork() };
     if pid < 0 {
@@ -123,7 +123,7 @@ pub fn wm_restart() {
             std::thread::sleep(std::time::Duration::from_millis(500));
         }
 
-        // Close inherited Wayland FDs so the new clearwm instance doesn't
+        // Close inherited Wayland FDs so the new ccec instance doesn't
         // confuse River with stale connections.
         // (close everything except stdin/stdout/stderr)
         let max_fd = unsafe { libc::sysconf(libc::_SC_OPEN_MAX) } as i32;
@@ -133,7 +133,7 @@ pub fn wm_restart() {
             }
         }
 
-        // Exec the same binary — replaces this process with a fresh clearwm.
+        // Exec the same binary — replaces this process with a fresh ccec.
         // Retry up to 3 times with a short delay — the binary may be temporarily
         // unavailable if cargo build is replacing it mid-write (atomic rename).
         for attempt in 0..3 {
@@ -205,6 +205,7 @@ pub fn wm_reload(state: &mut WindowManager) {
     // Mark status for update after reload
     state.needs_status_update = true;
     state.tap_config_applied = false;
+    state.startup_spawned = false;
 
     // Clear mode rules
     state.mode_rules.clear();
@@ -216,7 +217,7 @@ pub fn wm_reload(state: &mut WindowManager) {
     // Try TOML config first
     let home = std::env::var("HOME").unwrap_or_default();
     if !home.is_empty() {
-        let config_path = format!("{}/.config/clearwm/config.toml", home);
+        let config_path = format!("{}/.config/ccec/config.toml", home);
         if std::path::Path::new(&config_path).exists() {
             match parse_config(&config_path, false, state) {
                 Ok(_) => {
@@ -225,12 +226,12 @@ pub fn wm_reload(state: &mut WindowManager) {
                         spawn_command_bg(cmd);
                     }
                     if state.notifications_enable {
-                        crate::config::show_notification("clearwm", "Configuration reloaded successfully");
+                        crate::config::show_notification("ccec", "Configuration reloaded successfully");
                     }
                 }
                 Err(e) => {
                     if state.notifications_enable {
-                        crate::config::show_notification("clearwm", &format!("Config reload failed:\n{}", e));
+                        crate::config::show_notification("ccec", &format!("Config reload failed:\n{}", e));
                     }
                 }
             }
diff --git a/src/state.rs b/src/state.rs
index 3188328..978d4ce 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -1,7 +1,7 @@
-// Persistent state file for clearwm restart recovery.
+// Persistent state file for ccec restart recovery.
 //
 // Writes window tag assignments and global tag/layout state to
-// ~/.cache/clearwm_state so that it survives restarts. On startup,
+// ~/.cache/ccec_state so that it survives restarts. On startup,
 // the state file is read and applied to re-advertised windows
 // matched by their River identifier (stable across WM restarts)
 // or app_id+title as a fallback.
@@ -21,12 +21,12 @@ use std::fs;
 use std::io::{BufRead, Write};
 use std::path::PathBuf;
 
-/// Get the state file path: ~/.cache/clearwm_state
+/// Get the state file path: ~/.cache/ccec_state
 fn state_file_path() -> PathBuf {
     let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
     let mut path = PathBuf::from(home);
     path.push(".cache");
-    path.push("clearwm_state");
+    path.push("ccec_state");
     path
 }
 
@@ -224,8 +224,6 @@ fn parse_tiling_mode_str(s: &str) -> TilingMode {
     match s {
         "Cascade" => TilingMode::Cascade,
         "Grid" => TilingMode::Grid,
-        "Vsplit" => TilingMode::Vsplit,
-        "Hsplit" => TilingMode::Hsplit,
         "Fullscreen" => TilingMode::Fullscreen,
         "Floating" => TilingMode::Floating,
         "Popup" => TilingMode::Popup,
@@ -306,6 +304,13 @@ pub fn apply_state(wm: &mut WindowManager, state: &PersistentState) {
                 win.mode_locked = true;
             }
         }
+
+        // Post-restore normalization: Ensure blank steam_proton helper windows are untagged (tags = 0)
+        let is_proton = win.app_id.as_deref() == Some("steam_proton");
+        let is_blank = win.title.is_none() || win.title.as_deref().map_or(true, |t| t.is_empty());
+        if is_proton && is_blank {
+            win.tags = 0;
+        }
     }
 
     eprintln!(
@@ -342,9 +347,9 @@ mod tests {
 
     #[test]
     fn test_write_read_roundtrip() {
-        let dir = std::env::temp_dir().join("clearwm_state_test");
+        let dir = std::env::temp_dir().join("ccec_state_test");
         let _ = fs::create_dir_all(&dir);
-        let path = dir.join("clearwm_state");
+        let path = dir.join("ccec_state");
 
         let mut wm = WindowManager::default();
         wm.active_tags = 0b1010; // tags 2 and 4
diff --git a/src/status.rs b/src/status.rs
index db9d43e..121eab2 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -12,7 +12,7 @@ pub fn write_status_files(state: &WindowManager) {
     use crate::paths;
     use std::io::Write;
 
-    // /tmp/clearwm-tags: active_tags focused_tags num_tags
+    // /tmp/ccec-tags: active_tags focused_tags num_tags
     if let Ok(mut f) = fs::File::create(paths::get_tags_path()) {
         let _ = writeln!(
             f,
@@ -21,7 +21,7 @@ pub fn write_status_files(state: &WindowManager) {
         );
     }
 
-    // /tmp/clearwm-layout: focused window's tiling mode
+    // /tmp/ccec-layout: focused window's tiling mode
     let mode_str = if state.expose_active {
         "Expose"
     } else {
@@ -35,7 +35,7 @@ pub fn write_status_files(state: &WindowManager) {
         let _ = writeln!(f, "{}", mode_str);
     }
 
-    // /tmp/clearwm-windows: one line per window
+    // /tmp/ccec-windows: one line per window
     if let Ok(mut f) = fs::File::create(paths::get_windows_path()) {
         let focused_title = state.focused_window().map(|w| w.title.clone());
 
@@ -66,7 +66,7 @@ pub fn write_status_files(state: &WindowManager) {
             );
         }
 
-        // /tmp/clearwm-title
+        // /tmp/ccec-title
         if let Some(title) = focused_title {
             if let Ok(mut tf) = fs::File::create(paths::get_title_path()) {
                 let _ = writeln!(tf, "{}", title.as_deref().unwrap_or("(null)"));
@@ -92,8 +92,6 @@ fn tiling_mode_str(mode: TilingMode) -> &'static str {
         TilingMode::Floating => "Floating",
         TilingMode::Cascade => "Cascade",
         TilingMode::Grid => "Grid",
-        TilingMode::Vsplit => "Vsplit",
-        TilingMode::Hsplit => "Hsplit",
         TilingMode::Fullscreen => "Fullscreen",
         TilingMode::Popup => "Popup",
     }
diff --git a/src/status_server.rs b/src/status_server.rs
index d734455..0a69de9 100644
--- a/src/status_server.rs
+++ b/src/status_server.rs
@@ -1,7 +1,7 @@
 // Status socket server for waybar integration
 //
 // Runs in a dedicated thread. Waybar custom module scripts connect to
-// /tmp/clearwm-status.sock, send a subscription line ("tags", "layout",
+// /tmp/ccec-status.sock, send a subscription line ("tags", "layout",
 // or "title"), and receive JSON lines whenever the status changes.
 //
 // The main loop sends updates through an mpsc channel — no blocking,
@@ -72,7 +72,7 @@ pub fn spawn_status_server() -> StatusSender {
     let (tx, rx) = mpsc::channel::<StatusUpdate>();
 
     std::thread::Builder::new()
-        .name("clearwm-status".into())
+        .name("ccec-status".into())
         .spawn(move || {
             status_server_main(rx);
         })
@@ -242,7 +242,7 @@ fn format_for_subscription(sub: Subscription, update: &StatusUpdate) -> String {
 /// This is the same logic that write_status_files() uses, but produces
 /// the data for the socket instead of writing to files.
 pub fn build_status_update(wm: &crate::types::WindowManager) -> StatusUpdate {
-    // Tags: generate the same pango-marked JSON that clearwm-tags.sh produces
+    // Tags: generate the same pango-marked JSON that ccec-tags.sh produces
     let tags_json = render_tags_json(
         wm.active_tags,
         wm.focused_tags,
@@ -269,7 +269,7 @@ pub fn build_status_update(wm: &crate::types::WindowManager) -> StatusUpdate {
 }
 
 /// Render tag state as a JSON string with pango markup, matching the format
-/// produced by the old clearwm-tags.sh script.
+/// produced by the old ccec-tags.sh script.
 ///
 /// Colors:
 /// - Active + Focused: bright (#a8c0d8)
diff --git a/src/tiling.rs b/src/tiling.rs
index 47e8ebb..69c79cc 100644
--- a/src/tiling.rs
+++ b/src/tiling.rs
@@ -1,4 +1,4 @@
-// Tiling formulas ported from clearwm.c
+// Tiling formulas ported from ccec.c
 
 /// Cascade depth factor: each depth step multiplies channels by this
 pub const CASCADE_DEPTH_FACTOR: f64 = 0.80;
@@ -80,71 +80,7 @@ pub fn tile_grid(
     (x, y, width, height)
 }
 
-/// Tile a window in vsplit mode (vertical splits — windows side by side).
-///
-/// Each window gets an equal share of the horizontal space.
-///
-/// Screen edges use per-side gaps; inter-window spacing uses `gap`.
-///   n = total windows in vsplit
-///   width  = (screen_w - gap_left - gap_right - (n - 1) * gap) / n - 2 * bw
-///   height = screen_h - gap_top - gap_bottom - bw * 2 - bar_height
-///   x = gap_left + bw + idx * (width + 2 * bw + gap)
-///   y = bar_height + gap_top + bw
-pub fn tile_vsplit(
-    screen_w: i32,
-    screen_h: i32,
-    gap: i32,
-    gap_top: i32,
-    gap_left: i32,
-    gap_right: i32,
-    gap_bottom: i32,
-    bw: i32,
-    bar_height: i32,
-    n_vsplit: i32,
-    idx: i32,
-) -> (i32, i32, i32, i32) {
-    let n = if n_vsplit < 1 { 1 } else { n_vsplit };
-    let width = (screen_w - gap_left - gap_right - (n - 1) * gap) / n - 2 * bw;
-    let height = screen_h - gap_top - gap_bottom - bw * 2 - bar_height;
-    let width = if width < 1 { 1 } else { width };
-    let height = if height < 1 { 1 } else { height };
-    let x = gap_left + bw + idx * (width + 2 * bw + gap);
-    let y = bar_height + gap_top + bw;
-    (x, y, width, height)
-}
 
-/// Tile a window in hsplit mode (horizontal splits — windows stacked vertically).
-///
-/// Each window gets an equal share of the vertical space.
-///
-/// Screen edges use per-side gaps; inter-window spacing uses `gap`.
-///   n = total windows in hsplit
-///   width  = screen_w - gap_left - gap_right - bw * 2
-///   height = (screen_h - bar_height - gap_top - gap_bottom - (n - 1) * gap) / n - 2 * bw
-///   x = gap_left + bw
-///   y = bar_height + gap_top + bw + idx * (height + 2 * bw + gap)
-pub fn tile_hsplit(
-    screen_w: i32,
-    screen_h: i32,
-    gap: i32,
-    gap_top: i32,
-    gap_left: i32,
-    gap_right: i32,
-    gap_bottom: i32,
-    bw: i32,
-    bar_height: i32,
-    n_hsplit: i32,
-    idx: i32,
-) -> (i32, i32, i32, i32) {
-    let n = if n_hsplit < 1 { 1 } else { n_hsplit };
-    let width = screen_w - gap_left - gap_right - bw * 2;
-    let height = (screen_h - bar_height - gap_top - gap_bottom - (n - 1) * gap) / n - 2 * bw;
-    let width = if width < 1 { 1 } else { width };
-    let height = if height < 1 { 1 } else { height };
-    let x = gap_left + bw;
-    let y = bar_height + gap_top + bw + idx * (height + 2 * bw + gap);
-    (x, y, width, height)
-}
 
 /// Tile a window in fullscreen mode — fills the screen minus gaps, bar, and borders.
 ///
@@ -336,123 +272,7 @@ mod tests {
         assert_eq!(color, "#3e3e3e");
     }
 
-    #[test]
-    fn test_tile_vsplit_single() {
-        // Single vsplit window fills screen minus gaps/borders/bar
-        let (x, y, w, h) = tile_vsplit(1920, 1080, 18, 18, 18, 18, 18, 18, 28, 1, 0);
-        assert_eq!(x, 36); // gap_left + bw
-        assert_eq!(y, 64); // bar_height + gap_top + bw
-                           // w = (1920 - 18 - 18 - 0*18) / 1 - 2*18 = 1884 - 36 = 1848
-        assert_eq!(w, 1848);
-        // h = 1080 - 18 - 18 - 18*2 - 28 = 1080 - 100 = 980
-        assert_eq!(h, 980);
-    }
-
-    #[test]
-    fn test_tile_vsplit_two() {
-        // Two vsplit windows side by side
-        let (x0, y0, w0, h0) = tile_vsplit(1920, 1080, 18, 18, 18, 18, 18, 18, 28, 2, 0);
-        let (x1, y1, w1, h1) = tile_vsplit(1920, 1080, 18, 18, 18, 18, 18, 18, 28, 2, 1);
-
-        // Same dimensions
-        assert_eq!(w0, w1);
-        assert_eq!(h0, h1);
-        // Same y (same row)
-        assert_eq!(y0, y1);
-        // Window 1 is to the right
-        assert!(x1 > x0);
-
-        // w = (1920 - 18 - 18 - 1*18) / 2 - 2*18 = (1920-54)/2 - 36 = 933 - 36 = 897
-        assert_eq!(w0, 897);
-    }
-
-    #[test]
-    fn test_tile_vsplit_minimum_size() {
-        let (_, _, w, h) = tile_vsplit(100, 100, 18, 18, 18, 18, 18, 18, 28, 10, 5);
-        assert!(w >= 1);
-        assert!(h >= 1);
-    }
-
-    #[test]
-    fn test_tile_vsplit_asymmetric_gaps() {
-        // Asymmetric screen gaps with 2 vsplit windows
-        let (x0, y0, w0, h0) = tile_vsplit(1920, 1080, 12, 10, 20, 30, 40, 6, 28, 2, 0);
-        let (x1, _y1, w1, _h1) = tile_vsplit(1920, 1080, 12, 10, 20, 30, 40, 6, 28, 2, 1);
-
-        // w = (1920 - 20 - 30 - 1*12) / 2 - 2*6 = (1920-62)/2 - 12 = 929 - 12 = 917
-        assert_eq!(w0, 917);
-        assert_eq!(w0, w1);
-
-        // x0 = gap_left + bw = 20 + 6 = 26
-        assert_eq!(x0, 26);
-        // y0 = bar_height + gap_top + bw = 28 + 10 + 6 = 44
-        assert_eq!(y0, 44);
-        // h = 1080 - 10 - 40 - 6*2 - 28 = 1080 - 90 = 990
-        assert_eq!(h0, 990);
-
-        // x1 = gap_left + bw + 1*(917 + 2*6 + 12) = 26 + 941 = 967
-        assert_eq!(x1, 967);
-    }
 
-    #[test]
-    fn test_tile_hsplit_single() {
-        // Single hsplit window fills screen minus gaps/borders/bar
-        let (x, y, w, h) = tile_hsplit(1920, 1080, 18, 18, 18, 18, 18, 18, 28, 1, 0);
-        assert_eq!(x, 36); // gap_left + bw
-        assert_eq!(y, 64); // bar_height + gap_top + bw
-                           // w = 1920 - 18 - 18 - 18*2 = 1920 - 72 = 1848
-        assert_eq!(w, 1848);
-        // h = (1080 - 28 - 18 - 18 - 0*18) / 1 - 2*18 = 1016 - 36 = 980
-        assert_eq!(h, 980);
-    }
-
-    #[test]
-    fn test_tile_hsplit_two() {
-        // Two hsplit windows stacked vertically
-        let (x0, y0, w0, h0) = tile_hsplit(1920, 1080, 18, 18, 18, 18, 18, 18, 28, 2, 0);
-        let (x1, y1, w1, h1) = tile_hsplit(1920, 1080, 18, 18, 18, 18, 18, 18, 28, 2, 1);
-
-        // Same dimensions
-        assert_eq!(w0, w1);
-        assert_eq!(h0, h1);
-        // Same x (same column)
-        assert_eq!(x0, x1);
-        // Window 1 is below window 0
-        assert!(y1 > y0);
-
-        // h = (1080 - 28 - 18 - 18 - 1*18) / 2 - 2*18 = (1080-82)/2 - 36 = 499 - 36 = 463
-        assert_eq!(h0, 463);
-    }
-
-    #[test]
-    fn test_tile_hsplit_minimum_size() {
-        let (_, _, w, h) = tile_hsplit(100, 100, 18, 18, 18, 18, 18, 18, 28, 10, 5);
-        assert!(w >= 1);
-        assert!(h >= 1);
-    }
-
-    #[test]
-    fn test_tile_hsplit_asymmetric_gaps() {
-        // Asymmetric screen gaps with 2 hsplit windows
-        let (x0, y0, w0, h0) = tile_hsplit(1920, 1080, 12, 10, 20, 30, 40, 6, 28, 2, 0);
-        let (_x1, y1, w1, h1) = tile_hsplit(1920, 1080, 12, 10, 20, 30, 40, 6, 28, 2, 1);
-
-        // w = 1920 - 20 - 30 - 6*2 = 1920 - 62 = 1858
-        assert_eq!(w0, 1858);
-        assert_eq!(w0, w1);
-
-        // x0 = gap_left + bw = 20 + 6 = 26
-        assert_eq!(x0, 26);
-        // y0 = bar_height + gap_top + bw = 28 + 10 + 6 = 44
-        assert_eq!(y0, 44);
-
-        // h = (1080 - 28 - 10 - 40 - 1*12) / 2 - 2*6 = (1080-90)/2 - 12 = 495 - 12 = 483
-        assert_eq!(h0, 483);
-        assert_eq!(h0, h1);
-
-        // y1 = bar_height + gap_top + bw + 1*(483 + 2*6 + 12) = 44 + 507 = 551
-        assert_eq!(y1, 551);
-    }
 
     #[test]
     fn test_tile_fullscreen_basic() {
diff --git a/src/types.rs b/src/types.rs
index 7abc041..587a606 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,4 +1,4 @@
-// Core data structures for clearwm
+// Core data structures for ccec
 
 use std::collections::HashMap;
 
@@ -10,8 +10,6 @@ pub enum TilingMode {
     Floating,
     Cascade,
     Grid,
-    Vsplit,
-    Hsplit,
     Fullscreen,
     Popup,
 }
@@ -22,8 +20,6 @@ impl TilingMode {
             TilingMode::Floating => "Floating",
             TilingMode::Cascade => "Cascade",
             TilingMode::Grid => "Grid",
-            TilingMode::Vsplit => "Vsplit",
-            TilingMode::Hsplit => "Hsplit",
             TilingMode::Fullscreen => "Fullscreen",
             TilingMode::Popup => "Popup",
         }
@@ -74,8 +70,6 @@ pub struct Layout {
     pub fullscreen_border_width: i32,
     pub cascade_border_width: i32,
     pub grid_border_width: i32,
-    pub vsplit_border_width: i32,
-    pub hsplit_border_width: i32,
     pub floating_border_width: i32,
     pub border_r: u32,
     pub border_g: u32,
@@ -86,6 +80,7 @@ pub struct Layout {
     pub background_b: u32,
     pub background_a: u32,
     pub border_font_size: i32,
+    pub transition_duration: i32,
 }
 
 impl Default for Layout {
@@ -102,8 +97,6 @@ impl Default for Layout {
             fullscreen_border_width: 0,
             cascade_border_width: 6,
             grid_border_width: 6,
-            vsplit_border_width: 6,
-            hsplit_border_width: 6,
             floating_border_width: 6,
             border_r: 0x3E3E3E3Eu32,
             border_g: 0x3E3E3E3Eu32,
@@ -114,6 +107,7 @@ impl Default for Layout {
             background_b: 0x0E0E0E0Eu32,
             background_a: 0xFFFFFFFFu32,
             border_font_size: 11,
+            transition_duration: 300,
         }
     }
 }
@@ -228,6 +222,11 @@ pub struct Window {
     pub needs_xprop_check: bool,
     /// How many ManageStart cycles we've waited for the xprop result file.
     pub xprop_check_attempts: u8,
+    pub anim_x: Option<f64>,
+    pub anim_y: Option<f64>,
+    pub anim_w: Option<f64>,
+    pub anim_h: Option<f64>,
+    pub anim_opacity: Option<f64>,
 }
 
 impl Default for Window {
@@ -260,6 +259,11 @@ impl Default for Window {
             mode_locked: false,
             needs_xprop_check: false,
             xprop_check_attempts: 0,
+            anim_x: None,
+            anim_y: None,
+            anim_w: None,
+            anim_h: None,
+            anim_opacity: None,
         }
     }
 }
@@ -322,7 +326,7 @@ pub struct WindowManager {
     /// on the next output_manager done event. Set by config load and
     /// by VT-switch-back (where wlroots resets scale to 1).
     pub pending_scale_apply: bool,
-    /// When true, apply persisted state from ~/.cache/clearwm_state on the
+    /// When true, apply persisted state from ~/.cache/ccec_state on the
     /// next ManageStart cycle (after windows have been re-advertised).
     /// Set to true on startup/restart, consumed after application.
     pub needs_state_restore: bool,
@@ -347,9 +351,11 @@ pub struct WindowManager {
     pub notifications_enable: bool,
     /// Reload commands to execute on configuration reload
     pub reload_commands: Vec<String>,
-    pub input_controller: Option<tokio::sync::mpsc::UnboundedSender<(crate::config::InertialConfig, bool)>>,
+    pub input_controller: Option<tokio::sync::mpsc::UnboundedSender<crate::input::InputDaemonMsg>>,
     pub trackpad_disabled: bool,
     pub expose_active: bool,
+    pub expose_visual_active: bool,
+    pub animating: bool,
 }
 
 impl Default for WindowManager {
@@ -398,6 +404,8 @@ impl Default for WindowManager {
             input_controller: None,
             trackpad_disabled: false,
             expose_active: false,
+            expose_visual_active: false,
+            animating: false,
         }
     }
 }
@@ -483,8 +491,6 @@ pub fn parse_tiling_mode(s: &str) -> TilingMode {
     match s {
         "cascade" => TilingMode::Cascade,
         "grid" => TilingMode::Grid,
-        "vsplit" => TilingMode::Vsplit,
-        "hsplit" => TilingMode::Hsplit,
         "fullscreen" => TilingMode::Fullscreen,
         "floating" => TilingMode::Floating,
         "popup" => TilingMode::Popup,
@@ -599,11 +605,13 @@ pub fn parse_modifiers(mod_str: &str) -> u32 {
 
 /// Parse a button string ("left", "right", "middle", or numeric)
 pub fn parse_button(s: &str) -> u32 {
-    match s {
+    match s.trim() {
         "left" => 0x110,   // BTN_LEFT
         "right" => 0x111,  // BTN_RIGHT
         "middle" => 0x112, // BTN_MIDDLE
-        _ => s.parse().unwrap_or(0),
+        "side" => 0x113,   // BTN_SIDE
+        "extra" => 0x114,  // BTN_EXTRA
+        _ => s.trim().parse().unwrap_or(0),
     }
 }
 
@@ -640,8 +648,6 @@ mod tests {
     fn test_parse_tiling_mode() {
         assert_eq!(parse_tiling_mode("cascade"), TilingMode::Cascade);
         assert_eq!(parse_tiling_mode("grid"), TilingMode::Grid);
-        assert_eq!(parse_tiling_mode("vsplit"), TilingMode::Vsplit);
-        assert_eq!(parse_tiling_mode("hsplit"), TilingMode::Hsplit);
         assert_eq!(parse_tiling_mode("fullscreen"), TilingMode::Fullscreen);
         assert_eq!(parse_tiling_mode("floating"), TilingMode::Floating);
         assert_eq!(parse_tiling_mode("popup"), TilingMode::Popup);
diff --git a/src/wayland.rs b/src/wayland.rs
index fa60f62..0018c61 100644
--- a/src/wayland.rs
+++ b/src/wayland.rs
@@ -1,4 +1,4 @@
-// Wayland display connection, registry, and event dispatch for clearwm
+// Wayland display connection, registry, and event dispatch for ccec
 
 use wayland_client::{
     event_created_child, protocol::wl_registry, Connection, Dispatch, EventQueue, Proxy,
@@ -93,6 +93,25 @@ pub struct PendingBorderDrag {
     pub op_type: PointerOpType,
 }
 
+fn update_screen_bounds(outputs: &[crate::types::Output]) {
+    let mut max_x = 1920;
+    let mut max_y = 1080;
+    for out in outputs {
+        if !out.removed {
+            let right = out.x + out.width;
+            let bottom = out.y + out.height;
+            if right > max_x {
+                max_x = right;
+            }
+            if bottom > max_y {
+                max_y = bottom;
+            }
+        }
+    }
+    crate::input::SCREEN_WIDTH.store(max_x, std::sync::atomic::Ordering::SeqCst);
+    crate::input::SCREEN_HEIGHT.store(max_y, std::sync::atomic::Ordering::SeqCst);
+}
+
 /// Wayland proxy objects stored alongside each Window, so we can
 /// call protocol methods (set_position, propose_dimensions, etc.) on it.
 pub struct WindowProxy {
@@ -367,19 +386,18 @@ impl AppState {
                             crate::types::TilingMode::Cascade => state.wm.layout.cascade_border_width,
                             crate::types::TilingMode::Fullscreen => state.wm.layout.fullscreen_border_width,
                             crate::types::TilingMode::Grid => state.wm.layout.grid_border_width,
-                            crate::types::TilingMode::Vsplit => state.wm.layout.vsplit_border_width,
-                            crate::types::TilingMode::Hsplit => state.wm.layout.hsplit_border_width,
                             crate::types::TilingMode::Floating => state.wm.layout.floating_border_width,
                             crate::types::TilingMode::Popup => 0,
                         }
                     };
                     let grab_w = border_w.max(10);
 
+                    let logical_height = border_w.max(16);
                     shape = match surface_type {
                         "top" => {
                             let total_width = w.width + 2 * border_w;
-                            let edge_thresh = (border_w as f64).max(8.0);
-                            if state.last_pointer_surface_y < edge_thresh {
+                            let mid_y = (logical_height as f64) / 2.0;
+                            if state.last_pointer_surface_y < mid_y {
                                 if state.last_pointer_surface_x < CORNER_THRESHOLD {
                                     Shape::NwseResize
                                 } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
@@ -388,41 +406,50 @@ impl AppState {
                                     Shape::NsResize
                                 }
                             } else {
-                                if state.last_pointer_surface_x < CORNER_THRESHOLD {
-                                    Shape::EwResize
-                                } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
-                                    Shape::EwResize
-                                } else {
-                                    Shape::Default
-                                }
+                                Shape::Default
                             }
                         }
                         "left" => {
-                            if state.last_pointer_surface_y < CORNER_THRESHOLD {
-                                Shape::NwseResize
-                            } else if state.last_pointer_surface_y > (w.height as f64 - CORNER_THRESHOLD) {
-                                Shape::NeswResize
+                            let mid_x = (grab_w as f64) / 2.0;
+                            if state.last_pointer_surface_x < mid_x {
+                                if state.last_pointer_surface_y < CORNER_THRESHOLD {
+                                    Shape::NwseResize
+                                } else if state.last_pointer_surface_y > (w.height as f64 - CORNER_THRESHOLD) {
+                                    Shape::NeswResize
+                                } else {
+                                    Shape::EwResize
+                                }
                             } else {
-                                Shape::EwResize
+                                Shape::Default
                             }
                         }
                         "right" => {
-                            if state.last_pointer_surface_y < CORNER_THRESHOLD {
-                                Shape::NeswResize
-                            } else if state.last_pointer_surface_y > (w.height as f64 - CORNER_THRESHOLD) {
-                                Shape::NwseResize
+                            let mid_x = (grab_w as f64) / 2.0;
+                            if state.last_pointer_surface_x >= mid_x {
+                                if state.last_pointer_surface_y < CORNER_THRESHOLD {
+                                    Shape::NeswResize
+                                } else if state.last_pointer_surface_y > (w.height as f64 - CORNER_THRESHOLD) {
+                                    Shape::NwseResize
+                                } else {
+                                    Shape::EwResize
+                                }
                             } else {
-                                Shape::EwResize
+                                Shape::Default
                             }
                         }
                         "bottom" => {
                             let total_width = w.width + 2 * grab_w;
-                            if state.last_pointer_surface_x < CORNER_THRESHOLD {
-                                Shape::NeswResize
-                            } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
-                                Shape::NwseResize
+                            let mid_y = (grab_w as f64) / 2.0;
+                            if state.last_pointer_surface_y >= mid_y {
+                                if state.last_pointer_surface_x < CORNER_THRESHOLD {
+                                    Shape::NeswResize
+                                } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
+                                    Shape::NwseResize
+                                } else {
+                                    Shape::NsResize
+                                }
                             } else {
-                                Shape::NsResize
+                                Shape::Default
                             }
                         }
                         _ => Shape::Default,
@@ -690,6 +717,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                     });
                 }
                 state.wm.outputs.retain(|o| !o.removed);
+                update_screen_bounds(&state.wm.outputs);
 
                 // Remove removed seats — destroy protocol proxies first
                 // (matching tinyrwm's remove_seats pattern which destroys
@@ -802,7 +830,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                     }
                 }
 
-                // Apply persisted state from ~/.cache/clearwm_state on first ManageStart.
+                // Apply persisted state from ~/.cache/ccec_state on first ManageStart.
                 // This restores window tag assignments, active_tags, tag_layouts, and
                 // locked tiling modes from the previous session. Must run before
                 // assign_window_modes so restored tags/modes take effect.
@@ -937,6 +965,13 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
 
                 if state.pointer_op_release_pending {
                     state.pointer_op_release_pending = false;
+                    if let Some(ref op) = state.active_pointer_op {
+                        if op.op_type != PointerOpType::Move {
+                            if let Some(wp) = state.get_window_proxy(op.window_id) {
+                                wp.river_window.inform_resize_end();
+                            }
+                        }
+                    }
                     state.active_pointer_op = None;
                     for (_sid, sp) in &state.seat_proxies {
                         sp.river_seat.op_end();
@@ -967,10 +1002,17 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                         }
                     })
                     .collect();
+                let has_pending = !pending.is_empty();
                 for (seat_id, action, command) in pending {
                     execute_action(state, seat_id, &action, command.as_deref());
                 }
 
+                if has_pending {
+                    if let Some(ref wm) = state.window_manager {
+                        wm.manage_dirty();
+                    }
+                }
+
                 wm_proxy.manage_finish();
                 // Flush immediately so River can start the configure/render cycle
                 // without waiting for our blocking_dispatch to complete.
@@ -1012,6 +1054,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                     // set_position and propose_dimensions are window management state
                     // and must happen during ManageStart.
                     crate::wm::render_borders(state);
+                    crate::wm::render_opacity(state);
 
                     // Update and render window title decorations on borders
                     crate::decorations::update_decorations(state, qhandle);
@@ -1103,7 +1146,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                 if state.wm.needs_status_update {
                     crate::status::write_status_files(&state.wm);
 
-                    // Persist state to ~/.cache/clearwm_state for restart recovery.
+                    // Persist state to ~/.cache/ccec_state for restart recovery.
                     // Safe: just file I/O, no fork, no blocking.
                     crate::state::write_state(&state.wm);
 
@@ -1182,6 +1225,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                 };
 
                 state.wm.outputs.push(output);
+                update_screen_bounds(&state.wm.outputs);
                 state.output_proxies.push((
                     id,
                     OutputProxy {
@@ -1274,6 +1318,16 @@ impl Dispatch<RiverWindowV1, ()> for AppState {
                         );
                         window.app_id = app_id;
                         re_eval = !window.mode_locked;
+
+                        // Check if this is a blank steam_proton helper window
+                        let is_proton = window.app_id.as_deref() == Some("steam_proton");
+                        let is_blank = window.title.is_none() || window.title.as_deref().map_or(true, |t| t.is_empty());
+                        if is_proton && is_blank {
+                            if window.tags != 0 {
+                                window.tags = 0;
+                                re_eval = true;
+                            }
+                        }
                     }
                 }
                 if re_eval {
@@ -1285,10 +1339,38 @@ impl Dispatch<RiverWindowV1, ()> for AppState {
             }
 
             river_window_v1::Event::Title { title } => {
+                let mut re_eval = false;
+                let mut needs_render = false;
+                let active_tags = state.wm.active_tags;
                 if let Some(window) = state.wm.get_window_mut(wid) {
                     if window.title != title {
                         window.title = title;
-                        state.wm.needs_render = true;
+                        needs_render = true;
+
+                        // Check if we need to update/restore tags based on title
+                        let is_proton = window.app_id.as_deref() == Some("steam_proton");
+                        let is_blank = window.title.is_none() || window.title.as_deref().map_or(true, |t| t.is_empty());
+                        if is_proton {
+                            if is_blank {
+                                if window.tags != 0 {
+                                    window.tags = 0;
+                                    re_eval = true;
+                                }
+                            } else {
+                                if window.tags == 0 {
+                                    window.tags = active_tags;
+                                    re_eval = true;
+                                }
+                            }
+                        }
+                    }
+                }
+                if needs_render {
+                    state.wm.needs_render = true;
+                }
+                if re_eval {
+                    if let Some(ref wm) = state.window_manager {
+                        wm.manage_dirty();
                     }
                 }
             }
@@ -1318,7 +1400,7 @@ impl Dispatch<RiverWindowV1, ()> for AppState {
                     // Spawn an async xprop check for XWayland parent detection.
                     // River doesn't forward WM_TRANSIENT_FOR for XWayland windows,
                     // so we check via xdotool + xprop as a fallback.
-                    // The script writes results to /tmp/clearwm-xprop-{wid} which
+                    // The script writes results to /tmp/ccec-xprop-{wid} which
                     // is read on the next ManageStart cycle.
                     if !window.has_parent && window.pid > 0 {
                         let pid = window.pid;
@@ -1512,7 +1594,10 @@ impl Dispatch<RiverSeatV1, ()> for AppState {
 
                         // Disable expose if it was active
                         if state.wm.expose_active {
-                            state.wm.expose_active = false;
+                            crate::wm::set_expose_active(&mut state.wm, false);
+                            if let Some(ref wm) = state.window_manager {
+                                wm.manage_dirty();
+                            }
                         }
 
                         state.wm.needs_render = true;
@@ -1655,6 +1740,7 @@ impl Dispatch<RiverOutputV1, ()> for AppState {
                                 output.x = info.x;
                                 output.y = info.y;
                                 state.wm.needs_render = true;
+                                update_screen_bounds(&state.wm.outputs);
                                 eprintln!(
                                     "river_output linked to wl_output name={} dimensions (copied): {}x{} at ({},{})",
                                     name, info.width, info.height, info.x, info.y
@@ -1705,6 +1791,7 @@ impl Dispatch<wl_output::WlOutput, ()> for AppState {
                         output.x = x;
                         output.y = y;
                         state.wm.needs_render = true;
+                        update_screen_bounds(&state.wm.outputs);
                     }
                 }
             }
@@ -1730,6 +1817,7 @@ impl Dispatch<wl_output::WlOutput, ()> for AppState {
                             output.width = width;
                             output.height = height;
                             state.wm.needs_render = true;
+                            update_screen_bounds(&state.wm.outputs);
                             eprintln!(
                                 "wl_output name={} updated current mode dimensions: {}x{}",
                                 name, width, height
@@ -1996,7 +2084,7 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
                 eprintln!("spawn: {}", cmd);
                 // Close inherited FDs > 2 in the child so that spawned
                 // Wayland clients (fuzzel, etc.) never accidentally read
-                // from clearwm's Wayland socket fd. This prevents protocol
+                // from ccec's Wayland socket fd. This prevents protocol
                 // corruption and the CPU spin loop that results from it.
                 use std::os::unix::process::CommandExt;
                 let _ = unsafe {
@@ -2136,7 +2224,17 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
                     if let Some((_, sp)) = state.seat_proxies.iter().find(|(sid, _)| *sid == seat_id) {
                         sp.river_seat.op_start_pointer();
                     }
-                    if let Some(win) = state.wm.get_window(wid) {
+                    if op_type != PointerOpType::Move {
+                        if let Some(wp) = state.get_window_proxy(wid) {
+                            wp.river_window.inform_resize_start();
+                        }
+                    }
+                    if let Some(win) = state.wm.get_window_mut(wid) {
+                        win.anim_x = None;
+                        win.anim_y = None;
+                        win.anim_w = None;
+                        win.anim_h = None;
+                        win.anim_opacity = None;
                         state.active_pointer_op = Some(PointerOp {
                             window_id: wid,
                             op_type,
@@ -2200,8 +2298,8 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
                             minimize_requested: false,
                             tiling_mode: TilingMode::Fullscreen,
                             mode_locked,
-                            needs_xprop_check: false,
                             xprop_check_attempts: 0,
+                            ..Default::default()
                         };
                         crate::wm::get_mode_for_window(&state.wm, &temp_win)
                             .unwrap_or(state.wm.global_layout)
@@ -2227,8 +2325,6 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
             let cycle = [
                 TilingMode::Cascade,
                 TilingMode::Grid,
-                TilingMode::Vsplit,
-                TilingMode::Hsplit,
                 TilingMode::Fullscreen,
             ];
             // Cycle the layout for the currently active tag(s) only.
@@ -2265,7 +2361,7 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
             );
 
             if state.wm.notifications_enable {
-                crate::config::show_notification("clearwm", &format!("Layout set to {} for active tags", next.as_str()));
+                crate::config::show_notification("ccec", &format!("Layout set to {} for active tags", next.as_str()));
             }
 
             // Unlock windows that got their mode from the layout (not from mode_rules
@@ -2281,8 +2377,6 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
             let cycle = [
                 TilingMode::Cascade,
                 TilingMode::Grid,
-                TilingMode::Vsplit,
-                TilingMode::Hsplit,
                 TilingMode::Fullscreen,
                 TilingMode::Floating,
             ];
@@ -2311,7 +2405,7 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
                     win.mode_locked = true;
                     if notifications_enable {
                         let win_title = win.title.as_deref().unwrap_or("Window");
-                        crate::config::show_notification("clearwm", &format!("Tiling mode set to {} for: {}", next.as_str(), win_title));
+                        crate::config::show_notification("ccec", &format!("Tiling mode set to {} for: {}", next.as_str(), win_title));
                     }
                     state.wm.needs_render = true;
                     state.wm.needs_status_update = true;
@@ -2325,7 +2419,8 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
             crate::restart::wm_restart();
         }
         Action::Expose => {
-            state.wm.expose_active = !state.wm.expose_active;
+            let active = !state.wm.expose_active;
+            crate::wm::set_expose_active(&mut state.wm, active);
             state.wm.needs_render = true;
             state.wm.needs_status_update = true;
         }
@@ -2928,7 +3023,7 @@ pub fn wayland_init() -> Result<(Connection, EventQueue<AppState>, AppState), St
         state.render_count
     );
 
-    eprintln!("clearwm: Wayland connection established");
+    eprintln!("ccec: Wayland connection established");
 
     Ok((conn, event_queue, state))
 }
@@ -3293,7 +3388,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
             wl_pointer::Event::Leave { .. } => {
                 eprintln!("[pointer] leave");
                 state.pointer_hovered_surface = None;
-                state.pending_border_drag = None;
                 Self::update_cursor_shape_for_surface(state, proxy);
             }
             wl_pointer::Event::Motion { surface_x, surface_y, .. } => {
@@ -3306,8 +3400,8 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                     let dx = surface_x - pending.start_surface_x;
                     let dy = surface_y - pending.start_surface_y;
                     let dist = (dx * dx + dy * dy).sqrt();
-                    // Threshold of 12.0 logical pixels to distinguish click vs drag
-                    if dist > 12.0 {
+                    // Threshold of 4.0 logical pixels to distinguish click vs drag
+                    if dist > 4.0 {
                         state.pending_border_drag = None;
 
                         // Start the drag! Set the window to Floating and lock it.
@@ -3390,64 +3484,48 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                                             }
                                         });
 
-                                        let mut focus_changed = false;
                                         if let Some(sid) = seat_id {
                                             if let Some(seat) = state.wm.seats.iter_mut().find(|s| s.id == sid) {
-                                                focus_changed = seat.focused_window_id != Some(wid);
                                                 seat.focused_window_id = Some(wid);
                                                 state.wm.move_window_to_end(wid);
                                                 if state.wm.expose_active {
-                                                    state.wm.expose_active = false;
+                                                    crate::wm::set_expose_active(&mut state.wm, false);
                                                 }
-                                                state.wm.needs_focus = true;
-                                                state.wm.needs_render = true;
-                                                state.wm.needs_status_update = true;
-                                                if let Some(ref wm) = state.window_manager {
-                                                    wm.manage_dirty();
                                                 }
-                                            }
-
-                                            if !focus_changed {
-                                                // Determine the specific PointerOpType based on coordinates on the matched surface
-                                                let op_type = if let Some(w) = state.wm.windows.iter().find(|win| win.id == wid) {
-                                                    let border_w = if state.wm.expose_active && w.tiling_mode != crate::types::TilingMode::Popup {
-                                                        state.wm.layout.grid_border_width
-                                                    } else {
-                                                        match w.tiling_mode {
-                                                            crate::types::TilingMode::Cascade => state.wm.layout.cascade_border_width,
-                                                            crate::types::TilingMode::Fullscreen => state.wm.layout.fullscreen_border_width,
-                                                            crate::types::TilingMode::Grid => state.wm.layout.grid_border_width,
-                                                            crate::types::TilingMode::Vsplit => state.wm.layout.vsplit_border_width,
-                                                            crate::types::TilingMode::Hsplit => state.wm.layout.hsplit_border_width,
-                                                            crate::types::TilingMode::Floating => state.wm.layout.floating_border_width,
-                                                            crate::types::TilingMode::Popup => 0,
-                                                        }
-                                                    };
-                                                    let grab_w = border_w.max(10);
-
-                                                    match surface_type {
-                                                        "top" => {
-                                                            let total_width = w.width + 2 * border_w;
-                                                            let edge_thresh = (border_w as f64).max(8.0);
-                                                            if state.last_pointer_surface_y < edge_thresh {
-                                                                if state.last_pointer_surface_x < CORNER_THRESHOLD {
-                                                                    PointerOpType::ResizeTopLeft
-                                                                } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
-                                                                    PointerOpType::ResizeTopRight
-                                                                } else {
-                                                                    PointerOpType::ResizeTop
-                                                                }
+                                                                 // Determine the specific PointerOpType based on coordinates on the matched surface
+                                            let op_type = if let Some(w) = state.wm.windows.iter().find(|win| win.id == wid) {
+                                                let border_w = if state.wm.expose_active && w.tiling_mode != crate::types::TilingMode::Popup {
+                                                    state.wm.layout.grid_border_width
+                                                } else {
+                                                    match w.tiling_mode {
+                                                        crate::types::TilingMode::Cascade => state.wm.layout.cascade_border_width,
+                                                        crate::types::TilingMode::Fullscreen => state.wm.layout.fullscreen_border_width,
+                                                        crate::types::TilingMode::Grid => state.wm.layout.grid_border_width,
+                                                        crate::types::TilingMode::Floating => state.wm.layout.floating_border_width,
+                                                        crate::types::TilingMode::Popup => 0,
+                                                    }
+                                                };
+                                                let grab_w = border_w.max(10);
+                                                let logical_height = border_w.max(16);
+                                                match surface_type {
+                                                    "top" => {
+                                                        let total_width = w.width + 2 * border_w;
+                                                        let mid_y = (logical_height as f64) / 2.0;
+                                                        if state.last_pointer_surface_y < mid_y {
+                                                            if state.last_pointer_surface_x < CORNER_THRESHOLD {
+                                                                PointerOpType::ResizeTopLeft
+                                                            } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
+                                                                PointerOpType::ResizeTopRight
                                                             } else {
-                                                                if state.last_pointer_surface_x < CORNER_THRESHOLD {
-                                                                    PointerOpType::ResizeLeft
-                                                                } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
-                                                                    PointerOpType::ResizeRight
-                                                                } else {
-                                                                    PointerOpType::Move
-                                                                }
+                                                                PointerOpType::ResizeTop
                                                             }
+                                                        } else {
+                                                            PointerOpType::Move
                                                         }
-                                                        "left" => {
+                                                    }
+                                                    "left" => {
+                                                        let mid_x = (grab_w as f64) / 2.0;
+                                                        if state.last_pointer_surface_x < mid_x {
                                                             if state.last_pointer_surface_y < CORNER_THRESHOLD {
                                                                 PointerOpType::ResizeTopLeft
                                                             } else if state.last_pointer_surface_y > (w.height as f64 - CORNER_THRESHOLD) {
@@ -3455,8 +3533,13 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                                                             } else {
                                                                 PointerOpType::ResizeLeft
                                                             }
+                                                        } else {
+                                                            PointerOpType::Move
                                                         }
-                                                        "right" => {
+                                                    }
+                                                    "right" => {
+                                                        let mid_x = (grab_w as f64) / 2.0;
+                                                        if state.last_pointer_surface_x >= mid_x {
                                                             if state.last_pointer_surface_y < CORNER_THRESHOLD {
                                                                 PointerOpType::ResizeTopRight
                                                             } else if state.last_pointer_surface_y > (w.height as f64 - CORNER_THRESHOLD) {
@@ -3464,9 +3547,14 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                                                             } else {
                                                                 PointerOpType::ResizeRight
                                                             }
+                                                        } else {
+                                                            PointerOpType::Move
                                                         }
-                                                        "bottom" => {
-                                                            let total_width = w.width + 2 * grab_w;
+                                                    }
+                                                    "bottom" => {
+                                                        let total_width = w.width + 2 * grab_w;
+                                                        let mid_y = (grab_w as f64) / 2.0;
+                                                        if state.last_pointer_surface_y >= mid_y {
                                                             if state.last_pointer_surface_x < CORNER_THRESHOLD {
                                                                 PointerOpType::ResizeBottomLeft
                                                             } else if state.last_pointer_surface_x > (total_width as f64 - CORNER_THRESHOLD) {
@@ -3474,22 +3562,24 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                                                             } else {
                                                                 PointerOpType::ResizeBottom
                                                             }
+                                                        } else {
+                                                            PointerOpType::Move
                                                         }
-                                                        _ => PointerOpType::Move,
                                                     }
-                                                } else {
-                                                    PointerOpType::Move
-                                                };
-
-                                                // 2. Store the pending drag info (threshold check is in Event::Motion)
-                                                state.pending_border_drag = Some(PendingBorderDrag {
-                                                    window_id: wid,
-                                                    seat_id: sid,
-                                                    start_surface_x: state.last_pointer_surface_x,
-                                                    start_surface_y: state.last_pointer_surface_y,
-                                                    op_type,
-                                                });
-                                            }
+                                                    _ => PointerOpType::Move,
+                                                }
+                                            } else {
+                                                PointerOpType::Move
+                                            };
+
+                                            // 2. Store the pending drag info (threshold check is in Event::Motion)
+                                            state.pending_border_drag = Some(PendingBorderDrag {
+                                                window_id: wid,
+                                                seat_id: sid,
+                                                start_surface_x: state.last_pointer_surface_x,
+                                                start_surface_y: state.last_pointer_surface_y,
+                                                op_type,
+                                            });
                                         }
                                     }
                                 }
diff --git a/src/wm.rs b/src/wm.rs
index 917dabd..85fa139 100644
--- a/src/wm.rs
+++ b/src/wm.rs
@@ -78,10 +78,20 @@ pub fn get_mode_for_window(wm: &WindowManager, win: &Window) -> Option<TilingMod
 /// Should be called during ManageStart before compute_tiling.
 pub fn assign_window_modes(wm: &mut WindowManager) {
     // Enforce that clear-status-interface is assigned all tags so that it is always visible
+    // and that blank steam_proton helper windows are always untagged so they remain hidden.
     for win in &mut wm.windows {
         if win.app_id.as_deref() == Some("clear-status-interface") {
             win.tags = u32::MAX;
         }
+
+        let is_proton = win.app_id.as_deref() == Some("steam_proton");
+        let is_blank = win.title.is_none() || win.title.as_deref().map_or(true, |t| t.is_empty());
+        if is_proton && is_blank {
+            if win.tags != 0 {
+                eprintln!("[mode] enforcing tags=0 for blank steam_proton helper window id={}", win.id);
+                win.tags = 0;
+            }
+        }
     }
 
     // Collect assignments first (borrow checker: can't borrow wm mutably while iterating mode_rules)
@@ -256,13 +266,13 @@ fn compute_tiling(
                 let row = idx / cols;
                 let col = idx % cols;
 
-                let width = (screen_w - gap_left - gap_right - (cols - 1) * gap) / cols - 2 * bw;
-                let height = (screen_h - bar_height - gap_top - gap_bottom - (rows - 1) * gap) / rows - 2 * bw;
+                let width = (screen_w - gap - gap - (cols - 1) * gap) / cols - 2 * bw;
+                let height = (screen_h - bar_height - gap - gap - (rows - 1) * gap) / rows - 2 * bw;
                 let width = if width < 1 { 1 } else { width };
                 let height = if height < 1 { 1 } else { height };
 
-                let x = gap_left + bw + col * (width + 2 * bw + gap);
-                let y = bar_height + gap_top + bw + row * (height + 2 * bw + gap);
+                let x = gap + bw + col * (width + 2 * bw + gap);
+                let y = bar_height + gap + bw + row * (height + 2 * bw + gap);
 
                 results.push(TileResult {
                     wid: win.id,
@@ -330,8 +340,6 @@ fn compute_tiling(
     // Count windows per tiling mode
     let mut n_cascade = 0i32;
     let mut n_grid = 0i32;
-    let mut n_vsplit = 0i32;
-    let mut n_hsplit = 0i32;
     for win in &wm.windows {
         if (win.tags & wm.active_tags) == 0 || win.closed {
             continue;
@@ -339,8 +347,6 @@ fn compute_tiling(
         match win.tiling_mode {
             TilingMode::Cascade => n_cascade += 1,
             TilingMode::Grid => n_grid += 1,
-            TilingMode::Vsplit => n_vsplit += 1,
-            TilingMode::Hsplit => n_hsplit += 1,
             _ => {}
         }
     }
@@ -368,8 +374,6 @@ fn compute_tiling(
     let mut results = Vec::new();
     let mut idx_cascade = 0i32;
     let mut idx_grid = 0i32;
-    let mut idx_vsplit = 0i32;
-    let mut idx_hsplit = 0i32;
     let mut idx_floating = 0i32;
 
     for win in &wm.windows {
@@ -427,20 +431,7 @@ fn compute_tiling(
                 idx_grid += 1;
                 (x, y, w, h)
             }
-            TilingMode::Vsplit => {
-                let (x, y, w, h) = tiling::tile_vsplit(
-                    screen_w, screen_h, gap, gap_top, gap_left, gap_right, gap_bottom, wm.layout.vsplit_border_width, bar_height, n_vsplit, idx_vsplit,
-                );
-                idx_vsplit += 1;
-                (x, y, w, h)
-            }
-            TilingMode::Hsplit => {
-                let (x, y, w, h) = tiling::tile_hsplit(
-                    screen_w, screen_h, gap, gap_top, gap_left, gap_right, gap_bottom, wm.layout.hsplit_border_width, bar_height, n_hsplit, idx_hsplit,
-                );
-                idx_hsplit += 1;
-                (x, y, w, h)
-            }
+
             TilingMode::Floating => {
                 // Floating windows: don't tile them, but still propose
                 // dimensions so they don't end up at w=0 h=0 (which
@@ -504,7 +495,139 @@ fn compute_tiling(
 
 /// Apply computed tiling results: set position and propose dimensions.
 fn apply_tiling(state: &mut AppState, results: &[TileResult]) {
+    let mut any_animating = false;
+
+    let focused_id = state.wm.seats.iter()
+        .find(|s| !s.removed)
+        .and_then(|s| s.focused_window_id);
+
+    let transition_duration = state.wm.layout.transition_duration;
+    let easing = if transition_duration <= 16 {
+        1.0
+    } else {
+        1.0 - 0.01f64.powf(16.0 / transition_duration as f64)
+    };
+
+    let expose_active = state.wm.expose_active;
+    let (screen_w, screen_h, _, _, _, _) = get_screen_geometry(&state.wm);
+    let fbw = state.wm.layout.floating_border_width;
+    let gap_left = state.wm.layout.gap_left;
+    let gap_top = state.wm.layout.gap_top;
+    let bar_height = state.wm.layout.bar_height;
+
     for tr in results {
+        let mut final_x = tr.x;
+        let mut final_y = tr.y;
+        let mut final_w = tr.w;
+        let mut final_h = tr.h;
+
+        if let Some(win) = state.wm.get_window_mut(tr.wid) {
+            let is_cascade = win.tiling_mode == TilingMode::Cascade;
+            let is_exposed = expose_active && win.tiling_mode != TilingMode::Popup && win.app_id.as_deref() != Some("clear-status-interface");
+            let was_animating = win.anim_x.is_some() || win.anim_y.is_some() || win.anim_w.is_some() || win.anim_h.is_some() || win.anim_opacity.is_some();
+            let should_animate = is_cascade || is_exposed || was_animating;
+
+            if should_animate {
+                let curr_x = win.anim_x.unwrap_or(win.x as f64);
+                let curr_y = win.anim_y.unwrap_or(win.y as f64);
+                let curr_w = win.anim_w.unwrap_or(win.width as f64);
+                let curr_h = win.anim_h.unwrap_or(win.height as f64);
+                let curr_opacity = win.anim_opacity.unwrap_or(1.0);
+
+                let target_opacity = if is_exposed {
+                    if Some(win.id) == focused_id { 1.0 } else { 0.75 }
+                } else if is_cascade {
+                    if Some(win.id) == focused_id { 1.0 } else { 0.75 }
+                } else {
+                    1.0
+                };
+
+                if curr_w == 0.0 || win.is_new {
+                    // New window: snap instantly
+                    win.anim_x = Some(tr.x as f64);
+                    win.anim_y = Some(tr.y as f64);
+                    win.anim_w = Some(tr.w as f64);
+                    win.anim_h = Some(tr.h as f64);
+                    win.anim_opacity = Some(target_opacity);
+                } else {
+                    let target_x = tr.x as f64;
+                    let target_y = tr.y as f64;
+                    let target_w = tr.w as f64;
+                    let target_h = tr.h as f64;
+
+                    let dx = target_x - curr_x;
+                    let dy = target_y - curr_y;
+                    let dw = target_w - curr_w;
+                    let dh = target_h - curr_h;
+                    let d_opacity = target_opacity - curr_opacity;
+
+                    if dx.abs() > 0.5 || dy.abs() > 0.5 || dw.abs() > 0.5 || dh.abs() > 0.5 || d_opacity.abs() > 0.01 {
+                        let next_x = curr_x + dx * easing;
+                        let next_y = curr_y + dy * easing;
+                        let next_w = curr_w + dw * easing;
+                        let next_h = curr_h + dh * easing;
+                        let next_opacity = curr_opacity + d_opacity * easing;
+
+                        win.anim_x = Some(next_x);
+                        win.anim_y = Some(next_y);
+                        win.anim_w = Some(next_w);
+                        win.anim_h = Some(next_h);
+                        win.anim_opacity = Some(next_opacity);
+
+                        final_x = next_x.round() as i32;
+                        final_y = next_y.round() as i32;
+                        // Propose the target size instantly during transitions to avoid configure storms and flickering
+                        final_w = tr.w;
+                        final_h = tr.h;
+
+                        any_animating = true;
+                    } else {
+                        if is_cascade || is_exposed {
+                            win.anim_x = Some(target_x);
+                            win.anim_y = Some(target_y);
+                            win.anim_w = Some(target_w);
+                            win.anim_h = Some(target_h);
+                            win.anim_opacity = Some(target_opacity);
+                        } else {
+                            win.anim_x = None;
+                            win.anim_y = None;
+                            win.anim_w = None;
+                            win.anim_h = None;
+                            win.anim_opacity = None;
+                        }
+                    }
+                }
+            } else {
+                win.anim_x = None;
+                win.anim_y = None;
+                win.anim_w = None;
+                win.anim_h = None;
+                win.anim_opacity = None;
+            }
+
+            // Update internal state
+            let is_floating_and_expose = expose_active && win.tiling_mode == TilingMode::Floating;
+            if !is_floating_and_expose {
+                win.x = final_x;
+                win.y = final_y;
+                win.width = final_w;
+                win.height = final_h;
+            } else {
+                // If it's a new floating window created during expose mode,
+                // initialize its position to the default floating position if unset.
+                if win.x == 0 && win.y == 0 {
+                    let fw = if win.hint_min_width > 32 { win.hint_min_width } else { screen_w * 2 / 3 };
+                    let fh = if win.hint_min_height > 32 { win.hint_min_height } else { screen_h * 2 / 3 };
+                    let fx = gap_left + fbw;
+                    let fy = gap_left + fbw + bar_height + gap_top;
+                    win.x = fx;
+                    win.y = fy;
+                    win.width = fw;
+                    win.height = fh;
+                }
+            }
+        }
+
         // Set position via river_node_v1
         if let Some(node) = state
             .window_nodes
@@ -512,12 +635,12 @@ fn apply_tiling(state: &mut AppState, results: &[TileResult]) {
             .find(|(id, _)| *id == tr.wid)
             .map(|(_, n)| n)
         {
-            node.set_position(tr.x, tr.y);
+            node.set_position(final_x, final_y);
         }
 
         // Propose dimensions via river_window_v1
         if let Some(wp) = state.get_window_proxy(tr.wid) {
-            wp.river_window.propose_dimensions(tr.w, tr.h);
+            wp.river_window.propose_dimensions(final_w, final_h);
             // Tell the client to use server-side decoration.
             // Per the River protocol, use_csd is the default when neither
             // use_csd nor use_ssd is called. Calling use_ssd here ensures
@@ -526,15 +649,52 @@ fn apply_tiling(state: &mut AppState, results: &[TileResult]) {
             // (decoration_hint == only_supports_csd).
             wp.river_window.use_ssd();
         }
+    }
 
-        // Update internal state
-        if let Some(win) = state.wm.get_window_mut(tr.wid) {
-            win.x = tr.x;
-            win.y = tr.y;
-            win.width = tr.w;
-            win.height = tr.h;
+    state.wm.animating = any_animating;
+    if any_animating {
+        state.wm.needs_render = true;
+    }
+    state.wm.expose_visual_active = state.wm.expose_active;
+}
+
+/// Set the expose_active state and initialize transition animation states to prevent flickering and enable smooth animations.
+pub fn set_expose_active(wm: &mut WindowManager, active: bool) {
+    if wm.expose_active == active {
+        return;
+    }
+    let prior_expose_visual_active = wm.expose_visual_active;
+
+    let focused_id = wm.seats.iter()
+        .find(|s| !s.removed)
+        .and_then(|s| s.focused_window_id);
+
+    for win in &mut wm.windows {
+        if win.closed || win.app_id.as_deref() == Some("clear-status-interface") || win.tiling_mode == TilingMode::Popup {
+            continue;
+        }
+        let is_focused = Some(win.id) == focused_id;
+
+        // Ensure current geometry animation states are initialized
+        if win.anim_x.is_none() { win.anim_x = Some(win.x as f64); }
+        if win.anim_y.is_none() { win.anim_y = Some(win.y as f64); }
+        if win.anim_w.is_none() { win.anim_w = Some(win.width as f64); }
+        if win.anim_h.is_none() { win.anim_h = Some(win.height as f64); }
+
+        // Ensure opacity animation state is initialized to its current visual value
+        if win.anim_opacity.is_none() {
+            let current_opacity = if win.tiling_mode == TilingMode::Cascade && !is_focused {
+                0.75
+            } else if prior_expose_visual_active && !is_focused {
+                0.75
+            } else {
+                1.0
+            };
+            win.anim_opacity = Some(current_opacity);
         }
     }
+
+    wm.expose_active = active;
 }
 
 /// Set border colors on all visible windows.
@@ -554,3 +714,42 @@ fn set_borders(state: &mut AppState) {
         }
     }
 }
+
+/// Set opacity on all visible windows.
+/// This modifies rendering state and is called during RenderStart.
+/// Opacity is applied with the next render_finish.
+pub fn render_opacity(state: &mut AppState) {
+    let focused_id = state.wm.seats.iter()
+        .find(|s| !s.removed)
+        .and_then(|s| s.focused_window_id);
+
+    for win in &state.wm.windows {
+        if win.closed {
+            continue;
+        }
+        if let Some(wp) = state.get_window_proxy(win.id) {
+            let is_cascade = win.tiling_mode == TilingMode::Cascade;
+            let is_exposed = state.wm.expose_visual_active && win.tiling_mode != TilingMode::Popup && win.app_id.as_deref() != Some("clear-status-interface");
+            let was_animating = win.anim_opacity.is_some();
+            let should_fade = is_cascade || is_exposed || was_animating;
+
+            let opacity = if should_fade {
+                win.anim_opacity.unwrap_or_else(|| {
+                    if is_exposed || is_cascade {
+                        if Some(win.id) == focused_id {
+                            1.0
+                        } else {
+                            0.75
+                        }
+                    } else {
+                        1.0
+                    }
+                })
+            } else {
+                1.0
+            };
+            let op_u32 = (opacity * u32::MAX as f64).round() as u32;
+            wp.river_window.set_opacity(op_u32);
+        }
+    }
+}
diff --git a/start-river.sh b/start-river.sh
index 81e451e..ce87e56 100755
--- a/start-river.sh
+++ b/start-river.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# Launch river with clearwm on this TTY
+# Launch river with ccec on this TTY
 # Usage: Switch to a free TTY, log in, and run this script
 
 LOGGING=false
@@ -15,26 +15,26 @@ export XCURSOR_THEME="crosshair-theme"
 export XCURSOR_SIZE=24
 export XCURSOR_PATH="/home/lsgalante/.local/share/icons:/home/lsgalante/.icons:/usr/share/icons"
 
-# Create the River init executable (clearwm launch script)
+# Create the River init executable (ccec launch script)
 # This must exist before River starts, and /tmp is cleared on reboot.
 if [ "$LOGGING" = true ]; then
-    cat > /tmp/clearwm-rs-launch-river.sh << 'LAUNCH_EOF'
+    cat > /tmp/ccec-launch-river.sh << 'LAUNCH_EOF'
 #!/bin/sh
-exec /home/lsgalante/.local/bin/clearwm 2>/tmp/clearwm-${WAYLAND_DISPLAY}.log
+exec /home/lsgalante/.local/bin/ccec 2>/tmp/ccec-${WAYLAND_DISPLAY}.log
 LAUNCH_EOF
 else
-    cat > /tmp/clearwm-rs-launch-river.sh << 'LAUNCH_EOF'
+    cat > /tmp/ccec-launch-river.sh << 'LAUNCH_EOF'
 #!/bin/sh
-exec /home/lsgalante/.local/bin/clearwm
+exec /home/lsgalante/.local/bin/ccec
 LAUNCH_EOF
 fi
-chmod +x /tmp/clearwm-rs-launch-river.sh
+chmod +x /tmp/ccec-launch-river.sh
 
-echo "Starting river with clearwm..."
+echo "Starting river with ccec..."
 if [ "$LOGGING" = true ]; then
-    echo "Logs: /tmp/river-clearwm.log + /tmp/clearwm-${WAYLAND_DISPLAY}.log"
+    echo "Logs: /tmp/river-ccec.log + /tmp/ccec-${WAYLAND_DISPLAY}.log"
 else
     echo "Logging disabled. Use --logging to enable."
 fi
 
-exec river -c /tmp/clearwm-rs-launch-river.sh 2>/tmp/river-clearwm.log
+exec river -c /tmp/ccec-launch-river.sh 2>/tmp/river-ccec.log