Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Improve XWayland window and window manager node handling in server
src/window.rs | 125 +++++++++++++++++++++++++++++++++++++-----
src/window_manager.rs | 25 +++++----
src/wm_node.rs | 6 ++-
src/xdg_toplevel.rs | 5 +-
src/xwayland_window.rs | 144 +++++++++++++++++++++++++++++++++++++++++++------
start-rust.sh | 15 +++---
6 files changed, 270 insertions(+), 50 deletions(-)
diff --git a/src/window.rs b/src/window.rs
index 35d28f0..ed34b0f 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -152,7 +152,7 @@ impl Configure {
height: None,
bounds: Dimensions { width: 0, height: 0 },
activated: false,
- ssd: false,
+ ssd: true,
tiled: 0,
capabilities: 0,
maximized: false,
@@ -314,7 +314,7 @@ impl Window {
wm_requested: WmRequestedState {
dimensions: None,
bounds: Dimensions { width: 0, height: 0 },
- ssd: false,
+ ssd: true,
tiled: 0,
capabilities: 1 | 2 | 4 | 8,
resizing: false,
@@ -667,7 +667,7 @@ impl Window {
self.wm_requested = WmRequestedState {
dimensions: None,
bounds: Dimensions { width: 0, height: 0 },
- ssd: false,
+ ssd: true,
tiled: 0,
capabilities: 1 | 2 | 4 | 8,
resizing: false,
@@ -713,6 +713,7 @@ impl Window {
return;
}
self.object = res;
+ self.rendering_scheduled.resend_dimensions = true;
ffi::wl_resource_set_implementation(
res,
&WINDOW_INTERFACE as *const _ as *const _,
@@ -1041,9 +1042,20 @@ impl Window {
}
WindowImpl::Xwayland(xwindow) => {
if !xwindow.is_null() {
- let scale = crate::xwayland_window::XwaylandWindow::get_scale(xwindow);
- self.rendering_scheduled.width = ((*(*xwindow).xsurface).width as f32 / scale).round() as u32;
- self.rendering_scheduled.height = ((*(*xwindow).xsurface).height as f32 / scale).round() as u32;
+ let mut w = (*(*xwindow).xsurface).width as u32;
+ let mut h = (*(*xwindow).xsurface).height as u32;
+ let class_ptr = (*(*xwindow).xsurface).class;
+ let class = if class_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(class_ptr).to_str().unwrap_or("") };
+ let title_ptr = (*(*xwindow).xsurface).title;
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ let is_wine = class.contains("steam_proton") || class.contains("wine") || class.contains("upc.exe") || title.contains("Ubisoft");
+ let has_parent = !(*(*xwindow).xsurface).parent.is_null();
+ if is_wine && !has_parent {
+ w = w.saturating_sub(32);
+ h = h.saturating_sub(32);
+ }
+ self.rendering_scheduled.width = w;
+ self.rendering_scheduled.height = h;
}
}
WindowImpl::Destroying => {}
@@ -1137,6 +1149,17 @@ impl Window {
let requested = &self.rendering_requested;
let enabled = !requested.hidden && (matches!(self.state, WindowState::Mapped) || matches!(self.state, WindowState::Closing));
+ let title_ptr = match self.impl_type {
+ WindowImpl::Xwayland(xwindow) => {
+ if xwindow.is_null() { std::ptr::null() } else { (*(*xwindow).xsurface).title }
+ }
+ _ => std::ptr::null(),
+ };
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ if title.contains("Ubisoft") {
+ log::info!("render_finish for '{}' (addr={:p}): enabled={} hidden={} state={:?}", title, self as *const Window, enabled, requested.hidden, self.state);
+ }
+
ffi::wlr_scene_node_set_enabled(self.tree as *mut ffi::wlr_scene_node, enabled);
ffi::wlr_scene_node_set_enabled(self.popup_tree as *mut ffi::wlr_scene_node, enabled);
@@ -1203,6 +1226,18 @@ impl Window {
ffi::wlr_scene_node_set_position(self.tree as *mut ffi::wlr_scene_node, self.box_geom.x, self.box_geom.y);
ffi::wlr_scene_node_set_position(self.popup_tree as *mut ffi::wlr_scene_node, self.box_geom.x, self.box_geom.y);
+ let (geom_x, geom_y) = match self.impl_type {
+ WindowImpl::Toplevel(toplevel) => {
+ if toplevel.is_null() {
+ (0, 0)
+ } else {
+ ((*toplevel).geometry.x, (*toplevel).geometry.y)
+ }
+ }
+ _ => (0, 0),
+ };
+ ffi::wlr_scene_node_set_position(self.surfaces.tree as *mut ffi::wlr_scene_node, -geom_x, -geom_y);
+
self.apply_surface_clip(&clip, &content_clip);
for decorations in [&mut self.decorations_above as *mut ffi::wl_list, &mut self.decorations_below as *mut ffi::wl_list] {
@@ -1219,6 +1254,19 @@ impl Window {
match self.impl_type {
WindowImpl::Xwayland(xwindow) => {
if !xwindow.is_null() {
+ if !(*xwindow).surface_tree.is_null() {
+ let class_ptr = (*(*xwindow).xsurface).class;
+ let class = if class_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(class_ptr).to_str().unwrap_or("") };
+ let title_ptr = (*(*xwindow).xsurface).title;
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ let is_wine = class.contains("steam_proton") || class.contains("wine") || class.contains("upc.exe") || title.contains("Ubisoft");
+ let has_parent = !(*(*xwindow).xsurface).parent.is_null();
+ if is_wine && !has_parent {
+ ffi::wlr_scene_node_set_position((*xwindow).surface_tree as *mut ffi::wlr_scene_node, -16, -16);
+ } else {
+ ffi::wlr_scene_node_set_position((*xwindow).surface_tree as *mut ffi::wlr_scene_node, 0, 0);
+ }
+ }
(*xwindow).configure();
}
}
@@ -1315,18 +1363,45 @@ impl Window {
let a_empty = (*a).width == 0 && (*a).height == 0;
let b_empty = (*b).width == 0 && (*b).height == 0;
+ let layout_box = ffi::wlr_box {
+ x: 0,
+ y: 0,
+ width: self.box_geom.width,
+ height: self.box_geom.height,
+ };
+
if !a_empty && !b_empty {
- if !ffi::wlr_box_intersection(&mut surface_clip, a, b) {
+ let mut temp_clip = std::mem::zeroed::<ffi::wlr_box>();
+ if !ffi::wlr_box_intersection(&mut temp_clip, a, b) {
+ self.surfaces.set_enabled(false);
+ return;
+ }
+ if !ffi::wlr_box_intersection(&mut surface_clip, &temp_clip, &layout_box) {
self.surfaces.set_enabled(false);
return;
}
} else if !a_empty {
- surface_clip = *a;
+ if !ffi::wlr_box_intersection(&mut surface_clip, a, &layout_box) {
+ self.surfaces.set_enabled(false);
+ return;
+ }
+ } else if !b_empty {
+ if !ffi::wlr_box_intersection(&mut surface_clip, b, &layout_box) {
+ self.surfaces.set_enabled(false);
+ return;
+ }
} else {
- surface_clip = *b;
+ surface_clip = layout_box;
}
self.surfaces.set_enabled(true);
+
+ let margin = 4;
+ surface_clip.x -= margin;
+ surface_clip.y -= margin;
+ surface_clip.width += 2 * margin;
+ surface_clip.height += 2 * margin;
+
match self.impl_type {
WindowImpl::Toplevel(toplevel) => {
if !toplevel.is_null() {
@@ -1334,6 +1409,26 @@ impl Window {
surface_clip.y += (*toplevel).geometry.y;
}
}
+ WindowImpl::Xwayland(xwindow) => {
+ if !xwindow.is_null() {
+ let title_ptr = (*(*xwindow).xsurface).title;
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ if title.contains("Ubisoft") {
+ log::info!(
+ "XWayland window clip check: title='{}' box_geom=({}, {}, {}, {}) xsurface=({}, {}, {}, {})",
+ title,
+ self.box_geom.x,
+ self.box_geom.y,
+ self.box_geom.width,
+ self.box_geom.height,
+ (*(*xwindow).xsurface).x,
+ (*(*xwindow).xsurface).y,
+ (*(*xwindow).xsurface).width,
+ (*(*xwindow).xsurface).height,
+ );
+ }
+ }
+ }
_ => {}
}
@@ -1406,10 +1501,12 @@ unsafe extern "C" fn window_propose_dimensions(
);
return;
}
- (*window).wm_requested.dimensions = Some(Dimensions {
- width: width as u32,
- height: height as u32,
- });
+ if (*window).get_parent().is_null() {
+ (*window).wm_requested.dimensions = Some(Dimensions {
+ width: width as u32,
+ height: height as u32,
+ });
+ }
}
unsafe extern "C" fn window_hide(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
@@ -1446,6 +1543,7 @@ unsafe extern "C" fn window_use_csd(client: *mut ffi::wl_client, resource: *mut
return;
}
(*window).wm_requested.ssd = false;
+ (*server).wm.dirty_windowing();
}
unsafe extern "C" fn window_use_ssd(client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
@@ -1458,6 +1556,7 @@ unsafe extern "C" fn window_use_ssd(client: *mut ffi::wl_client, resource: *mut
return;
}
(*window).wm_requested.ssd = true;
+ (*server).wm.dirty_windowing();
}
unsafe extern "C" fn window_set_borders(
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 3202913..e407465 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -420,17 +420,22 @@ impl WindowManager {
crate::wm_node::WmNodeType::Window(window) => {
(*window).render_finish();
if reorder {
- ffi::wlr_scene_node_reparent((*window).popup_tree as *mut _, (*self.server).scene.layers.popups);
- if rendered_fullscreen(window) {
- ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.fullscreen);
- ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
- found_fullscreen = true;
- } else if (*window).rendering_requested.circular {
- ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.top);
- ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ if (*window).rendering_requested.hidden {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.hidden_tree);
+ ffi::wlr_scene_node_reparent((*window).popup_tree as *mut _, (*self.server).scene.hidden_tree);
} else {
- ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.wm);
- ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ ffi::wlr_scene_node_reparent((*window).popup_tree as *mut _, (*self.server).scene.layers.popups);
+ if rendered_fullscreen(window) {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.fullscreen);
+ ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ found_fullscreen = true;
+ } else if (*window).rendering_requested.circular {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.top);
+ ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ } else {
+ ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.layers.wm);
+ ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
+ }
}
}
}
diff --git a/src/wm_node.rs b/src/wm_node.rs
index 4faa91e..d7af93d 100644
--- a/src/wm_node.rs
+++ b/src/wm_node.rs
@@ -115,8 +115,10 @@ unsafe extern "C" fn node_set_position(
match (*node).get() {
WmNodeType::Window(w) => {
- (*w).rendering_requested.x = x;
- (*w).rendering_requested.y = y;
+ if (*w).get_parent().is_null() {
+ (*w).rendering_requested.x = x;
+ (*w).rendering_requested.y = y;
+ }
}
WmNodeType::ShellSurface(s) => {
(*s).rendering_requested.x = x;
diff --git a/src/xdg_toplevel.rs b/src/xdg_toplevel.rs
index de98fa6..222e6c3 100644
--- a/src/xdg_toplevel.rs
+++ b/src/xdg_toplevel.rs
@@ -403,7 +403,10 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
let window = (*toplevel).window;
let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
- ffi::river_scene_node_enable_blur((*window).surfaces.tree as *mut ffi::wlr_scene_node, true);
+ ffi::river_scene_node_enable_blur(
+ (*window).surfaces.tree as *mut ffi::wlr_scene_node,
+ (*window).rendering_requested.blur,
+ );
let capture_node = &mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node;
let mut geom = std::mem::zeroed();
diff --git a/src/xwayland_window.rs b/src/xwayland_window.rs
index 8833ae9..7df3d6e 100644
--- a/src/xwayland_window.rs
+++ b/src/xwayland_window.rs
@@ -154,20 +154,38 @@ impl XwaylandWindow {
scheduled.height = Some((*self.xsurface).height as u32);
}
- let phys_width = if let Some(w) = scheduled.width {
- (w as f32 * scale).round() as u16
+ let mut phys_width = if let Some(w) = scheduled.width {
+ w as u16
} else {
(*self.xsurface).width
};
- let phys_height = if let Some(h) = scheduled.height {
- (h as f32 * scale).round() as u16
+ let mut phys_height = if let Some(h) = scheduled.height {
+ h as u16
} else {
(*self.xsurface).height
};
- let phys_x = ((*window).box_geom.x as f32 * scale).round() as i16;
- let phys_y = ((*window).box_geom.y as f32 * scale).round() as i16;
+ let mut phys_x = ((*window).box_geom.x as f32 * scale).round() as i16;
+ let mut phys_y = ((*window).box_geom.y as f32 * scale).round() as i16;
+
+ let class_ptr = (*self.xsurface).class;
+ let class = if class_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(class_ptr).to_str().unwrap_or("") };
+ let title_ptr = (*self.xsurface).title;
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ let is_wine = class.contains("steam_proton") || class.contains("wine") || class.contains("upc.exe") || title.contains("Ubisoft");
+ let has_parent = !(*self.xsurface).parent.is_null();
+
+ if is_wine && !has_parent {
+ if scheduled.width.is_some() {
+ phys_width += 32;
+ }
+ if scheduled.height.is_some() {
+ phys_height += 32;
+ }
+ phys_x -= (16.0 * scale).round() as i16;
+ phys_y -= (16.0 * scale).round() as i16;
+ }
if phys_x != (*self.xsurface).x
|| phys_y != (*self.xsurface).y
@@ -193,8 +211,17 @@ impl XwaylandWindow {
ffi::wlr_xwayland_surface_set_fullscreen(self.xsurface, scheduled.inform_fullscreen);
}
- let width = scheduled.width.unwrap_or(((*self.xsurface).width as f32 / scale).round() as u32);
- let height = scheduled.height.unwrap_or(((*self.xsurface).height as f32 / scale).round() as u32);
+ let mut width = scheduled.width.unwrap_or((*self.xsurface).width as u32);
+ let mut height = scheduled.height.unwrap_or((*self.xsurface).height as u32);
+
+ if is_wine && !has_parent {
+ if scheduled.width.is_none() {
+ width = width.saturating_sub(32);
+ }
+ if scheduled.height.is_none() {
+ height = height.saturating_sub(32);
+ }
+ }
(*window).configure_sent = (*window).configure_scheduled.clone();
(*window).configure_sent.width = Some(width);
@@ -289,6 +316,18 @@ unsafe fn handle_map_impl(xwindow: *mut XwaylandWindow) {
return;
}
(*xwindow).surface_tree = surface_tree;
+
+ let class_ptr = (*(*xwindow).xsurface).class;
+ let class = if class_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(class_ptr).to_str().unwrap_or("") };
+ let title_ptr = (*(*xwindow).xsurface).title;
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ let is_wine = class.contains("steam_proton") || class.contains("wine") || class.contains("upc.exe") || title.contains("Ubisoft");
+ let has_parent = !(*(*xwindow).xsurface).parent.is_null();
+
+ if is_wine && !has_parent {
+ ffi::wlr_scene_node_set_position(surface_tree as *mut ffi::wlr_scene_node, -16, -16);
+ }
+
ffi::river_wlr_surface_set_data(surface, &mut (*(*xwindow).window).node as *mut crate::wm_node::WmNode as *mut _);
let capture_tree = &mut (*(*(*xwindow).window).capture_scene).tree as *mut ffi::wlr_scene_tree;
@@ -343,19 +382,94 @@ unsafe extern "C" fn handle_request_configure(listener: *mut ffi::wl_listener, d
}
let scale = XwaylandWindow::get_scale(xwindow);
- let phys_x = ((*(*xwindow).window).box_geom.x as f32 * scale).round() as i16;
- let phys_y = ((*(*xwindow).window).box_geom.y as f32 * scale).round() as i16;
+
+ let class_ptr = (*(*xwindow).xsurface).class;
+ let class = if class_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(class_ptr).to_str().unwrap_or("") };
+ let title_ptr = (*(*xwindow).xsurface).title;
+ let title = if title_ptr.is_null() { "" } else { std::ffi::CStr::from_ptr(title_ptr).to_str().unwrap_or("") };
+ let is_wine = class.contains("steam_proton") || class.contains("wine") || class.contains("upc.exe") || title.contains("Ubisoft");
+
+ let has_parent = !(*(*xwindow).xsurface).parent.is_null();
+ log::info!(
+ "XWayland configure request: title='{}' class='{}' has_parent={} is_wine={} event=({}, {}, {}, {}) xsurface=({}, {}, {}, {}) scale={}",
+ title,
+ class,
+ has_parent,
+ is_wine,
+ (*event).x, (*event).y, (*event).width, (*event).height,
+ (*(*xwindow).xsurface).x, (*(*xwindow).xsurface).y, (*(*xwindow).xsurface).width, (*(*xwindow).xsurface).height,
+ scale
+ );
+
+ if has_parent {
+ ffi::wlr_xwayland_surface_configure(
+ (*xwindow).xsurface,
+ (*event).x,
+ (*event).y,
+ (*event).width,
+ (*event).height,
+ );
+ let log_x = ((*event).x as f32 / scale).round() as i32;
+ let log_y = ((*event).y as f32 / scale).round() as i32;
+ let log_width = (*event).width as u32;
+ let log_height = (*event).height as u32;
+
+ let window = (*xwindow).window;
+ (*window).box_geom.x = log_x;
+ (*window).box_geom.y = log_y;
+ (*window).box_geom.width = log_width as i32;
+ (*window).box_geom.height = log_height as i32;
+ (*window).rendering_requested.x = log_x;
+ (*window).rendering_requested.y = log_y;
+ (*window).rendering_sent.width = log_width;
+ (*window).rendering_sent.height = log_height;
+ (*window).set_dimensions(log_width, log_height);
+ return;
+ }
+
+ let window = (*xwindow).window;
+ let is_tiled = unsafe { (*window).wm_requested.tiled != 0 };
+
+ let (phys_width, phys_height) = if is_tiled {
+ let log_w = (*window).configure_sent.width.unwrap_or((*window).box_geom.width as u32);
+ let log_h = (*window).configure_sent.height.unwrap_or((*window).box_geom.height as u32);
+ if log_w > 0 && log_h > 0 {
+ let mut w = log_w;
+ let mut h = log_h;
+ if is_wine && !has_parent {
+ w += 32;
+ h += 32;
+ }
+ (w as u16, h as u16)
+ } else {
+ ((*event).width, (*event).height)
+ }
+ } else {
+ ((*event).width, (*event).height)
+ };
+
+ let mut phys_x = ((*window).box_geom.x as f32 * scale).round() as i16;
+ let mut phys_y = ((*window).box_geom.y as f32 * scale).round() as i16;
+
+ if is_wine && !has_parent {
+ phys_x -= (16.0 * scale).round() as i16;
+ phys_y -= (16.0 * scale).round() as i16;
+ }
ffi::wlr_xwayland_surface_configure(
(*xwindow).xsurface,
phys_x,
phys_y,
- (*event).width,
- (*event).height,
+ phys_width,
+ phys_height,
);
- let log_width = ((*event).width as f32 / scale).round() as u32;
- let log_height = ((*event).height as f32 / scale).round() as u32;
- (*(*xwindow).window).set_dimensions(log_width, log_height);
+ let mut log_width = phys_width as u32;
+ let mut log_height = phys_height as u32;
+ if is_wine && !has_parent {
+ log_width = log_width.saturating_sub(32);
+ log_height = log_height.saturating_sub(32);
+ }
+ (*window).set_dimensions(log_width, log_height);
}
unsafe extern "C" fn handle_set_override_redirect(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
diff --git a/start-rust.sh b/start-rust.sh
index 398ee3a..84bff38 100755
--- a/start-rust.sh
+++ b/start-rust.sh
@@ -29,17 +29,14 @@ ${DEBUG_FLAG}exec /home/lsgalante/.local/bin/cce-client 2>/tmp/cce-client-\${WAY
LAUNCH_EOF
chmod +x /tmp/cce-client-launch-rust.sh
-if [ "$LOGGING" = true ] || [ "$DEBUG" = true ]; then
- echo "Starting cce-server with cce-client..."
- echo " Logs: /tmp/river-cce-client.log + /tmp/cce-client-\${WAYLAND_DISPLAY}.log"
- if [ "$DEBUG" = true ]; then
- echo " Wayland debug logging enabled (WAYLAND_DEBUG=1)"
- fi
-fi
-
# Resolve script directory to reference target/release/cce-server reliably
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ "$DEBUG" = true ]; then
export WAYLAND_DEBUG=1
fi
-exec "$SCRIPT_DIR/target/release/cce-server" -c /tmp/cce-client-launch-rust.sh 2>/tmp/river-cce-client.log
+
+if [ "$LOGGING" = true ] || [ "$DEBUG" = true ]; then
+ exec "$SCRIPT_DIR/target/release/cce-server" -c /tmp/cce-client-launch-rust.sh 2>/tmp/river-cce-client.log
+else
+ exec "$SCRIPT_DIR/target/release/cce-server" --log-level error -c /tmp/cce-client-launch-rust.sh 2>/dev/null
+fi