Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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) {