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

commit68fb1ed5f72790687aaa6ed1506014423fa1b6b2
parent993f18bb9e
authorLucas Galante <[email protected]>
date2026-06-02 13:25
Update system configuration and interface modules

 protocol/clear-inspector-v1.xml         |  6 ++-
 protocol/river-window-management-v1.xml |  9 ++++
 src/cursor.rs                           | 11 ++++-
 src/inspector.rs                        | 79 +++++++++++++++++++++++++--------
 src/scene.rs                            | 73 +++++++++++++++++++++++-------
 src/server.rs                           | 64 ++++++++++++++++++++++++--
 src/window.rs                           | 56 +++++++++++++++++++++++
 7 files changed, 256 insertions(+), 42 deletions(-)

diff --git a/protocol/clear-inspector-v1.xml b/protocol/clear-inspector-v1.xml
index b321965..4dca611 100644
--- a/protocol/clear-inspector-v1.xml
+++ b/protocol/clear-inspector-v1.xml
@@ -27,7 +27,8 @@
         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"/>
+      <arg name="fd" type="fd" summary="JSON state file descriptor"/>
+      <arg name="len" type="uint" summary="JSON state length in bytes"/>
     </request>
 
     <request name="get_inspected_surfaces">
@@ -42,7 +43,8 @@
       <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"/>
+      <arg name="fd" type="fd" summary="JSON state file descriptor"/>
+      <arg name="len" type="uint" summary="JSON state length in bytes"/>
     </event>
 
     <event name="inspected_surface_done">
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 32d6db3..60040b2 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -1130,6 +1130,15 @@
       </description>
       <arg name="opacity" type="uint" summary="opacity value from 0 to 0xffffffff"/>
     </request>
+
+    <request name="set_circular" since="4">
+      <description summary="set whether the window is circular">
+        Set whether the window is circular (1 for circular, 0 for rectangular).
+        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="circular" type="uint" summary="1 if circular, 0 otherwise"/>
+    </request>
   </interface>
 
   <interface name="river_decoration_v1" version="4">
diff --git a/src/cursor.rs b/src/cursor.rs
index ab25112..331c390 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -516,7 +516,12 @@ unsafe extern "C" fn handle_motion_absolute(listener: *mut ffi::wl_listener, dat
     let cursor = &mut *crate::container_of!(listener, Cursor, motion_absolute_listener);
     let event = data as *mut ffi::wlr_pointer_motion_absolute_event;
     
-    ffi::wlr_cursor_warp_absolute(cursor.wlr_cursor, std::ptr::null_mut(), (*event).x, (*event).y);
+    let wlr_device = if (*event).pointer.is_null() {
+        std::ptr::null_mut()
+    } else {
+        &mut (*(*event).pointer).base as *mut ffi::wlr_input_device
+    };
+    ffi::wlr_cursor_warp_absolute(cursor.wlr_cursor, wlr_device, (*event).x, (*event).y);
     cursor.update_hovered();
     cursor.update_drag_icons();
 
@@ -566,6 +571,10 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
             match result.data {
                 SceneNodeDataVal::Window(window) => {
                     seat.focus(Focus::Window(window));
+                    if !seat.object.is_null() && !(*window).object.is_null() {
+                        ffi::wl_resource_post_event(seat.object, 4, (*window).object);
+                        (*(*seat).server).wm.dirty_windowing();
+                    }
                 }
                 SceneNodeDataVal::LayerSurface(_) => {
                     seat.focus(Focus::LayerSurface(result.surface));
diff --git a/src/inspector.rs b/src/inspector.rs
index 76a7963..71171d1 100644
--- a/src/inspector.rs
+++ b/src/inspector.rs
@@ -102,23 +102,62 @@ unsafe extern "C" fn inspector_register_client(
     (*inspector).client_states.entry(surface).or_insert_with(String::new);
 }
 
+unsafe fn create_memfd_with_data(name: &str, data: &[u8]) -> Option<(std::os::raw::c_int, u32)> {
+    let c_name = std::ffi::CString::new(name).ok()?;
+    let fd = libc::memfd_create(c_name.as_ptr(), libc::MFD_CLOEXEC);
+    if fd < 0 {
+        return None;
+    }
+    let mut written = 0;
+    while written < data.len() {
+        let res = libc::write(
+            fd,
+            data.as_ptr().add(written) as *const _,
+            data.len() - written,
+        );
+        if res < 0 {
+            let err = *libc::__errno_location();
+            if err == libc::EINTR {
+                continue;
+            }
+            libc::close(fd);
+            return None;
+        }
+        written += res as usize;
+    }
+    libc::lseek(fd, 0, libc::SEEK_SET);
+    Some((fd, data.len() as u32))
+}
+
 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,
+    fd: std::os::raw::c_int,
+    len: u32,
 ) {
     let inspector = ffi::wl_resource_get_user_data(resource) as *mut Inspector;
     if inspector.is_null() {
+        if fd >= 0 {
+            libc::close(fd);
+        }
         return;
     }
     let surface = ffi::wlr_surface_from_resource(surface_resource);
     if surface.is_null() {
+        if fd >= 0 {
+            libc::close(fd);
+        }
         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);
+    if fd >= 0 {
+        use std::os::unix::io::FromRawFd;
+        let file = unsafe { std::fs::File::from_raw_fd(fd) };
+        let mut state_str = String::with_capacity(len as usize);
+        use std::io::Read;
+        if file.take(len as u64).read_to_string(&mut state_str).is_ok() {
+            (*inspector).client_states.insert(surface, state_str);
+        }
     }
 }
 
@@ -164,21 +203,23 @@ unsafe extern "C" fn inspector_get_inspected_surfaces(
             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(),
-        );
+        if let Some((fd, len)) = create_memfd_with_data("clear_ui_inspected_state", state.as_bytes()) {
+            // Send: inspected_surface(title, app_id, x, y, width, height, fd, len)
+            // 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,
+                fd,
+                len,
+            );
+            libc::close(fd);
+        }
     }
 
     // Send: inspected_surface_done()
diff --git a/src/scene.rs b/src/scene.rs
index 3f98d19..f9d225f 100644
--- a/src/scene.rs
+++ b/src/scene.rs
@@ -117,26 +117,65 @@ impl Scene {
     pub unsafe fn deinit(&mut self) {}
 
     pub unsafe fn at(&self, lx: f64, ly: f64) -> Option<AtResult> {
-        let mut sx: f64 = 0.0;
-        let mut sy: f64 = 0.0;
-        let node = ffi::wlr_scene_node_at(self.interactive_tree as *mut ffi::wlr_scene_node, lx, ly, &mut sx, &mut sy);
-        if node.is_null() {
-            return None;
+        let mut disabled_nodes = Vec::new();
+        let mut result = None;
+
+        loop {
+            let mut sx: f64 = 0.0;
+            let mut sy: f64 = 0.0;
+            let node = ffi::wlr_scene_node_at(
+                self.interactive_tree as *mut ffi::wlr_scene_node,
+                lx,
+                ly,
+                &mut sx,
+                &mut sy,
+            );
+
+            if node.is_null() {
+                break;
+            }
+
+            if let Some(scene_node_data) = SceneNodeData::from_node(node) {
+                if let SceneNodeDataVal::Window(window) = scene_node_data.data {
+                    if (*window).rendering_requested.circular {
+                        // Check if outside the circle
+                        let w = (*window).box_geom.width as f64;
+                        let h = (*window).box_geom.height as f64;
+                        let cx = (*window).box_geom.x as f64 + w / 2.0;
+                        let cy = (*window).box_geom.y as f64 + h / 2.0;
+                        let r = w.min(h) / 2.0;
+                        let dx = lx - cx;
+                        let dy = ly - cy;
+                        if dx * dx + dy * dy > r * r {
+                            // Outside the circle! Disable the window tree node temporarily and try again.
+                            let tree_node = (*window).tree as *mut ffi::wlr_scene_node;
+                            ffi::wlr_scene_node_set_enabled(tree_node, false);
+                            disabled_nodes.push(tree_node);
+                            continue;
+                        }
+                    }
+                }
+
+                let surface = ffi::river_scene_node_get_surface(node);
+                result = Some(AtResult {
+                    node,
+                    surface,
+                    sx,
+                    sy,
+                    data: scene_node_data.data,
+                });
+                break;
+            } else {
+                break;
+            }
         }
 
-        let surface = ffi::river_scene_node_get_surface(node);
-
-        if let Some(scene_node_data) = SceneNodeData::from_node(node) {
-            Some(AtResult {
-                node,
-                surface,
-                sx,
-                sy,
-                data: scene_node_data.data,
-            })
-        } else {
-            None
+        // Re-enable all disabled nodes
+        for node in disabled_nodes {
+            ffi::wlr_scene_node_set_enabled(node, true);
         }
+
+        result
     }
 
     pub unsafe fn layer_surface_tree(&self, layer: u32) -> *mut ffi::wlr_scene_tree {
diff --git a/src/server.rs b/src/server.rs
index f077b72..510173e 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -311,9 +311,67 @@ unsafe extern "C" fn handle_new_toplevel_decoration(listener: *mut ffi::wl_liste
     crate::xdg_toplevel::XdgDecoration::init(decoration);
 }
 
-unsafe extern "C" fn handle_request_activate(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
-    let _server = container_of!(listener, Server, request_activate);
-    log::info!("xdg activation request");
+unsafe extern "C" fn handle_request_activate(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
+    let server = container_of!(listener, Server, request_activate);
+    log::info!("xdg activation request received");
+    if data.is_null() {
+        return;
+    }
+    let event = data as *mut ffi::wlr_xdg_activation_v1_request_activate_event;
+    let surface = (*event).surface;
+    if surface.is_null() {
+        return;
+    }
+
+    let default_seat = (*server).input_manager.default_seat;
+    if !default_seat.is_null() {
+        let focused_surf = ffi::river_wlr_seat_get_keyboard_focused_surface((*default_seat).wlr_seat);
+        if focused_surf == surface {
+            log::info!("xdg activation request ignored: window is already focused");
+            return;
+        }
+    }
+
+    for &win_ptr in (*server).wm.windows.iter() {
+        if !win_ptr.is_null() && (*win_ptr).root_surface() == surface {
+            let title_ptr = (*win_ptr).get_title();
+            let title = if title_ptr.is_null() {
+                "Window".to_string()
+            } else {
+                std::ffi::CStr::from_ptr(title_ptr).to_string_lossy().into_owned()
+            };
+            
+            let app_id_ptr = (*win_ptr).get_app_id();
+            let app_id = if app_id_ptr.is_null() {
+                "unknown".to_string()
+            } else {
+                std::ffi::CStr::from_ptr(app_id_ptr).to_string_lossy().into_owned()
+            };
+            
+            log::info!("xdg activation request for window '{}' ({})", title, app_id);
+            
+            let uid = unsafe { libc::getuid() };
+            let bus_address = format!("unix:path=/run/user/{}/bus", uid);
+            
+            std::process::Command::new("gdbus")
+                .env("DBUS_SESSION_BUS_ADDRESS", &bus_address)
+                .args([
+                    "call",
+                    "--session",
+                    "--dest",
+                    "org.kde.StatusNotifierWatcher",
+                    "--object-path",
+                    "/StatusInterface",
+                    "--method",
+                    "org.clear.StatusInterface.NotifyAttention",
+                    &app_id,
+                    &title,
+                ])
+                .spawn()
+                .ok();
+            break;
+        }
+    }
 }
 
 unsafe extern "C" fn handle_request_set_cursor_shape(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
diff --git a/src/window.rs b/src/window.rs
index e08e5ae..c40ea0b 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -182,6 +182,7 @@ pub struct WindowRenderingRequested {
     pub clip: ffi::wlr_box,
     pub content_clip: ffi::wlr_box,
     pub opacity: f32,
+    pub circular: bool,
 }
 
 pub struct Window {
@@ -341,6 +342,7 @@ impl Window {
                 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,
+                circular: false,
             },
             box_geom: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
             foreign_toplevel_handle: std::ptr::null_mut(),
@@ -685,6 +687,7 @@ impl Window {
                     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,
+                    circular: false,
                 };
 
                 wl_list_remove(&mut self.node.link as *mut ffi::wl_list as *mut WlList);
@@ -1137,6 +1140,34 @@ 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);
+
+            let radius = if requested.circular {
+                let w = self.rendering_sent.width as i32;
+                let h = self.rendering_sent.height as i32;
+                w.min(h) / 2
+            } else {
+                0
+            };
+
+            unsafe extern "C" fn set_corner_radius_iterator(
+                buffer: *mut ffi::wlr_scene_buffer,
+                _sx: i32,
+                _sy: i32,
+                user_data: *mut std::ffi::c_void,
+            ) {
+                let radius = *(user_data as *const i32);
+                ffi::wlr_scene_buffer_set_corner_radius(
+                    buffer,
+                    radius,
+                    ffi::corner_location_CORNER_LOCATION_ALL,
+                );
+            }
+
+            ffi::wlr_scene_node_for_each_buffer(
+                self.surfaces.tree as *mut ffi::wlr_scene_node,
+                Some(set_corner_radius_iterator),
+                &radius as *const i32 as *mut std::ffi::c_void,
+            );
         }
 
         self.box_geom.width = self.rendering_sent.width as i32;
@@ -1194,6 +1225,13 @@ impl Window {
 
     pub unsafe fn draw_borders(&mut self) {
         let requested = &self.rendering_requested;
+        if requested.circular || requested.border.width <= 0 {
+            ffi::wlr_scene_node_set_enabled(self.border.left as *mut ffi::wlr_scene_node, false);
+            ffi::wlr_scene_node_set_enabled(self.border.right as *mut ffi::wlr_scene_node, false);
+            ffi::wlr_scene_node_set_enabled(self.border.top as *mut ffi::wlr_scene_node, false);
+            ffi::wlr_scene_node_set_enabled(self.border.bottom as *mut ffi::wlr_scene_node, false);
+            return;
+        }
         let content = ffi::wlr_box {
             x: 0,
             y: 0,
@@ -1787,6 +1825,22 @@ unsafe extern "C" fn window_set_opacity(
     (*window).rendering_requested.opacity = opacity_f32;
 }
 
+unsafe extern "C" fn window_set_circular(
+    client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+    circular: 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;
+    }
+    (*window).rendering_requested.circular = circular != 0;
+}
+
 // river_window_v1 implementation
 static WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_interface {
     destroy: Some(window_destroy),
@@ -1814,6 +1868,7 @@ static WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_i
     set_content_clip_box: Some(window_set_content_clip_box),
     set_dimension_bounds: Some(window_set_dimension_bounds),
     set_opacity: Some(window_set_opacity),
+    set_circular: Some(window_set_circular),
 };
 
 static INERT_WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_window_v1_interface {
@@ -1842,6 +1897,7 @@ static INERT_WINDOW_INTERFACE: ffi::river_window_v1_interface = ffi::river_windo
     set_content_clip_box: None,
     set_dimension_bounds: None,
     set_opacity: None,
+    set_circular: None,
 };
 
 unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {