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

commit993f18bb9e096b3555180189ecd8005f2dfbecc5
parentcdfe80232a
authorLucas Galante <[email protected]>
date2026-06-01 06:55
Implement clear-inspector-v1 protocol and add inspector capability to server

 Cargo.lock                              |   2 +-
 Cargo.toml                              |   6 +-
 build.rs                                |   1 +
 protocol/clear-inspector-v1.xml         |  52 +++++++++
 protocol/river-window-management-v1.xml |   9 ++
 src/cursor.rs                           |   5 +
 src/input_manager.rs                    |  26 +++++
 src/inspector.rs                        | 194 ++++++++++++++++++++++++++++++++
 src/layer_shell.rs                      |  72 ++++++++++++
 src/main.rs                             |   1 +
 src/pointer_binding.rs                  |  12 +-
 src/seat.rs                             |  80 +++++++------
 src/server.rs                           |  34 +++++-
 src/window.rs                           |  35 +++++-
 src/window_manager.rs                   |  12 +-
 src/wlroots_log_wrapper.c               |  10 ++
 src/xkb_bindings.rs                     |  22 ++--
 src/xwayland_window.rs                  |  74 +++++++++---
 start-rust.sh                           |  18 +--
 wrapper.h                               |   3 +
 20 files changed, 582 insertions(+), 86 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index bad00a7..44c8104 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -173,7 +173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
 
 [[package]]
-name = "clear-river"
+name = "clear-computing-environment-server"
 version = "0.1.0"
 dependencies = [
  "bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index 7c547e2..65d220a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "clear-river"
+name = "clear-computing-environment-server"
 version = "0.1.0"
 edition = "2021"
 
@@ -13,6 +13,8 @@ env_logger = "0.11"
 
 [build-dependencies]
 bindgen = "0.69"
-cc = "1.0"
 pkg-config = "0.3"
+cc = "1.0"
 
+[profile.release]
+debug = true
diff --git a/build.rs b/build.rs
index da159a9..81003c0 100644
--- a/build.rs
+++ b/build.rs
@@ -82,6 +82,7 @@ fn main() {
         ("river-input-management-v1.xml", "protocol/river-input-management-v1.xml"),
         ("river-libinput-config-v1.xml", "protocol/river-libinput-config-v1.xml"),
         ("river-xkb-config-v1.xml", "protocol/river-xkb-config-v1.xml"),
+        ("clear-inspector-v1.xml", "protocol/clear-inspector-v1.xml"),
     ];
 
     let mut generated_c_files = Vec::new();
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/cursor.rs b/src/cursor.rs
index d0db355..ab25112 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -478,6 +478,11 @@ impl Cursor {
 
     pub unsafe fn clear_focus(&mut self) {
         ffi::wlr_seat_pointer_notify_clear_focus((*self.seat).wlr_seat);
+        ffi::wlr_cursor_set_xcursor(
+            self.wlr_cursor,
+            self.xcursor_manager,
+            b"default\0".as_ptr() as *const _,
+        );
     }
 }
 
diff --git a/src/input_manager.rs b/src/input_manager.rs
index 6209664..fff4453 100644
--- a/src/input_manager.rs
+++ b/src/input_manager.rs
@@ -26,6 +26,7 @@ pub struct InputManager {
     pub new_input_listener: ffi::wl_listener,
     pub new_text_input: ffi::wl_listener,
     pub new_input_method: ffi::wl_listener,
+    pub new_virtual_pointer_listener: ffi::wl_listener,
 }
 
 pub struct InputManagerObject {
@@ -62,6 +63,10 @@ impl InputManager {
         // Create default seat
         self.default_seat = Seat::create(server, "default")?;
 
+        if !(*server).xwayland.is_null() {
+            ffi::wlr_xwayland_set_seat((*server).xwayland, (*self.default_seat).wlr_seat);
+        }
+
         self.global = ffi::wl_global_create(
             wl_server,
             &ffi::river_input_manager_v1_interface,
@@ -89,6 +94,11 @@ impl InputManager {
         (*new_input_method_ptr).notify = Some(handle_new_input_method);
         wl_signal_add(&mut (*self.input_method_manager).events.input_method, &mut self.new_input_method);
 
+        // Connect new_virtual_pointer listener
+        let new_virtual_pointer_ptr = &mut self.new_virtual_pointer_listener as *mut ffi::wl_listener as *mut WlListener;
+        (*new_virtual_pointer_ptr).notify = Some(handle_new_virtual_pointer);
+        wl_signal_add(&mut (*self.virtual_pointer_manager).events.new_virtual_pointer, &mut self.new_virtual_pointer_listener);
+
         Ok(())
     }
 
@@ -114,6 +124,7 @@ impl InputManager {
         wl_listener_remove(&mut self.new_input_listener);
         wl_listener_remove(&mut self.new_text_input);
         wl_listener_remove(&mut self.new_input_method);
+        wl_listener_remove(&mut self.new_virtual_pointer_listener);
     }
 }
 
@@ -330,3 +341,18 @@ unsafe extern "C" fn handle_new_input_method(listener: *mut ffi::wl_listener, da
         (*im.default_seat).relay.new_input_method(input_method);
     }
 }
+
+unsafe extern "C" fn handle_new_virtual_pointer(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+    let im = &mut *crate::container_of!(listener, InputManager, new_virtual_pointer_listener);
+    let event = data as *mut ffi::wlr_virtual_pointer_v1_new_pointer_event;
+
+    log::info!("new virtual pointer device connected");
+
+    let virtual_pointer = (*event).new_pointer;
+    let wlr_device = &mut (*virtual_pointer).pointer.base as *mut ffi::wlr_input_device;
+
+    let device = crate::input_device::InputDevice::new(im.default_seat, wlr_device, true);
+
+    // Attach device to the default seat
+    (*im.default_seat).attach_device(device);
+}
diff --git a/src/inspector.rs b/src/inspector.rs
new file mode 100644
index 0000000..76a7963
--- /dev/null
+++ b/src/inspector.rs
@@ -0,0 +1,194 @@
+use crate::ffi;
+use crate::server::Server;
+use std::collections::HashMap;
+
+pub struct Inspector {
+    pub server: *mut Server,
+    pub global: *mut ffi::wl_global,
+    pub client_states: HashMap<*mut ffi::wlr_surface, String>,
+    pub resources: Vec<*mut ffi::wl_resource>,
+}
+
+impl Inspector {
+    pub fn new() -> Self {
+        Self {
+            server: std::ptr::null_mut(),
+            global: std::ptr::null_mut(),
+            client_states: HashMap::new(),
+            resources: Vec::new(),
+        }
+    }
+
+    pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+        self.server = server;
+        self.client_states = HashMap::new();
+        self.resources = Vec::new();
+
+        self.global = ffi::wl_global_create(
+            (*server).wl_server,
+            &ffi::zclear_inspector_v1_interface,
+            1,
+            self as *mut Inspector as *mut _,
+            Some(bind),
+        );
+
+        if self.global.is_null() {
+            return Err("Failed to create zclear_inspector_v1 global");
+        }
+
+        log::info!("zclear_inspector_v1 protocol global initialized successfully");
+        Ok(())
+    }
+
+    pub unsafe fn deinit(&mut self) {
+        if !self.global.is_null() {
+            ffi::wl_global_destroy(self.global);
+            self.global = std::ptr::null_mut();
+        }
+    }
+}
+
+unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
+    let inspector = ffi::wl_resource_get_user_data(resource) as *mut Inspector;
+    if !inspector.is_null() {
+        (*inspector).resources.retain(|&r| r != resource);
+    }
+}
+
+unsafe extern "C" fn bind(
+    client: *mut ffi::wl_client,
+    data: *mut std::ffi::c_void,
+    version: u32,
+    id: u32,
+) {
+    let inspector = data as *mut Inspector;
+    if inspector.is_null() {
+        return;
+    }
+
+    let resource = ffi::wl_resource_create(client, &ffi::zclear_inspector_v1_interface, version as i32, id);
+    if resource.is_null() {
+        ffi::wl_client_post_no_memory(client);
+        return;
+    }
+
+    ffi::wl_resource_set_implementation(
+        resource,
+        &INSPECTOR_INTERFACE as *const _ as *const _,
+        inspector as *mut _,
+        Some(handle_destroy_resource),
+    );
+
+    (*inspector).resources.push(resource);
+}
+
+unsafe extern "C" fn inspector_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+    ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn inspector_register_client(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+    surface_resource: *mut ffi::wl_resource,
+) {
+    let inspector = ffi::wl_resource_get_user_data(resource) as *mut Inspector;
+    if inspector.is_null() {
+        return;
+    }
+    let surface = ffi::wlr_surface_from_resource(surface_resource);
+    if surface.is_null() {
+        return;
+    }
+    (*inspector).client_states.entry(surface).or_insert_with(String::new);
+}
+
+unsafe extern "C" fn inspector_update_state(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+    surface_resource: *mut ffi::wl_resource,
+    state: *const std::os::raw::c_char,
+) {
+    let inspector = ffi::wl_resource_get_user_data(resource) as *mut Inspector;
+    if inspector.is_null() {
+        return;
+    }
+    let surface = ffi::wlr_surface_from_resource(surface_resource);
+    if surface.is_null() {
+        return;
+    }
+    if !state.is_null() {
+        let state_str = std::ffi::CStr::from_ptr(state).to_string_lossy().into_owned();
+        (*inspector).client_states.insert(surface, state_str);
+    }
+}
+
+unsafe extern "C" fn inspector_get_inspected_surfaces(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let inspector = ffi::wl_resource_get_user_data(resource) as *mut Inspector;
+    if inspector.is_null() {
+        return;
+    }
+    let server = (*inspector).server;
+    let windows = &(*server).wm.windows;
+
+    for &window in windows.iter() {
+        if window.is_null() {
+            continue;
+        }
+        let root_surface = (*window).root_surface();
+        if root_surface.is_null() {
+            continue;
+        }
+
+        // Retrieve registered UI state JSON or default to empty
+        let state = (*inspector)
+            .client_states
+            .get(&root_surface)
+            .cloned()
+            .unwrap_or_else(|| "{}".to_string());
+
+        let title_ptr = (*window).get_title();
+        let app_id_ptr = (*window).get_app_id();
+
+        let title = if title_ptr.is_null() {
+            std::ffi::CString::new("").unwrap()
+        } else {
+            std::ffi::CStr::from_ptr(title_ptr).to_owned()
+        };
+
+        let app_id = if app_id_ptr.is_null() {
+            std::ffi::CString::new("").unwrap()
+        } else {
+            std::ffi::CStr::from_ptr(app_id_ptr).to_owned()
+        };
+
+        let state_c = std::ffi::CString::new(state).unwrap();
+
+        // Send: inspected_surface(title, app_id, x, y, width, height, state)
+        // Event inspected_surface has index 0
+        ffi::wl_resource_post_event(
+            resource,
+            0,
+            title.as_ptr(),
+            app_id.as_ptr(),
+            (*window).box_geom.x,
+            (*window).box_geom.y,
+            (*window).box_geom.width,
+            (*window).box_geom.height,
+            state_c.as_ptr(),
+        );
+    }
+
+    // Send: inspected_surface_done()
+    // Event inspected_surface_done has index 1
+    ffi::wl_resource_post_event(resource, 1);
+}
+
+static INSPECTOR_INTERFACE: ffi::zclear_inspector_v1_interface = ffi::zclear_inspector_v1_interface {
+    destroy: Some(inspector_destroy),
+    register_client: Some(inspector_register_client),
+    update_state: Some(inspector_update_state),
+    get_inspected_surfaces: Some(inspector_get_inspected_surfaces),
+};
diff --git a/src/layer_shell.rs b/src/layer_shell.rs
index ff981ae..9a7c5e8 100644
--- a/src/layer_shell.rs
+++ b/src/layer_shell.rs
@@ -296,6 +296,8 @@ pub struct LayerSurface {
     pub wlr_layer_surface: *mut ffi::wlr_layer_surface_v1,
     pub scene_layer_surface: *mut ffi::wlr_scene_layer_surface_v1,
     pub popup_tree: *mut ffi::wlr_scene_tree,
+    pub opacity: f32,
+    pub animation_timer: *mut ffi::wl_event_source,
 
     pub destroy: ffi::wl_listener,
     pub map: ffi::wl_listener,
@@ -327,6 +329,8 @@ impl LayerSurface {
             wlr_layer_surface,
             scene_layer_surface,
             popup_tree,
+            opacity: 1.0,
+            animation_timer: std::ptr::null_mut(),
             destroy: std::mem::zeroed(),
             map: std::mem::zeroed(),
             unmap: std::mem::zeroed(),
@@ -382,6 +386,11 @@ unsafe extern "C" fn handle_layer_surface_destroy(listener: *mut ffi::wl_listene
 
     log::debug!("layer surface {:?} destroyed", CStr::from_ptr((*(*layer_surface).wlr_layer_surface).namespace));
 
+    if !(*layer_surface).animation_timer.is_null() {
+        ffi::wl_event_source_remove((*layer_surface).animation_timer);
+        (*layer_surface).animation_timer = std::ptr::null_mut();
+    }
+
     wl_listener_remove(&mut (*layer_surface).destroy);
     wl_listener_remove(&mut (*layer_surface).map);
     wl_listener_remove(&mut (*layer_surface).unmap);
@@ -399,6 +408,31 @@ unsafe extern "C" fn handle_layer_surface_destroy(listener: *mut ffi::wl_listene
     let _ = Box::from_raw(layer_surface);
 }
 
+unsafe extern "C" fn handle_animation_tick(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
+    let layer_surface = data as *mut LayerSurface;
+
+    let new_opacity = ((*layer_surface).opacity + 0.08).min(1.0);
+    (*layer_surface).opacity = new_opacity;
+
+    ffi::river_scene_node_set_opacity(
+        (*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node,
+        new_opacity,
+    );
+
+    if new_opacity >= 1.0 {
+        if !(*layer_surface).animation_timer.is_null() {
+            ffi::wl_event_source_remove((*layer_surface).animation_timer);
+            (*layer_surface).animation_timer = std::ptr::null_mut();
+        }
+    } else {
+        if !(*layer_surface).animation_timer.is_null() {
+            ffi::wl_event_source_timer_update((*layer_surface).animation_timer, 16);
+        }
+    }
+
+    0
+}
+
 unsafe extern "C" fn handle_layer_surface_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
     let layer_surface = crate::container_of!(listener, LayerSurface, map);
     let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
@@ -407,6 +441,32 @@ unsafe extern "C" fn handle_layer_surface_map(listener: *mut ffi::wl_listener, _
 
     let server = (*layer_surface).server;
 
+    if (*wlr_layer_surface).current.layer == ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY {
+        (*layer_surface).opacity = 0.0;
+        ffi::river_scene_node_set_opacity(
+            (*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node,
+            0.0,
+        );
+
+        if !(*layer_surface).animation_timer.is_null() {
+            ffi::wl_event_source_remove((*layer_surface).animation_timer);
+            (*layer_surface).animation_timer = std::ptr::null_mut();
+        }
+
+        let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
+        let timer = ffi::wl_event_loop_add_timer(
+            event_loop,
+            Some(handle_animation_tick),
+            layer_surface as *mut _,
+        );
+        if timer.is_null() {
+            log::error!("Failed to create layer surface animation timer");
+        } else {
+            (*layer_surface).animation_timer = timer;
+            ffi::wl_event_source_timer_update(timer, 16);
+        }
+    }
+
     if (*wlr_layer_surface).current.keyboard_interactive == ffi::zwlr_layer_surface_v1_keyboard_interactivity_ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND {
         let seats = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
         let mut curr = (*seats).next;
@@ -432,6 +492,11 @@ unsafe extern "C" fn handle_layer_surface_unmap(listener: *mut ffi::wl_listener,
 
     log::debug!("layer surface {:?} unmapped", CStr::from_ptr((*wlr_layer_surface).namespace));
 
+    if !(*layer_surface).animation_timer.is_null() {
+        ffi::wl_event_source_remove((*layer_surface).animation_timer);
+        (*layer_surface).animation_timer = std::ptr::null_mut();
+    }
+
     let server = (*layer_surface).server;
 
     let seats = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
@@ -469,6 +534,13 @@ unsafe extern "C" fn handle_layer_surface_commit(listener: *mut ffi::wl_listener
         );
     }
 
+    if (*layer_surface).opacity < 1.0 {
+        ffi::river_scene_node_set_opacity(
+            (*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node,
+            (*layer_surface).opacity,
+        );
+    }
+
     assert!(!(*wlr_layer_surface).output.is_null());
 
     let server = (*layer_surface).server;
diff --git a/src/main.rs b/src/main.rs
index 5dfdc02..557b7fe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -41,6 +41,7 @@ pub mod input_popup;
 pub mod drag_icon;
 pub mod pointer_binding;
 pub mod keyboard_group;
+pub mod inspector;
 
 
 use clap::Parser;
diff --git a/src/pointer_binding.rs b/src/pointer_binding.rs
index cfc2115..4d10910 100644
--- a/src/pointer_binding.rs
+++ b/src/pointer_binding.rs
@@ -12,7 +12,7 @@ pub enum PointerBindingStateChange {
 }
 
 pub struct PointerBindingScheduled {
-    pub state_change: PointerBindingStateChange,
+    pub state_changes: Vec<PointerBindingStateChange>,
 }
 
 pub struct PointerBindingRequested {
@@ -45,7 +45,7 @@ impl PointerBinding {
             button,
             modifiers,
             wm_scheduled: PointerBindingScheduled {
-                state_change: PointerBindingStateChange::None,
+                state_changes: Vec::new(),
             },
             wm_requested: PointerBindingRequested {
                 enabled: false,
@@ -91,16 +91,12 @@ impl PointerBinding {
     }
 
     pub unsafe fn pressed(&mut self) {
-        assert!(!self.sent_pressed);
-        assert!(matches!(self.wm_scheduled.state_change, PointerBindingStateChange::None));
-        self.wm_scheduled.state_change = PointerBindingStateChange::Pressed;
+        self.wm_scheduled.state_changes.push(PointerBindingStateChange::Pressed);
         (*(*self.seat).server).wm.dirty_windowing();
     }
 
     pub unsafe fn released(&mut self) {
-        assert!(self.sent_pressed);
-        assert!(matches!(self.wm_scheduled.state_change, PointerBindingStateChange::None));
-        self.wm_scheduled.state_change = PointerBindingStateChange::Released;
+        self.wm_scheduled.state_changes.push(PointerBindingStateChange::Released);
         (*(*self.seat).server).wm.dirty_windowing();
     }
 
diff --git a/src/seat.rs b/src/seat.rs
index 81b7dd6..9fe702b 100644
--- a/src/seat.rs
+++ b/src/seat.rs
@@ -554,26 +554,30 @@ impl Seat {
             while curr != bindings_head {
                 let next = (*curr).next;
                 let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
-                match (*binding).wm_scheduled.state_change {
-                    crate::xkb_bindings::XkbBindingStateChange::None => {},
-                    crate::xkb_bindings::XkbBindingStateChange::Pressed => {
-                        assert!(!(*binding).sent_pressed);
-                        (*binding).sent_pressed = true;
-                        ffi::wl_resource_post_event((*binding).object, 0);
-                    },
-                    crate::xkb_bindings::XkbBindingStateChange::StopRepeat => {
-                        assert!((*binding).sent_pressed);
-                        if ffi::wl_resource_get_version((*binding).object) >= 2 {
-                            ffi::wl_resource_post_event((*binding).object, 2);
-                        }
-                    },
-                    crate::xkb_bindings::XkbBindingStateChange::Released => {
-                        assert!((*binding).sent_pressed);
-                        (*binding).sent_pressed = false;
-                        ffi::wl_resource_post_event((*binding).object, 1);
-                    },
+                for state in (*binding).wm_scheduled.state_changes.drain(..) {
+                    match state {
+                        crate::xkb_bindings::XkbBindingStateChange::None => {},
+                        crate::xkb_bindings::XkbBindingStateChange::Pressed => {
+                            if !(*binding).sent_pressed {
+                                (*binding).sent_pressed = true;
+                                ffi::wl_resource_post_event((*binding).object, 0);
+                            }
+                        },
+                        crate::xkb_bindings::XkbBindingStateChange::StopRepeat => {
+                            if (*binding).sent_pressed {
+                                if ffi::wl_resource_get_version((*binding).object) >= 2 {
+                                    ffi::wl_resource_post_event((*binding).object, 2);
+                                }
+                            }
+                        },
+                        crate::xkb_bindings::XkbBindingStateChange::Released => {
+                            if (*binding).sent_pressed {
+                                (*binding).sent_pressed = false;
+                                ffi::wl_resource_post_event((*binding).object, 1);
+                            }
+                        },
+                    }
                 }
-                (*binding).wm_scheduled.state_change = crate::xkb_bindings::XkbBindingStateChange::None;
                 curr = next;
             }
 
@@ -583,20 +587,23 @@ impl Seat {
             while curr_ptr != ptr_bindings_head {
                 let next = (*curr_ptr).next;
                 let binding = crate::container_of!(curr_ptr, crate::pointer_binding::PointerBinding, link);
-                match (*binding).wm_scheduled.state_change {
-                    crate::pointer_binding::PointerBindingStateChange::None => {}
-                    crate::pointer_binding::PointerBindingStateChange::Pressed => {
-                        assert!(!(*binding).sent_pressed);
-                        (*binding).sent_pressed = true;
-                        ffi::wl_resource_post_event((*binding).object, 0); // pressed
-                    }
-                    crate::pointer_binding::PointerBindingStateChange::Released => {
-                        assert!((*binding).sent_pressed);
-                        (*binding).sent_pressed = false;
-                        ffi::wl_resource_post_event((*binding).object, 1); // released
+                for state in (*binding).wm_scheduled.state_changes.drain(..) {
+                    match state {
+                        crate::pointer_binding::PointerBindingStateChange::None => {}
+                        crate::pointer_binding::PointerBindingStateChange::Pressed => {
+                            if !(*binding).sent_pressed {
+                                (*binding).sent_pressed = true;
+                                ffi::wl_resource_post_event((*binding).object, 0); // pressed
+                            }
+                        }
+                        crate::pointer_binding::PointerBindingStateChange::Released => {
+                            if (*binding).sent_pressed {
+                                (*binding).sent_pressed = false;
+                                ffi::wl_resource_post_event((*binding).object, 1); // released
+                            }
+                        }
                     }
                 }
-                (*binding).wm_scheduled.state_change = crate::pointer_binding::PointerBindingStateChange::None;
                 curr_ptr = next;
             }
 
@@ -780,7 +787,16 @@ unsafe extern "C" fn handle_request_set_cursor(
     let event = data as *mut ffi::wlr_seat_pointer_request_set_cursor_event;
     
     let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
-    if focused_client == (*event).seat_client {
+    
+    let event_client = ffi::river_wlr_seat_client_get_client((*event).seat_client);
+    let wm_client = if !(*seat.server).wm.object.is_null() {
+        ffi::wl_resource_get_client((*seat.server).wm.object)
+    } else {
+        std::ptr::null_mut()
+    };
+    let is_wm = !wm_client.is_null() && event_client == wm_client;
+
+    if focused_client == (*event).seat_client || is_wm {
         ffi::wlr_cursor_set_surface(
             seat.cursor.wlr_cursor,
             (*event).surface,
diff --git a/src/server.rs b/src/server.rs
index 0a967f7..f077b72 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -270,6 +270,7 @@ pub struct Server {
     pub xkb_config: XkbConfig,
     pub idle_inhibit_manager: IdleInhibitManager,
     pub lock_manager: LockManager,
+    pub inspector: crate::inspector::Inspector,
 
     // Event listeners
     pub renderer_lost: ffi::wl_listener,
@@ -316,15 +317,39 @@ unsafe extern "C" fn handle_request_activate(listener: *mut ffi::wl_listener, _d
 }
 
 unsafe extern "C" fn handle_request_set_cursor_shape(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
-    let _server = container_of!(listener, Server, request_set_cursor_shape);
+    let server = container_of!(listener, Server, request_set_cursor_shape);
     let event = data as *mut ffi::wlr_cursor_shape_manager_v1_request_set_shape_event;
     
     let wlr_seat = (*(*event).seat_client).seat;
     let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(wlr_seat);
-    if focused_client == (*event).seat_client {
+    
+    let event_client = ffi::river_wlr_seat_client_get_client((*event).seat_client);
+    let wm_client = if !(*server).wm.object.is_null() {
+        ffi::wl_resource_get_client((*server).wm.object)
+    } else {
+        std::ptr::null_mut()
+    };
+    let is_wm = !wm_client.is_null() && event_client == wm_client;
+
+    let shape_name = ffi::wlr_cursor_shape_v1_name((*event).shape);
+    let shape_str = if shape_name.is_null() {
+        "unknown".to_string()
+    } else {
+        std::ffi::CStr::from_ptr(shape_name).to_string_lossy().into_owned()
+    };
+
+    log::debug!(
+        "set_cursor_shape: event_client={:?}, wm_client={:?}, is_wm={}, focused={:?}, shape={}",
+        event_client,
+        wm_client,
+        is_wm,
+        focused_client,
+        shape_str
+    );
+
+    if focused_client == (*event).seat_client || is_wm {
         let seat = ffi::river_wlr_seat_get_data(wlr_seat) as *mut crate::seat::Seat;
         if !seat.is_null() {
-            let shape_name = ffi::wlr_cursor_shape_v1_name((*event).shape);
             (*seat).cursor.set_xcursor(shape_name);
         }
     }
@@ -596,6 +621,7 @@ impl Server {
             self.xkb_config.init(server_ptr).map_err(|_| "Failed to init xkb_config")?;
             self.idle_inhibit_manager.init(server_ptr).map_err(|_| "Failed to init idle_inhibit_manager")?;
             self.lock_manager.init(server_ptr).map_err(|_| "Failed to init lock_manager")?;
+            self.inspector.init(server_ptr).map_err(|_| "Failed to init inspector")?;
 
             // Setup listeners
             let r_lost = &mut self.renderer_lost as *mut ffi::wl_listener as *mut WlListener;
@@ -670,6 +696,7 @@ impl Server {
             self.idle_inhibit_manager.deinit();
             self.lock_manager.deinit();
             self.layer_shell.deinit();
+            self.inspector.deinit();
 
             ffi::wl_display_destroy(self.wl_server);
         }
@@ -685,6 +712,7 @@ impl Default for Server {
             // Overwrite SlotMap with a valid SlotMap::new() to avoid UB from null vec pointers
             std::ptr::write(&mut (*server.as_mut_ptr()).wm.windows, crate::slotmap::SlotMap::new());
             std::ptr::write(&mut (*server.as_mut_ptr()).layer_shell.surfaces, crate::slotmap::SlotMap::new());
+            std::ptr::write(&mut (*server.as_mut_ptr()).inspector, crate::inspector::Inspector::new());
             server.assume_init()
         }
     }
diff --git a/src/window.rs b/src/window.rs
index b53d49e..e08e5ae 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -181,6 +181,7 @@ pub struct WindowRenderingRequested {
     pub border: Border,
     pub clip: ffi::wlr_box,
     pub content_clip: ffi::wlr_box,
+    pub opacity: f32,
 }
 
 pub struct Window {
@@ -339,6 +340,7 @@ impl Window {
                 border: Border { edges: Edges::new(), width: 0, r: 0, g: 0, b: 0, a: 0 },
                 clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
                 content_clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+                opacity: 1.0f32,
             },
             box_geom: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
             foreign_toplevel_handle: std::ptr::null_mut(),
@@ -624,6 +626,13 @@ impl Window {
                     ffi::river_wlr_xdg_surface_get_surface(base)
                 }
             }
+            WindowImpl::Xwayland(xwindow) => {
+                if xwindow.is_null() || (*xwindow).xsurface.is_null() {
+                    std::ptr::null_mut()
+                } else {
+                    (*(*xwindow).xsurface).surface
+                }
+            }
             _ => std::ptr::null_mut(),
         }
     }
@@ -675,6 +684,7 @@ impl Window {
                     border: Border { edges: Edges::new(), width: 0, r: 0, g: 0, b: 0, a: 0 },
                     clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
                     content_clip: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
+                    opacity: 1.0f32,
                 };
 
                 wl_list_remove(&mut self.node.link as *mut ffi::wl_list as *mut WlList);
@@ -1025,8 +1035,9 @@ impl Window {
             }
             WindowImpl::Xwayland(xwindow) => {
                 if !xwindow.is_null() {
-                    self.rendering_scheduled.width = (*(*xwindow).xsurface).width as u32;
-                    self.rendering_scheduled.height = (*(*xwindow).xsurface).height as u32;
+                    let scale = crate::xwayland_window::XwaylandWindow::get_scale(xwindow);
+                    self.rendering_scheduled.width = ((*(*xwindow).xsurface).width as f32 / scale).round() as u32;
+                    self.rendering_scheduled.height = ((*(*xwindow).xsurface).height as f32 / scale).round() as u32;
                 }
             }
             WindowImpl::Destroying => {}
@@ -1125,6 +1136,7 @@ impl Window {
 
         if enabled {
             ffi::river_scene_node_enable_blur(self.surfaces.tree as *mut ffi::wlr_scene_node, true);
+            ffi::river_scene_node_set_opacity(self.tree as *mut ffi::wlr_scene_node, requested.opacity);
         }
 
         self.box_geom.width = self.rendering_sent.width as i32;
@@ -1758,6 +1770,23 @@ unsafe extern "C" fn window_set_dimension_bounds(
     };
 }
 
+unsafe extern "C" fn window_set_opacity(
+    client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+    opacity: u32,
+) {
+    let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
+    if window.is_null() {
+        return;
+    }
+    let server = (*window).server;
+    if !(*server).wm.ensure_rendering() {
+        return;
+    }
+    let opacity_f32 = opacity as f32 / u32::MAX as f32;
+    (*window).rendering_requested.opacity = opacity_f32;
+}
+
 // river_window_v1 implementation
 static WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_interface {
     destroy: Some(window_destroy),
@@ -1784,6 +1813,7 @@ static WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_i
     set_clip_box: Some(window_set_clip_box),
     set_content_clip_box: Some(window_set_content_clip_box),
     set_dimension_bounds: Some(window_set_dimension_bounds),
+    set_opacity: Some(window_set_opacity),
 };
 
 static INERT_WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_interface {
@@ -1811,6 +1841,7 @@ static INERT_WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_windo
     set_clip_box: None,
     set_content_clip_box: None,
     set_dimension_bounds: None,
+    set_opacity: None,
 };
 
 unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 60700c8..160e6d9 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -243,7 +243,12 @@ impl WindowManager {
             curr = next;
         }
 
-        assert!(self.sent.output_config.is_null());
+        if !self.sent.output_config.is_null() {
+            log::warn!("sent.output_config was not null in manage_start, destroying old configuration");
+            ffi::wlr_output_configuration_v1_send_failed(self.sent.output_config);
+            ffi::wlr_output_configuration_v1_destroy(self.sent.output_config);
+            self.sent.output_config = std::ptr::null_mut();
+        }
         self.sent.output_config = self.scheduled.output_config;
         self.scheduled.output_config = std::ptr::null_mut();
 
@@ -489,8 +494,7 @@ unsafe extern "C" fn dirty_idle_callback(data: *mut std::ffi::c_void) {
         WindowManagerState::Idle => {
             if (*wm).rendering_scheduled.dirty {
                 (*wm).render_start();
-            } else {
-                assert!((*wm).scheduled.dirty || (*wm).scheduled.dirty_lazy);
+            } else if (*wm).scheduled.dirty || (*wm).scheduled.dirty_lazy {
                 (*wm).scheduled.dirty = true;
                 (*wm).scheduled.dirty_lazy = false;
                 (*wm).manage_start();
@@ -720,7 +724,7 @@ unsafe extern "C" fn handle_destroy_wm_resource(resource: *mut ffi::wl_resource)
         while curr_b != bindings_head {
             let next_b = (*curr_b).next;
             let binding = crate::container_of!(curr_b, crate::xkb_bindings::XkbBinding, link);
-            (*binding).wm_scheduled.state_change = crate::xkb_bindings::XkbBindingStateChange::None;
+            (*binding).wm_scheduled.state_changes.clear();
             curr_b = next_b;
         }
         
diff --git a/src/wlroots_log_wrapper.c b/src/wlroots_log_wrapper.c
index bc4927f..b23f0c9 100644
--- a/src/wlroots_log_wrapper.c
+++ b/src/wlroots_log_wrapper.c
@@ -661,5 +661,15 @@ void river_scene_node_enable_blur(struct wlr_scene_node *node, bool enabled) {
 	wlr_scene_node_for_each_buffer(node, enable_blur_iterator, &enabled);
 }
 
+static void set_opacity_iterator(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) {
+	float opacity = *(float *)user_data;
+	wlr_scene_buffer_set_opacity(buffer, opacity);
+}
+
+void river_scene_node_set_opacity(struct wlr_scene_node *node, float opacity) {
+	wlr_scene_node_for_each_buffer(node, set_opacity_iterator, &opacity);
+}
+
+
 
 
diff --git a/src/xkb_bindings.rs b/src/xkb_bindings.rs
index ef82cfd..2444794 100644
--- a/src/xkb_bindings.rs
+++ b/src/xkb_bindings.rs
@@ -313,7 +313,7 @@ unsafe extern "C" fn bindings_get_seat(
 }
 
 pub struct XkbBindingScheduled {
-    pub state_change: XkbBindingStateChange,
+    pub state_changes: Vec<XkbBindingStateChange>,
 }
 
 pub struct XkbBindingRequested {
@@ -347,7 +347,7 @@ impl XkbBinding {
             keysym,
             modifiers,
             wm_scheduled: XkbBindingScheduled {
-                state_change: XkbBindingStateChange::None,
+                state_changes: Vec::new(),
             },
             wm_requested: XkbBindingRequested {
                 enabled: false,
@@ -389,28 +389,26 @@ impl XkbBinding {
     }
     
     pub unsafe fn pressed(&mut self) {
-        assert!(!self.sent_pressed);
         if (*(*self.seat).server).wm.object.is_null() {
             log::warn!("Pressed keybind while window manager is disconnected");
-            self.wm_scheduled.state_change = XkbBindingStateChange::None;
             return;
         }
-        assert!(matches!(self.wm_scheduled.state_change, XkbBindingStateChange::None));
-        self.wm_scheduled.state_change = XkbBindingStateChange::Pressed;
+        self.wm_scheduled.state_changes.push(XkbBindingStateChange::Pressed);
         (*(*self.seat).server).wm.dirty_windowing();
     }
     
     pub unsafe fn released(&mut self) {
-        self.wm_scheduled.state_change = XkbBindingStateChange::Released;
+        self.wm_scheduled.state_changes.push(XkbBindingStateChange::Released);
         (*(*self.seat).server).wm.dirty_windowing();
     }
 
     pub unsafe fn stop_repeat(&mut self) {
-        if self.sent_pressed {
-            if matches!(self.wm_scheduled.state_change, XkbBindingStateChange::None) {
-                self.wm_scheduled.state_change = XkbBindingStateChange::StopRepeat;
-                (*(*self.seat).server).wm.dirty_windowing();
-            }
+        let already_releasing_or_stopping = self.wm_scheduled.state_changes.iter().any(|&s| 
+            matches!(s, XkbBindingStateChange::Released | XkbBindingStateChange::StopRepeat)
+        );
+        if self.sent_pressed && !already_releasing_or_stopping {
+            self.wm_scheduled.state_changes.push(XkbBindingStateChange::StopRepeat);
+            (*(*self.seat).server).wm.dirty_windowing();
         }
     }
 
diff --git a/src/xwayland_window.rs b/src/xwayland_window.rs
index c1ed65d..8833ae9 100644
--- a/src/xwayland_window.rs
+++ b/src/xwayland_window.rs
@@ -115,11 +115,38 @@ impl XwaylandWindow {
         Ok(())
     }
 
+    pub unsafe fn get_scale(xwindow: *mut XwaylandWindow) -> f32 {
+        let window = (*xwindow).window;
+        if window.is_null() {
+            return 1.0;
+        }
+        let server = (*window).server;
+        if server.is_null() {
+            return 1.0;
+        }
+        let wlr_output = (*server).om.max_overlap_output(&(*window).box_geom);
+        if !wlr_output.is_null() {
+            let output = ffi::river_wlr_output_get_data(wlr_output) as *mut crate::output::Output;
+            if !output.is_null() {
+                return (*output).current.scale;
+            }
+        }
+        // Fallback: first output in layout
+        let mut link = (*server).om.outputs.next;
+        if link != &mut (*server).om.outputs as *mut ffi::wl_list {
+            let output = crate::container_of!(link, crate::output::Output, link);
+            return (*output).current.scale;
+        }
+        1.0
+    }
+
     pub unsafe fn configure(&mut self) -> bool {
         let window = self.window;
         let scheduled = &mut (*window).configure_scheduled;
         let sent = &mut (*window).configure_sent;
 
+        let scale = Self::get_scale(self);
+
         if scheduled.width == Some(0) {
             scheduled.width = Some((*self.xsurface).width as u32);
         }
@@ -127,20 +154,32 @@ impl XwaylandWindow {
             scheduled.height = Some((*self.xsurface).height as u32);
         }
 
-        let width = scheduled.width.unwrap_or((*self.xsurface).width as u32);
-        let height = scheduled.height.unwrap_or((*self.xsurface).height as u32);
+        let phys_width = if let Some(w) = scheduled.width {
+            (w as f32 * scale).round() as u16
+        } else {
+            (*self.xsurface).width
+        };
+
+        let phys_height = if let Some(h) = scheduled.height {
+            (h as f32 * scale).round() as u16
+        } else {
+            (*self.xsurface).height
+        };
+
+        let phys_x = ((*window).box_geom.x as f32 * scale).round() as i16;
+        let phys_y = ((*window).box_geom.y as f32 * scale).round() as i16;
 
-        if (*window).box_geom.x != (*self.xsurface).x as i32
-            || (*window).box_geom.y != (*self.xsurface).y as i32
-            || width != (*self.xsurface).width as u32
-            || height != (*self.xsurface).height as u32
+        if phys_x != (*self.xsurface).x
+            || phys_y != (*self.xsurface).y
+            || phys_width != (*self.xsurface).width
+            || phys_height != (*self.xsurface).height
         {
             ffi::wlr_xwayland_surface_configure(
                 self.xsurface,
-                (*window).box_geom.x as i16,
-                (*window).box_geom.y as i16,
-                width as u16,
-                height as u16,
+                phys_x,
+                phys_y,
+                phys_width,
+                phys_height,
             );
         }
 
@@ -154,6 +193,9 @@ impl XwaylandWindow {
             ffi::wlr_xwayland_surface_set_fullscreen(self.xsurface, scheduled.inform_fullscreen);
         }
 
+        let width = scheduled.width.unwrap_or(((*self.xsurface).width as f32 / scale).round() as u32);
+        let height = scheduled.height.unwrap_or(((*self.xsurface).height as f32 / scale).round() as u32);
+
         (*window).configure_sent = (*window).configure_scheduled.clone();
         (*window).configure_sent.width = Some(width);
         (*window).configure_sent.height = Some(height);
@@ -300,14 +342,20 @@ unsafe extern "C" fn handle_request_configure(listener: *mut ffi::wl_listener, d
         return;
     }
 
+    let scale = XwaylandWindow::get_scale(xwindow);
+    let phys_x = ((*(*xwindow).window).box_geom.x as f32 * scale).round() as i16;
+    let phys_y = ((*(*xwindow).window).box_geom.y as f32 * scale).round() as i16;
+
     ffi::wlr_xwayland_surface_configure(
         (*xwindow).xsurface,
-        (*(*xwindow).window).box_geom.x as i16,
-        (*(*xwindow).window).box_geom.y as i16,
+        phys_x,
+        phys_y,
         (*event).width,
         (*event).height,
     );
-    (*(*xwindow).window).set_dimensions((*event).width as u32, (*event).height as u32);
+    let log_width = ((*event).width as f32 / scale).round() as u32;
+    let log_height = ((*event).height as f32 / scale).round() as u32;
+    (*(*xwindow).window).set_dimensions(log_width, log_height);
 }
 
 unsafe extern "C" fn handle_set_override_redirect(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
diff --git a/start-rust.sh b/start-rust.sh
index 8d28fff..23c4ff3 100755
--- a/start-rust.sh
+++ b/start-rust.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-# Launch clear-river with clearwm
+# Launch clear-computing-environment-server with ccec
 # Usage: ./start-rust.sh [--logging] [--debug]
 
 LOGGING=false
@@ -17,30 +17,30 @@ export XCURSOR_SIZE=24
 export XCURSOR_PATH="/home/lsgalante/.local/share/icons:/home/lsgalante/.icons:/usr/share/icons"
 export WLR_NO_HARDWARE_CURSORS=1
 
-# Create the clearwm launch script
+# Create the ccec launch script
 DEBUG_FLAG=""
 if [ "$DEBUG" = true ]; then
     DEBUG_FLAG="WAYLAND_DEBUG=1 "
 fi
 
-cat > /tmp/clearwm-rs-launch-rust.sh << LAUNCH_EOF
+cat > /tmp/ccec-launch-rust.sh << LAUNCH_EOF
 #!/bin/sh
-${DEBUG_FLAG}exec /home/lsgalante/.local/bin/clearwm 2>/tmp/clearwm-\${WAYLAND_DISPLAY}.log
+${DEBUG_FLAG}exec /home/lsgalante/.local/bin/ccec 2>/tmp/ccec-\${WAYLAND_DISPLAY}.log
 LAUNCH_EOF
-chmod +x /tmp/clearwm-rs-launch-rust.sh
+chmod +x /tmp/ccec-launch-rust.sh
 
 if [ "$LOGGING" = true ] || [ "$DEBUG" = true ]; then
-    echo "Starting clear-river with clearwm..."
-    echo "  Logs: /tmp/river-clearwm.log + /tmp/clearwm-\${WAYLAND_DISPLAY}.log"
+    echo "Starting clear-computing-environment-server with ccec..."
+    echo "  Logs: /tmp/river-ccec.log + /tmp/ccec-\${WAYLAND_DISPLAY}.log"
     if [ "$DEBUG" = true ]; then
         echo "  Wayland debug logging enabled (WAYLAND_DEBUG=1)"
     fi
 fi
 
-# Resolve script directory to reference target/release/clear-river reliably
+# Resolve script directory to reference target/release/clear-computing-environment-server reliably
 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 if [ "$DEBUG" = true ]; then
     export WAYLAND_DEBUG=1
 fi
-exec "$SCRIPT_DIR/target/release/clear-river" -c /tmp/clearwm-rs-launch-rust.sh 2>/tmp/river-clearwm.log
+exec "$SCRIPT_DIR/target/release/clear-computing-environment-server" -c /tmp/ccec-launch-rust.sh 2>/tmp/river-ccec.log
 
diff --git a/wrapper.h b/wrapper.h
index efe0ca5..756a637 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -97,6 +97,7 @@
 #include "river-input-management-v1-protocol.h"
 #include "river-libinput-config-v1-protocol.h"
 #include "river-xkb-config-v1-protocol.h"
+#include "clear-inspector-v1-protocol.h"
 #include "virtual-keyboard-unstable-v1-protocol.h"
 #include <wlr/backend/libinput.h>
 
@@ -258,4 +259,6 @@ void river_wlr_keyboard_init(struct wlr_keyboard *keyboard, void (*led_update)(s
 
 void river_scene_node_enable_blur(struct wlr_scene_node *node, bool enabled);
 
+void river_scene_node_set_opacity(struct wlr_scene_node *node, float opacity);
+
 #endif // WRAPPER_H