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

commitd4e462e04fe262da8efae7741bda0bbbc35f545f
parent6c9f39718d
authorLucas Galante <[email protected]>
date2026-05-28 13:04
Commit local changes: unify protocol/opcodes, fix pointer focus and keybind safety

 build.rs               |   2 +
 src/cursor.rs          |  67 ++++++++++--------
 src/ffi.rs             |  23 ++++++
 src/input_device.rs    |   8 ++-
 src/layer_shell.rs     |  11 +--
 src/main.rs            |   1 +
 src/output.rs          | 187 ++++++++++++++++++++++++++++++++++++++++++++-----
 src/output_manager.rs  |  14 ++--
 src/seat.rs            |  38 +++++++++-
 src/server.rs          |  18 ++++-
 src/window.rs          |  61 ++++++++++------
 src/window_manager.rs  |  27 +++++--
 src/xkb_bindings.rs    |  22 ++++++
 src/xwayland_window.rs |   2 +-
 start-rust.sh          |  45 ++++++++++++
 wrapper.h              |   2 +-
 16 files changed, 436 insertions(+), 92 deletions(-)

diff --git a/build.rs b/build.rs
index 774937f..01844e3 100644
--- a/build.rs
+++ b/build.rs
@@ -152,6 +152,8 @@ fn main() {
         .blocklist_item("FP_SUBNORMAL")
         .blocklist_item("FP_NORMAL")
         .blocklist_item("wl_listener")
+        .blocklist_item("wlr_addon")
+        .blocklist_item("wlr_input_device")
         .generate()
         .expect("Unable to generate bindings");
 
diff --git a/src/cursor.rs b/src/cursor.rs
index 757a0ab..6714ffa 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -343,6 +343,7 @@ impl Cursor {
             (*self.constraint).update_state();
         }
         self.update_hovered();
+        self.passthrough(crate::util::msec_timestamp());
     }
 
     pub unsafe fn update_drag_icons(&mut self) {
@@ -434,6 +435,39 @@ impl Cursor {
             log::debug!("entering cursor mode ignore");
         }
     }
+
+    pub unsafe fn passthrough(&mut self, time_msec: u32) {
+        let lx = self.x();
+        let ly = self.y();
+        let server = (*self.seat).server;
+
+        if let Some(result) = (*server).scene.at(lx, ly) {
+            let lock_state = (*server).lock_manager.state;
+            if lock_state != crate::lock_manager::LockState::Unlocked {
+                if !matches!(result.data, SceneNodeDataVal::LockSurface(_)) {
+                    self.clear_focus();
+                    return;
+                }
+            } else {
+                if matches!(result.data, SceneNodeDataVal::LockSurface(_)) {
+                    self.clear_focus();
+                    return;
+                }
+            }
+
+            if !result.surface.is_null() {
+                ffi::wlr_seat_pointer_notify_enter((*self.seat).wlr_seat, result.surface, result.sx, result.sy);
+                ffi::wlr_seat_pointer_notify_motion((*self.seat).wlr_seat, time_msec, result.sx, result.sy);
+                return;
+            }
+        }
+
+        self.clear_focus();
+    }
+
+    pub unsafe fn clear_focus(&mut self) {
+        ffi::wlr_seat_pointer_notify_clear_focus((*self.seat).wlr_seat);
+    }
 }
 
 unsafe extern "C" fn handle_motion(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
@@ -459,20 +493,7 @@ unsafe extern "C" fn handle_motion(listener: *mut ffi::wl_listener, data: *mut s
         return;
     }
 
-    let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
-    if !focused_client.is_null() {
-        let lx = cursor.x();
-        let ly = cursor.y();
-        let server = seat.server;
-        if let Some(result) = (*server).scene.at(lx, ly) {
-            ffi::wlr_seat_pointer_notify_motion(
-                seat.wlr_seat,
-                (*event).time_msec,
-                result.sx,
-                result.sy,
-            );
-        }
-    }
+    cursor.passthrough((*event).time_msec);
 }
 
 unsafe extern "C" fn handle_motion_absolute(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
@@ -491,20 +512,7 @@ unsafe extern "C" fn handle_motion_absolute(listener: *mut ffi::wl_listener, dat
         return;
     }
 
-    let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
-    if !focused_client.is_null() {
-        let lx = cursor.x();
-        let ly = cursor.y();
-        let server = seat.server;
-        if let Some(result) = (*server).scene.at(lx, ly) {
-            ffi::wlr_seat_pointer_notify_motion(
-                seat.wlr_seat,
-                (*event).time_msec,
-                result.sx,
-                result.sy,
-            );
-        }
-    }
+    cursor.passthrough((*event).time_msec);
 }
 
 unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
@@ -540,6 +548,9 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
         let server = seat.server;
         if let Some(result) = (*server).scene.at(lx, ly) {
             match result.data {
+                SceneNodeDataVal::Window(window) => {
+                    seat.focus(Focus::Window(window));
+                }
                 SceneNodeDataVal::LayerSurface(_) => {
                     seat.focus(Focus::LayerSurface(result.surface));
                 }
diff --git a/src/ffi.rs b/src/ffi.rs
index acbd2c3..6361257 100644
--- a/src/ffi.rs
+++ b/src/ffi.rs
@@ -14,6 +14,29 @@ pub struct wl_listener {
 
 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
 
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct wlr_input_device {
+    pub type_: wlr_input_device_type,
+    pub name: *mut ::std::os::raw::c_char,
+    pub events: wlr_input_device_events,
+    pub data: *mut ::std::os::raw::c_void,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct wlr_input_device_events {
+    pub destroy: wl_signal,
+}
+
 extern "C" {
     pub fn river_init_wlroots_log(importance: wlr_log_importance);
 }
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct wlr_addon {
+    pub impl_: *const wlr_addon_interface,
+    pub owner: *const ::std::os::raw::c_void,
+    pub link: wl_list,
+}
diff --git a/src/input_device.rs b/src/input_device.rs
index 15c6c41..72037fb 100644
--- a/src/input_device.rs
+++ b/src/input_device.rs
@@ -83,7 +83,11 @@ impl InputDevice {
                 curr = next;
             }
 
-            let handle = ffi::wlr_libinput_get_device_handle(wlr_device);
+            let handle = if ffi::wlr_input_device_is_libinput(wlr_device) {
+                ffi::wlr_libinput_get_device_handle(wlr_device)
+            } else {
+                std::ptr::null_mut()
+            };
             if !handle.is_null() {
                 (*device).libinput = Some(crate::libinput_device::LibinputDevice::init(device, handle));
             }
@@ -215,7 +219,7 @@ impl InputDevice {
         wl_list_insert(list_head, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
 
         // Send input_device event to the manager resource
-        ffi::wl_resource_post_event(im_v1_resource, 0, resource); // opcode 0 is input_device in river_input_manager_v1
+        ffi::wl_resource_post_event(im_v1_resource, 1, resource); // opcode 1 is input_device in river_input_manager_v1
 
         // Send type and name to client
         ffi::wl_resource_post_event(resource, 1, proto_type); // type event
diff --git a/src/layer_shell.rs b/src/layer_shell.rs
index e97644a..f652fd8 100644
--- a/src/layer_shell.rs
+++ b/src/layer_shell.rs
@@ -32,7 +32,8 @@ pub struct LayerShell {
 }
 
 impl LayerShell {
-    pub unsafe fn init(&mut self, wl_display: *mut ffi::wl_display) -> Result<(), ()> {
+    pub unsafe fn init(&mut self, server: *mut Server, wl_display: *mut ffi::wl_display) -> Result<(), ()> {
+        self.server = server;
         self.global = ffi::wl_global_create(
             wl_display,
             &ffi::river_layer_shell_v1_interface,
@@ -713,7 +714,7 @@ impl LayerShellOutput {
             if !self.object.is_null() {
                 ffi::wl_resource_post_event(
                     self.object,
-                    1, // send_non_exclusive_area
+                    ffi::RIVER_LAYER_SHELL_OUTPUT_V1_NON_EXCLUSIVE_AREA,
                     self.scheduled.non_exclusive_area.x,
                     self.scheduled.non_exclusive_area.y,
                     self.scheduled.non_exclusive_area.width,
@@ -829,13 +830,13 @@ impl LayerShellSeat {
             if !self.object.is_null() {
                 match self.scheduled_focus {
                     LayerShellSeatFocus::Exclusive(_) => {
-                        ffi::wl_resource_post_event(self.object, 1); // sendFocusExclusive
+                        ffi::wl_resource_post_event(self.object, ffi::RIVER_LAYER_SHELL_SEAT_V1_FOCUS_EXCLUSIVE);
                     }
                     LayerShellSeatFocus::NonExclusive(_) => {
-                        ffi::wl_resource_post_event(self.object, 2); // sendFocusNonExclusive
+                        ffi::wl_resource_post_event(self.object, ffi::RIVER_LAYER_SHELL_SEAT_V1_FOCUS_NON_EXCLUSIVE);
                     }
                     LayerShellSeatFocus::None => {
-                        ffi::wl_resource_post_event(self.object, 0); // sendFocusNone
+                        ffi::wl_resource_post_event(self.object, ffi::RIVER_LAYER_SHELL_SEAT_V1_FOCUS_NONE);
                     }
                 }
             }
diff --git a/src/main.rs b/src/main.rs
index 5acbd4d..5dfdc02 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -245,6 +245,7 @@ fn main() {
                         .collect();
                     let env_ptrs: Vec<&CStr> = env.iter().map(|s| s.as_c_str()).collect();
 
+                    eprintln!("[execve] target cmd: {}, env WAYLAND_DISPLAY: {:?}", cmd, std::env::var("WAYLAND_DISPLAY"));
                     let _ = nix::unistd::execve(&sh_c, &args, &env_ptrs);
                     std::process::exit(1);
                 }
diff --git a/src/output.rs b/src/output.rs
index fac4ca6..e6357f5 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -158,13 +158,67 @@ pub struct Output {
     pub present: ffi::wl_listener,
 }
 
+unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
+    let output = ffi::wl_resource_get_user_data(resource) as *mut Output;
+    if !output.is_null() {
+        if (*output).object != resource {
+            return;
+        }
+        (*output).object = std::ptr::null_mut();
+        (*output).sent_wl_output = false;
+    }
+}
+
+unsafe extern "C" fn output_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+    ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn output_set_presentation_mode(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+    mode: u32,
+) {
+    let output = ffi::wl_resource_get_user_data(resource) as *mut Output;
+    if output.is_null() {
+        return;
+    }
+    if !(*(*output).server).wm.ensure_rendering() {
+        return;
+    }
+    match mode {
+        ffi::river_output_v1_presentation_mode_RIVER_OUTPUT_V1_PRESENTATION_MODE_VSYNC => {
+            (*output).rendering_requested.tearing = false;
+        }
+        ffi::river_output_v1_presentation_mode_RIVER_OUTPUT_V1_PRESENTATION_MODE_ASYNC => {
+            (*output).rendering_requested.tearing = true;
+        }
+        _ => {
+            ffi::wl_resource_post_error(
+                resource,
+                ffi::river_output_v1_error_RIVER_OUTPUT_V1_ERROR_INVALID_PRESENTATION_MODE,
+                b"invalid presentation mode enum value\0".as_ptr() as *const _,
+            );
+        }
+    }
+}
+
+static OUTPUT_INTERFACE: ffi::river_output_v1_interface = ffi::river_output_v1_interface {
+    destroy: Some(output_destroy),
+    set_presentation_mode: Some(output_set_presentation_mode),
+};
+
+static INERT_OUTPUT_INTERFACE: ffi::river_output_v1_interface = ffi::river_output_v1_interface {
+    destroy: Some(output_destroy),
+    set_presentation_mode: None,
+};
+
 impl Output {
     pub unsafe fn make_inert(&mut self) {
         if !self.object.is_null() {
             ffi::wl_resource_post_event(self.object, 0); // river_output.removed
             ffi::wl_resource_set_implementation(
                 self.object,
-                std::ptr::null(),
+                &INERT_OUTPUT_INTERFACE as *const _ as *const _,
                 std::ptr::null_mut(),
                 None,
             );
@@ -174,6 +228,100 @@ impl Output {
         }
     }
 
+    pub unsafe fn manage_start(&mut self) {
+        match self.scheduled.state {
+            OutputStateValue::Enabled | OutputStateValue::DisabledSoft => {
+                assert!(!self.scheduled.mode_none());
+                let wlr_output = self.wlr_output;
+
+                let self_ptr = self as *mut Output;
+                let layer_shell_ptr = &mut self.layer_shell as *mut LayerShellOutput;
+                (*layer_shell_ptr).manage_start(self_ptr);
+
+                let wm_v1 = (*self.server).wm.object;
+                if !wm_v1.is_null() {
+                    let new = self.object.is_null();
+                    let output_v1 = if new {
+                        let client = ffi::wl_resource_get_client(wm_v1);
+                        let res = ffi::wl_resource_create(
+                            client,
+                            &ffi::river_output_v1_interface,
+                            ffi::wl_resource_get_version(wm_v1),
+                            0,
+                        );
+                        if res.is_null() {
+                            log::error!("out of memory");
+                            return;
+                        }
+                        self.object = res;
+                        ffi::wl_resource_set_implementation(
+                            res,
+                            &OUTPUT_INTERFACE as *const _ as *const _,
+                            self as *mut Output as *mut _,
+                            Some(handle_destroy_resource),
+                        );
+                        ffi::wl_resource_post_event(wm_v1, ffi::RIVER_WINDOW_MANAGER_V1_OUTPUT, res); // river_window_manager_v1.output
+                        res
+                    } else {
+                        self.object
+                    };
+
+                    if !self.sent_wl_output {
+                        let global = ffi::river_wlr_output_get_global(wlr_output);
+                        if !global.is_null() {
+                            let client = ffi::wl_resource_get_client(output_v1);
+                            let wl_output_name = ffi::wl_global_get_name(global, client);
+                            river_output_send_wl_output(output_v1, wl_output_name);
+                            self.sent_wl_output = true;
+                        }
+                    }
+
+                    let (scheduled_width, scheduled_height) = self.scheduled.dimensions();
+                    let (sent_width, sent_height) = self.sent.dimensions();
+
+                    if new || scheduled_width != sent_width || scheduled_height != sent_height {
+                        river_output_send_dimensions(output_v1, scheduled_width, scheduled_height);
+                    }
+                    if new || self.scheduled.x != self.sent.x || self.scheduled.y != self.sent.y {
+                        river_output_send_position(output_v1, self.scheduled.x, self.scheduled.y);
+                    }
+                }
+
+                self.sent = self.scheduled;
+
+                wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut WlList);
+                let sent_outputs = &mut (*self.server).wm.sent.outputs as *mut ffi::wl_list as *mut WlList;
+                wl_list_insert((*sent_outputs).prev, &mut self.link_sent as *mut ffi::wl_list as *mut WlList);
+            }
+            OutputStateValue::DisabledHard | OutputStateValue::Destroying => {
+                self.make_inert();
+
+                self.sent = self.scheduled;
+
+                if self.scheduled.state == OutputStateValue::Destroying {
+                    assert!(self.wlr_output.is_null());
+                    
+                    // remove output from windows fullscreen hint
+                    for &window in (*self.server).wm.windows.iter() {
+                        if let crate::window::FullscreenRequest::Fullscreen(out) = (*window).wm_scheduled.fullscreen_requested {
+                            if out == self as *mut Output {
+                                (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Fullscreen(std::ptr::null_mut());
+                            }
+                        }
+                        if (*window).wm_requested.fullscreen == self as *mut Output {
+                            (*window).wm_requested.fullscreen = std::ptr::null_mut();
+                        }
+                    }
+
+                    wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
+                    wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut WlList);
+
+                    let _ = Box::from_raw(self as *mut Output);
+                }
+            }
+        }
+    }
+
     pub unsafe fn create(server: *mut Server, wlr_output: *mut ffi::wlr_output) -> Result<(), &'static str> {
         let title = format!("river - {}\0", std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(wlr_output)).to_string_lossy());
         
@@ -259,10 +407,8 @@ impl Output {
         } else {
             (*raw).scheduled.mode = OutputMode::Custom { width: 1280, height: 720, refresh: 0 };
         }
-        // Trigger windowing manager update
-        // We will implement the actual server.wm.dirty_windowing() call in Phase 4, but for now we can stub it
-        // by log output or call a direct FFI / loop callback if needed.
-        log::debug!("Output created, dirty windowing");
+        
+        (*server).wm.dirty_windowing();
         Ok(())
     }
 
@@ -327,27 +473,28 @@ impl Output {
 }
 
 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
-    let output_ptr = crate::container_of!(listener, Output, destroy);
-    let mut output = Box::from_raw(output_ptr);
+    let output = crate::container_of!(listener, Output, destroy);
 
     log::debug!("Output destroyed");
 
     // Remove listeners
-    wl_listener_remove(&mut output.destroy);
-    wl_listener_remove(&mut output.request_state);
-    wl_listener_remove(&mut output.frame);
-    wl_listener_remove(&mut output.present);
+    wl_listener_remove(&mut (*output).destroy);
+    wl_listener_remove(&mut (*output).request_state);
+    wl_listener_remove(&mut (*output).frame);
+    wl_listener_remove(&mut (*output).present);
 
-    // Remove from outputs list
-    let link_custom = &mut output.link as *mut ffi::wl_list as *mut WlList;
-    wl_list_remove(link_custom);
+    if !(*output).wlr_output.is_null() {
+        ffi::river_wlr_output_set_data((*output).wlr_output, std::ptr::null_mut());
+    }
 
-    let link_sent_custom = &mut output.link_sent as *mut ffi::wl_list as *mut WlList;
-    wl_list_remove(link_sent_custom);
+    (*output).wlr_output = std::ptr::null_mut();
+    (*output).scene_output = std::ptr::null_mut();
+    (*output).scheduled.mode = OutputMode::None;
+    (*output).sent.mode = OutputMode::None;
+    (*output).current.mode = OutputMode::None;
+    (*output).scheduled.state = OutputStateValue::Destroying;
 
-    if !output.wlr_output.is_null() {
-        ffi::river_wlr_output_set_data(output.wlr_output, std::ptr::null_mut());
-    }
+    (*(*output).server).wm.dirty_windowing();
 }
 
 unsafe extern "C" fn handle_request_state(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
@@ -367,6 +514,8 @@ unsafe extern "C" fn handle_request_state(listener: *mut ffi::wl_listener, data:
             };
         }
     }
+
+    (*output.server).wm.dirty_windowing();
 }
 
 unsafe extern "C" fn handle_frame(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
diff --git a/src/output_manager.rs b/src/output_manager.rs
index 7db4d3f..4ac5ac3 100644
--- a/src/output_manager.rs
+++ b/src/output_manager.rs
@@ -284,18 +284,20 @@ impl OutputManager {
                     continue;
                 }
 
-                let mut state = std::mem::zeroed();
-                ffi::wlr_output_state_init(&mut state);
-                output.sent.apply_modeset(&mut state);
-
                 states_vec.push(ffi::wlr_backend_output_state {
                     output: wlr_output,
-                    base: state,
+                    base: std::mem::zeroed(),
                 });
 
                 link = (*link).next;
             }
 
+            for state in &mut states_vec {
+                ffi::wlr_output_state_init(&mut state.base);
+                let output = &mut *(ffi::river_wlr_output_get_data(state.output) as *mut Output);
+                output.sent.apply_modeset(&mut state.base);
+            }
+
             let mut swapchain_manager = std::mem::zeroed();
             ffi::wlr_output_swapchain_manager_init(&mut swapchain_manager, (*server).backend);
 
@@ -338,7 +340,7 @@ impl OutputManager {
             self.first_modeset = false;
             ffi::wlr_output_swapchain_manager_apply(&mut swapchain_manager);
 
-            for mut state in states_vec {
+            for state in &mut states_vec {
                 ffi::wlr_output_state_finish(&mut state.base);
             }
             ffi::wlr_output_swapchain_manager_finish(&mut swapchain_manager);
diff --git a/src/seat.rs b/src/seat.rs
index 63e179d..f80d945 100644
--- a/src/seat.rs
+++ b/src/seat.rs
@@ -78,6 +78,7 @@ pub struct Seat {
     pub link_sent: ffi::wl_list,
     pub object: *mut ffi::wl_resource,
     pub destroying: bool,
+    pub focus_requested: bool,
 }
 
 impl Seat {
@@ -115,6 +116,7 @@ impl Seat {
             link_sent: std::mem::zeroed(),
             object: std::ptr::null_mut(),
             destroying: false,
+            focus_requested: false,
         }));
 
         ffi::wl_list_init(&mut (*seat).link);
@@ -502,6 +504,7 @@ impl Seat {
             return;
         }
 
+        self.focus_requested = false;
         self.layer_shell.manage_start();
 
         let wm_v1 = (*self.server).wm.object;
@@ -524,7 +527,7 @@ impl Seat {
                     Some(handle_destroy_resource),
                 );
                 
-                ffi::wl_resource_post_event(wm_v1, 8, seat_v1); // river_window_manager_v1.seat
+                ffi::wl_resource_post_event(wm_v1, ffi::RIVER_WINDOW_MANAGER_V1_SEAT, seat_v1); // river_window_manager_v1.seat
 
                 crate::server::wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
                 let sent_seats = &mut (*self.server).wm.sent.seats as *mut ffi::wl_list as *mut crate::server::WlList;
@@ -620,6 +623,33 @@ impl Seat {
 
     pub unsafe fn manage_finish(&mut self) {
         self.xkb_bindings_seat.manage_finish();
+
+        if (*self.server).lock_manager.state != crate::lock_manager::LockState::Unlocked {
+            return;
+        }
+
+        match self.layer_shell.sent_focus {
+            crate::layer_shell::LayerShellSeatFocus::Exclusive(key) => {
+                let server = self.server;
+                if let Some(&layer_surface) = (*server).layer_shell.surfaces.get(key) {
+                    let wlr_surf = (*(*layer_surface).wlr_layer_surface).surface;
+                    self.focus(Focus::LayerSurface(wlr_surf));
+                }
+            }
+            crate::layer_shell::LayerShellSeatFocus::NonExclusive(key) => {
+                if !self.focus_requested {
+                    let server = self.server;
+                    if let Some(&layer_surface) = (*server).layer_shell.surfaces.get(key) {
+                        let wlr_surf = (*(*layer_surface).wlr_layer_surface).surface;
+                        self.focus(Focus::LayerSurface(wlr_surf));
+                    }
+                } else {
+                    self.layer_shell.scheduled_focus = crate::layer_shell::LayerShellSeatFocus::None;
+                    (*self.server).wm.dirty_windowing();
+                }
+            }
+            crate::layer_shell::LayerShellSeatFocus::None => {}
+        }
     }
 
     pub unsafe fn make_inert(&mut self) {
@@ -873,6 +903,7 @@ unsafe extern "C" fn seat_focus_window(
     if !(*(*seat).server).wm.ensure_windowing() {
         return;
     }
+    (*seat).focus_requested = true;
     if window_resource.is_null() {
         (*seat).focus(Focus::None);
         return;
@@ -895,6 +926,7 @@ unsafe extern "C" fn seat_focus_shell_surface(
     if !(*(*seat).server).wm.ensure_windowing() {
         return;
     }
+    (*seat).focus_requested = true;
     if shell_surface_resource.is_null() {
         (*seat).focus(Focus::None);
         return;
@@ -912,6 +944,7 @@ unsafe extern "C" fn seat_clear_focus(
     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
     if !seat.is_null() {
         if (*(*seat).server).wm.ensure_windowing() {
+            (*seat).focus_requested = true;
             (*seat).focus(Focus::None);
         }
     }
@@ -1031,6 +1064,9 @@ static SEAT_INTERFACE: ffi::river_seat_v1_interface = ffi::river_seat_v1_interfa
 unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
     if !seat.is_null() {
+        if (*seat).object != resource {
+            return;
+        }
         (*seat).object = std::ptr::null_mut();
     }
 }
diff --git a/src/server.rs b/src/server.rs
index f284aac..e701698 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -176,10 +176,20 @@ pub struct WlrXwayland {
 
 // Wayland list manipulation utilities
 pub unsafe fn wl_list_insert(list: *mut WlList, elm: *mut WlList) {
+    log::info!("wl_list_insert: list={:?}, elm={:?}", list, elm);
+    if list.is_null() {
+        log::error!("wl_list_insert: list is null!");
+        return;
+    }
+    if elm.is_null() {
+        log::error!("wl_list_insert: elm is null!");
+        return;
+    }
     (*elm).prev = list;
     (*elm).next = (*list).next;
     (*(*list).next).prev = elm;
     (*list).next = elm;
+    log::info!("wl_list_insert: done");
 }
 
 pub unsafe fn wl_list_remove(elm: *mut WlList) {
@@ -190,7 +200,13 @@ pub unsafe fn wl_list_remove(elm: *mut WlList) {
 }
 
 pub unsafe fn wl_signal_add(signal: *mut ffi::wl_signal, listener: *mut ffi::wl_listener) {
+    log::info!("wl_signal_add: signal={:?}, listener={:?}", signal, listener);
+    if signal.is_null() {
+        log::error!("wl_signal_add: signal is null!");
+        return;
+    }
     let sig_list = &mut (*signal).listener_list as *mut ffi::wl_list as *mut WlList;
+    log::info!("wl_signal_add: sig_list={:?}, prev={:?}, next={:?}", sig_list, (*sig_list).prev, (*sig_list).next);
     let listener_custom = listener as *mut WlListener;
     wl_list_insert((*sig_list).prev, &mut (*listener_custom).link);
 }
@@ -594,7 +610,7 @@ impl Server {
             let server_ptr = self as *mut Server;
             self.wm.init_with_server(server_ptr).map_err(|_| "Failed to init wm")?;
             self.xkb_bindings.init(server_ptr, self.wl_server).map_err(|_| "Failed to init xkb_bindings")?;
-            self.layer_shell.init(self.wl_server).map_err(|_| "Failed to init layer_shell")?;
+            self.layer_shell.init(server_ptr, self.wl_server).map_err(|_| "Failed to init layer_shell")?;
             self.scene.init(self.linux_dmabuf, self.color_manager).map_err(|_| "Failed to init scene")?;
             self.om.init(server_ptr).map_err(|_| "Failed to init om")?;
             self.input_manager.init(server_ptr).map_err(|_| "Failed to init input_manager")?;
diff --git a/src/window.rs b/src/window.rs
index 15ecd5c..41566e2 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -347,6 +347,8 @@ impl Window {
         ffi::wl_list_init(&mut window.decorations_above);
 
         let raw = Box::into_raw(window);
+        let key = (*(*raw).server).wm.windows.put(raw);
+        (*raw).ref_key = key;
         (*raw).node.init(crate::wm_node::WmNodeTag::Window);
 
         ffi::wlr_scene_node_set_enabled(tree as *mut ffi::wlr_scene_node, false);
@@ -580,6 +582,8 @@ impl Window {
 
         (*window).node.deinit();
 
+        (*(*window).server).wm.windows.remove((*window).ref_key);
+
         let _ = Box::from_raw(window);
     }
 
@@ -699,7 +703,7 @@ impl Window {
                     );
                     
                     // Send window to manager
-                    ffi::wl_resource_post_event(wm_v1, 0, res); // river_window_manager_v1.window
+                    ffi::wl_resource_post_event(wm_v1, ffi::RIVER_WINDOW_MANAGER_V1_WINDOW, res); // river_window_manager_v1.window
 
                     wl_list_remove(&mut self.node.link as *mut ffi::wl_list as *mut WlList);
                     let rendering_list = &mut (*self.server).wm.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
@@ -744,12 +748,12 @@ impl Window {
                 if new_resource {
                     let version = ffi::wl_resource_get_version(window_v1);
                     if version >= 2 {
-                        ffi::wl_resource_post_event(window_v1, 14, self.unreliable_pid()); // sendUnreliablePid
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_UNRELIABLE_PID, self.unreliable_pid()); // sendUnreliablePid
                     }
                     if version >= 4 {
                         if !self.foreign_toplevel_handle.is_null() {
                             let identifier = (*self.foreign_toplevel_handle).identifier;
-                            ffi::wl_resource_post_event(window_v1, 16, identifier);
+                            ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_IDENTIFIER, identifier);
                         }
                     }
                 }
@@ -757,7 +761,7 @@ impl Window {
                 if new_resource || self.wm_scheduled.dimensions_hint != self.wm_sent.dimensions_hint {
                     ffi::wl_resource_post_event(
                         window_v1,
-                        2, // sendDimensionsHint
+                        ffi::RIVER_WINDOW_V1_DIMENSIONS_HINT, // sendDimensionsHint
                         self.wm_scheduled.dimensions_hint.min_width as i32,
                         self.wm_scheduled.dimensions_hint.min_height as i32,
                         self.wm_scheduled.dimensions_hint.max_width as i32,
@@ -767,23 +771,35 @@ impl Window {
                 }
 
                 if new_resource || self.wm_scheduled.decoration_hint != self.wm_sent.decoration_hint {
-                    ffi::wl_resource_post_event(window_v1, 1, self.wm_scheduled.decoration_hint); // sendDecorationHint
+                    ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_DECORATION_HINT, self.wm_scheduled.decoration_hint); // sendDecorationHint
                     self.wm_sent.decoration_hint = self.wm_scheduled.decoration_hint;
                 }
 
                 if let Some(ref offset) = self.wm_scheduled.show_window_menu_requested {
-                    ffi::wl_resource_post_event(window_v1, 3, offset.x, offset.y); // sendShowWindowMenuRequested
+                    ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_SHOW_WINDOW_MENU_REQUESTED, offset.x, offset.y); // sendShowWindowMenuRequested
                     self.wm_scheduled.show_window_menu_requested = None;
                 }
 
                 match self.wm_scheduled.fullscreen_requested {
                     FullscreenRequest::NoRequest => {}
                     FullscreenRequest::Fullscreen(output) => {
-                        let out_resource = if output.is_null() { std::ptr::null_mut() } else { (*output).object };
-                        ffi::wl_resource_post_event(window_v1, 4, out_resource); // sendFullscreenRequested
+                        let mut out_resource = if output.is_null() { std::ptr::null_mut() } else { (*output).object };
+                        if !window_v1.is_null() && !out_resource.is_null() {
+                            let client_win = ffi::wl_resource_get_client(window_v1);
+                            let client_out = ffi::wl_resource_get_client(out_resource);
+                            if client_win != client_out {
+                                log::error!(
+                                    "Fullscreen output client mismatch: win_client={:?}, out_client={:?}. Fallback to null_mut",
+                                    client_win,
+                                    client_out
+                                );
+                                out_resource = std::ptr::null_mut();
+                            }
+                        }
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_FULLSCREEN_REQUESTED, out_resource); // sendFullscreenRequested
                     }
                     FullscreenRequest::Exit => {
-                        ffi::wl_resource_post_event(window_v1, 5); // sendExitFullscreenRequested
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_EXIT_FULLSCREEN_REQUESTED); // sendExitFullscreenRequested
                     }
                 }
                 self.wm_scheduled.fullscreen_requested = FullscreenRequest::NoRequest;
@@ -791,16 +807,16 @@ impl Window {
                 match self.wm_scheduled.maximize_requested {
                     MaximizeRequest::NoRequest => {}
                     MaximizeRequest::Maximize => {
-                        ffi::wl_resource_post_event(window_v1, 6); // sendMaximizeRequested
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_MAXIMIZE_REQUESTED); // sendMaximizeRequested
                     }
                     MaximizeRequest::Unmaximize => {
-                        ffi::wl_resource_post_event(window_v1, 7); // sendUnmaximizeRequested
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_UNMAXIMIZE_REQUESTED); // sendUnmaximizeRequested
                     }
                 }
                 self.wm_scheduled.maximize_requested = MaximizeRequest::NoRequest;
 
                 if self.wm_scheduled.minimize_requested {
-                    ffi::wl_resource_post_event(window_v1, 8); // sendMinimizeRequested
+                    ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_MINIMIZE_REQUESTED); // sendMinimizeRequested
                 }
                 self.wm_scheduled.minimize_requested = false;
 
@@ -809,36 +825,36 @@ impl Window {
                     let parent_ref = Some((*parent).ref_key);
                     if self.wm_sent.parent.is_none() || self.wm_sent.parent != parent_ref {
                         let parent_obj = (*parent).object;
-                        ffi::wl_resource_post_event(window_v1, 11, parent_obj); // sendParent
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_PARENT, parent_obj); // sendParent
                         self.wm_sent.parent = parent_ref;
                     }
                 } else if self.wm_sent.parent.is_some() {
-                    ffi::wl_resource_post_event(window_v1, 11, std::ptr::null_mut::<ffi::wl_resource>()); // sendParent
+                    ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_PARENT, std::ptr::null_mut::<ffi::wl_resource>()); // sendParent
                     self.wm_sent.parent = None;
                 }
 
                 if new_resource || self.wm_scheduled.dirty_app_id {
                     let app_id = self.get_app_id();
-                    ffi::wl_resource_post_event(window_v1, 13, app_id); // sendAppId
+                    ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_APP_ID, app_id); // sendAppId
                     self.wm_scheduled.dirty_app_id = false;
                 }
 
                 if new_resource || self.wm_scheduled.dirty_title {
                     let title = self.get_title();
-                    ffi::wl_resource_post_event(window_v1, 12, title); // sendTitle
+                    ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_TITLE, title); // sendTitle
                     self.wm_scheduled.dirty_title = false;
                 }
 
                 if let Some(seat) = self.wm_scheduled.pointer_move_requested.as_mut() {
                     if !seat.object.is_null() {
-                        ffi::wl_resource_post_event(window_v1, 9, seat.object); // sendPointerMoveRequested
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_POINTER_MOVE_REQUESTED, seat.object); // sendPointerMoveRequested
                     }
                 }
                 self.wm_scheduled.pointer_move_requested = std::ptr::null_mut();
 
                 if let Some(ref data) = self.wm_scheduled.pointer_resize_requested {
                     if !(*data.seat).object.is_null() {
-                        ffi::wl_resource_post_event(window_v1, 10, (*data.seat).object, data.edges); // sendPointerResizeRequested
+                        ffi::wl_resource_post_event(window_v1, ffi::RIVER_WINDOW_V1_POINTER_RESIZE_REQUESTED, (*data.seat).object, data.edges); // sendPointerResizeRequested
                     }
                 }
                 self.wm_scheduled.pointer_resize_requested = None;
@@ -848,7 +864,7 @@ impl Window {
 
     pub unsafe fn make_inert(&mut self) {
         if !self.object.is_null() {
-            ffi::wl_resource_post_event(self.object, 15); // sendClosed
+            ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_V1_CLOSED); // sendClosed
             ffi::wl_resource_set_implementation(
                 self.object,
                 &INERT_WINDOW_INTERFACE as *const _ as *const _,
@@ -1022,7 +1038,7 @@ impl Window {
            (scheduled.resend_dimensions ||
             scheduled.width != sent.width || scheduled.height != sent.height) {
             if !self.object.is_null() {
-                ffi::wl_resource_post_event(self.object, 0, scheduled.width as i32, scheduled.height as i32); // sendDimensions
+                ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_V1_DIMENSIONS, scheduled.width as i32, scheduled.height as i32); // sendDimensions
                 scheduled.resend_dimensions = false;
             }
         }
@@ -1032,7 +1048,7 @@ impl Window {
             if !self.object.is_null() {
                 let version = ffi::wl_resource_get_version(self.object);
                 if version >= 4 {
-                    ffi::wl_resource_post_event(self.object, 17, presentation_hint); // sendPresentationHint
+                    ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_V1_PRESENTATION_HINT, presentation_hint); // sendPresentationHint
                 }
             }
             sent.presentation_hint = presentation_hint;
@@ -1794,6 +1810,9 @@ static INERT_WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_windo
 unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
     let window = ffi::wl_resource_get_user_data(resource) as *mut Window;
     if !window.is_null() {
+        if (*window).object != resource {
+            return;
+        }
         (*window).object = std::ptr::null_mut();
         (*window).node.make_inert();
         
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 1d08bbc..60700c8 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -224,9 +224,9 @@ impl WindowManager {
         if session_locked != self.sent.session_locked {
             if !self.object.is_null() {
                 if session_locked {
-                    ffi::wl_resource_post_event(self.object, 4); // river_window_manager_v1.session_locked
+                    ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_MANAGER_V1_SESSION_LOCKED);
                 } else {
-                    ffi::wl_resource_post_event(self.object, 5); // river_window_manager_v1.session_unlocked
+                    ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_MANAGER_V1_SESSION_UNLOCKED);
                 }
             }
             self.sent.session_locked = session_locked;
@@ -239,7 +239,7 @@ impl WindowManager {
         while curr != outputs {
             let next = (*curr).next;
             let output = crate::container_of!(curr, crate::output::Output, link);
-            (*output).layer_shell.manage_start(output);
+            (*output).manage_start();
             curr = next;
         }
 
@@ -261,7 +261,7 @@ impl WindowManager {
         }
 
         if !self.object.is_null() {
-            ffi::wl_resource_post_event(self.object, 2); // river_window_manager_v1.manage_start
+            ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_MANAGER_V1_MANAGE_START);
             self.start_timeout_timer(3000);
         } else {
             self.manage_finish();
@@ -358,7 +358,7 @@ impl WindowManager {
         }
 
         if !self.object.is_null() {
-            ffi::wl_resource_post_event(self.object, 3); // river_window_manager_v1.render_start
+            ffi::wl_resource_post_event(self.object, ffi::RIVER_WINDOW_MANAGER_V1_RENDER_START);
             self.start_timeout_timer(3000);
         } else {
             self.render_finish();
@@ -543,7 +543,7 @@ unsafe extern "C" fn wm_stop(client: *mut ffi::wl_client, resource: *mut ffi::wl
     let wm = ffi::wl_resource_get_user_data(resource) as *mut WindowManager;
     if !wm.is_null() {
         (*wm).object = std::ptr::null_mut();
-        ffi::wl_resource_post_event(resource, 1); // river_window_manager_v1.finished (opcode 1)
+        ffi::wl_resource_post_event(resource, ffi::RIVER_WINDOW_MANAGER_V1_FINISHED);
         ffi::wl_resource_set_implementation(
             resource,
             &INERT_WM_INTERFACE as *const _ as *const _,
@@ -664,7 +664,7 @@ unsafe extern "C" fn bind(
     }
 
     if !(*wm).object.is_null() {
-        ffi::wl_resource_post_event(resource, 0); // river_window_manager_v1.unavailable
+        ffi::wl_resource_post_event(resource, ffi::RIVER_WINDOW_MANAGER_V1_UNAVAILABLE);
         ffi::wl_resource_set_implementation(
             resource,
             &INERT_WM_INTERFACE as *const _ as *const _,
@@ -689,6 +689,9 @@ unsafe extern "C" fn handle_destroy_wm_resource(resource: *mut ffi::wl_resource)
     if wm.is_null() {
         return;
     }
+    if (*wm).object != resource {
+        return;
+    }
     log::debug!("active river_window_manager_v1 destroyed");
     (*wm).object = std::ptr::null_mut();
 
@@ -711,6 +714,16 @@ unsafe extern "C" fn handle_destroy_wm_resource(resource: *mut ffi::wl_resource)
         let next = (*curr).next;
         let seat = crate::container_of!(curr, crate::seat::Seat, link);
         (*seat).make_inert();
+        
+        let bindings_head = &mut (*seat).xkb_bindings as *mut ffi::wl_list as *mut WlList;
+        let mut curr_b = (*bindings_head).next;
+        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;
+            curr_b = next_b;
+        }
+        
         curr = next;
     }
 
diff --git a/src/xkb_bindings.rs b/src/xkb_bindings.rs
index 1adc49b..a39689d 100644
--- a/src/xkb_bindings.rs
+++ b/src/xkb_bindings.rs
@@ -367,6 +367,11 @@ 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.seat).server).wm.dirty_windowing();
@@ -450,6 +455,23 @@ impl XkbBinding {
 unsafe extern "C" fn handle_binding_resource_destroy(resource: *mut ffi::wl_resource) {
     let binding = ffi::wl_resource_get_user_data(resource) as *mut XkbBinding;
     if !binding.is_null() {
+        let seat = (*binding).seat;
+        if !seat.is_null() {
+            let seat_groups_head = &mut (*seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
+            let mut curr_g = (*seat_groups_head).next;
+            while curr_g != seat_groups_head {
+                let next_g = (*curr_g).next;
+                let g = crate::container_of!(curr_g, crate::keyboard_group::KeyboardGroup, link);
+                for press in (*g).pressed.values_mut() {
+                    if let crate::keyboard_group::KeyConsumer::Binding(b) = press.consumer {
+                        if b == binding {
+                            press.consumer = crate::keyboard_group::KeyConsumer::Binding(std::ptr::null_mut());
+                        }
+                    }
+                }
+                curr_g = next_g;
+            }
+        }
         crate::server::wl_list_remove(&mut (*binding).link as *mut ffi::wl_list as *mut crate::server::WlList);
         let _ = Box::from_raw(binding);
     }
diff --git a/src/xwayland_window.rs b/src/xwayland_window.rs
index faad692..587e888 100644
--- a/src/xwayland_window.rs
+++ b/src/xwayland_window.rs
@@ -313,7 +313,7 @@ unsafe extern "C" fn handle_request_configure(listener: *mut ffi::wl_listener, d
 unsafe extern "C" fn handle_set_override_redirect(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
     let xwindow = crate::container_of!(listener, XwaylandWindow, set_override_redirect);
     let xsurface = (*xwindow).xsurface;
-    log::debug!("xwayland surface set override redirect");
+    log::info!("xwayland surface set override redirect: val={}", (*xsurface).override_redirect);
     assert!((*xsurface).override_redirect);
 
     let surface = (*xsurface).surface;
diff --git a/start-rust.sh b/start-rust.sh
new file mode 100755
index 0000000..d3638fb
--- /dev/null
+++ b/start-rust.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# Launch clear-river-rust with clearwm
+# Usage: ./start-rust.sh [--logging] [--debug]
+
+LOGGING=false
+DEBUG=false
+for arg in "$@"; do
+    case "$arg" in
+        --logging) LOGGING=true ;;
+        --debug) DEBUG=true ;;
+    esac
+done
+
+export XDG_RUNTIME_DIR=/run/user/$(id -u)
+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 clearwm launch script
+DEBUG_FLAG=""
+if [ "$DEBUG" = true ]; then
+    DEBUG_FLAG="WAYLAND_DEBUG=1 "
+fi
+
+cat > /tmp/clearwm-rs-launch-rust.sh << LAUNCH_EOF
+#!/bin/sh
+${DEBUG_FLAG}exec /home/lsgalante/.local/bin/clearwm 2>/tmp/clearwm-\${WAYLAND_DISPLAY}.log
+LAUNCH_EOF
+chmod +x /tmp/clearwm-rs-launch-rust.sh
+
+if [ "$LOGGING" = true ] || [ "$DEBUG" = true ]; then
+    echo "Starting clear-river-rust with clearwm..."
+    echo "  Logs: /tmp/river-clearwm-rust.log + /tmp/clearwm-\${WAYLAND_DISPLAY}.log"
+    if [ "$DEBUG" = true ]; then
+        echo "  Wayland debug logging enabled (WAYLAND_DEBUG=1)"
+    fi
+fi
+
+# Resolve script directory to reference target/debug/clear-river-rust reliably
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+if [ "$DEBUG" = true ]; then
+    export WAYLAND_DEBUG=1
+fi
+exec "$SCRIPT_DIR/target/release/clear-river-rust" -c /tmp/clearwm-rs-launch-rust.sh 2>/tmp/river-clearwm-rust.log
+
diff --git a/wrapper.h b/wrapper.h
index 35d94c7..412b77b 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -74,6 +74,7 @@
 #include <wlr/types/wlr_linux_drm_syncobj_v1.h>
 #include <wlr/types/wlr_scene.h>
 
+#include <wlr/util/addon.h>
 #include <wlr/xwayland.h>
 
 // Wlroots utils
@@ -81,7 +82,6 @@
 #include <wlr/util/edges.h>
 #include <wlr/util/box.h>
 #include <wlr/util/region.h>
-#include <wlr/util/addon.h>
 
 // System helper libraries
 #include <xkbcommon/xkbcommon.h>