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