From c4e3920c793250bc8ab77c9a61ba85b9fe18c82c Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Tue, 7 Apr 2026 19:15:52 +0200 Subject: [PATCH] Fix client surface rendering: add window.on_commit() and space.refresh() Root cause: Window::bbox() stayed at 0x0 because Window::on_commit() was never called after surface commits. The Space then never associated the window with the output (no overlap), so render_elements_for_output returned empty. Two fixes: - Call window.on_commit() in CompositorHandler::commit() to update the window's bounding box from the committed surface tree - Call space.refresh() each frame to update output-element mappings Also: send xdg_toplevel configure with suggested 800x600 size. --- crates/wrsrvd/src/backend/headless.rs | 24 +++--------------------- crates/wrsrvd/src/handlers/compositor.rs | 18 ++++++++++-------- crates/wrsrvd/src/handlers/xdg_shell.rs | 11 ++++++++--- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/crates/wrsrvd/src/backend/headless.rs b/crates/wrsrvd/src/backend/headless.rs index 138205c..e0fca40 100644 --- a/crates/wrsrvd/src/backend/headless.rs +++ b/crates/wrsrvd/src/backend/headless.rs @@ -232,19 +232,9 @@ fn render_headless_frame(data: &mut CalloopData) { } }; - let element_count = data.state.space.elements().count(); - tracing::debug!(element_count, "render tick"); - - // Check if window toplevel has been configured. - if element_count > 0 { - for window in data.state.space.elements() { - if let Some(toplevel) = window.toplevel() { - let configured = toplevel.is_initial_configure_sent(); - let bbox = window.bbox(); - tracing::info!(configured, ?bbox, "window state"); - } - } - } + // Refresh the space — updates output-to-element mappings. + // Must be called each frame before rendering. + data.state.space.refresh(); let custom_elements: &[TextureRenderElement] = &[]; @@ -279,14 +269,6 @@ fn render_headless_frame(data: &mut CalloopData) { std::slice::from_raw_parts(ptr, frame_bytes) }; - // One-shot content check for debugging. - if element_count > 0 { - let non_bg = pixels.chunks_exact(4).any(|p| p != [25, 25, 25, 255]); - if non_bg { - tracing::info!("client surface rendered successfully"); - } - } - // Send frame over network if a client is connected. if data.client_connected { send_frame_to_network(data, pixels, &damage, output_size.w, output_size.h, stride); diff --git a/crates/wrsrvd/src/handlers/compositor.rs b/crates/wrsrvd/src/handlers/compositor.rs index f3c8881..441acc2 100644 --- a/crates/wrsrvd/src/handlers/compositor.rs +++ b/crates/wrsrvd/src/handlers/compositor.rs @@ -8,7 +8,6 @@ use smithay::{ shm::{ShmHandler, ShmState}, }, }; -use tracing::trace; use crate::state::WayRay; @@ -40,21 +39,24 @@ impl CompositorHandler for WayRay { } fn commit(&mut self, surface: &WlSurface) { - trace!(?surface, "surface commit"); smithay::backend::renderer::utils::on_commit_buffer_handler::(surface); - // If this surface belongs to an xdg toplevel that hasn't received - // its initial configure yet, send it now so the client can start - // drawing. + // Find the window this surface belongs to and update its state. if let Some(window) = self .space .elements() .find(|w| w.wl_surface().map(|s| s.into_owned()) == Some(surface.clone())) .cloned() - && let Some(toplevel) = window.toplevel() - && !toplevel.is_initial_configure_sent() { - toplevel.send_configure(); + // Update the window's bounding box from the committed surface tree. + // Without this, the window stays at 0x0 and never gets rendered. + window.on_commit(); + + if let Some(toplevel) = window.toplevel() + && !toplevel.is_initial_configure_sent() + { + toplevel.send_configure(); + } } } } diff --git a/crates/wrsrvd/src/handlers/xdg_shell.rs b/crates/wrsrvd/src/handlers/xdg_shell.rs index 91f77c5..2abe226 100644 --- a/crates/wrsrvd/src/handlers/xdg_shell.rs +++ b/crates/wrsrvd/src/handlers/xdg_shell.rs @@ -21,11 +21,16 @@ impl XdgShellHandler for WayRay { } fn new_toplevel(&mut self, surface: ToplevelSurface) { - // Send the initial configure so the client can start drawing. + // Set a suggested size and send the initial configure so the + // client can start drawing. + surface.with_pending_state(|state| { + state.size = Some((800, 600).into()); + }); surface.send_configure(); + let window = Window::new_wayland_window(surface); - self.space.map_element(window, (0, 0), false); - info!("new toplevel mapped"); + self.space.map_element(window, (0, 0), true); + info!("new toplevel mapped with suggested size 800x600"); } fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {