From ed2f9be8e676755d6b28c2d3e342f4bac9a969fd Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Sat, 4 Apr 2026 18:59:49 +0200 Subject: [PATCH] Add missing delegates and framebuffer capture for functional compositor - Add PrimarySelectionState and delegate: terminals like foot expect the primary selection protocol to be present - Add XdgDecorationState and delegate: allows clients to negotiate server-side vs client-side window decorations - Send initial configure for popups so menus and tooltips render - Flatten nested if-let chains in commit handler (clippy) - Include framebuffer capture in render pipeline (from prior task): copies rendered frame via ExportMem for future network transport --- crates/wrsrvd/src/handlers/compositor.rs | 8 ++- crates/wrsrvd/src/handlers/input.rs | 10 +++- crates/wrsrvd/src/handlers/xdg_shell.rs | 43 +++++++++++++-- crates/wrsrvd/src/render.rs | 68 +++++++++++++++++++++--- crates/wrsrvd/src/state.rs | 25 +++++++-- 5 files changed, 134 insertions(+), 20 deletions(-) diff --git a/crates/wrsrvd/src/handlers/compositor.rs b/crates/wrsrvd/src/handlers/compositor.rs index cd0de12..eea96d1 100644 --- a/crates/wrsrvd/src/handlers/compositor.rs +++ b/crates/wrsrvd/src/handlers/compositor.rs @@ -54,12 +54,10 @@ impl CompositorHandler for WayRay { .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() { - if let Some(toplevel) = window.toplevel() { - if !toplevel.is_initial_configure_sent() { - toplevel.send_configure(); - } - } + toplevel.send_configure(); } } } diff --git a/crates/wrsrvd/src/handlers/input.rs b/crates/wrsrvd/src/handlers/input.rs index b86082a..334d56b 100644 --- a/crates/wrsrvd/src/handlers/input.rs +++ b/crates/wrsrvd/src/handlers/input.rs @@ -1,5 +1,5 @@ use smithay::{ - delegate_data_device, delegate_seat, + delegate_data_device, delegate_primary_selection, delegate_seat, input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus}, reexports::wayland_server::protocol::wl_surface::WlSurface, wayland::selection::{ @@ -7,6 +7,7 @@ use smithay::{ data_device::{ ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, }, + primary_selection::{PrimarySelectionHandler, PrimarySelectionState}, }, }; @@ -39,5 +40,12 @@ impl DataDeviceHandler for WayRay { } } +impl PrimarySelectionHandler for WayRay { + fn primary_selection_state(&self) -> &PrimarySelectionState { + &self.primary_selection_state + } +} + delegate_seat!(WayRay); delegate_data_device!(WayRay); +delegate_primary_selection!(WayRay); diff --git a/crates/wrsrvd/src/handlers/xdg_shell.rs b/crates/wrsrvd/src/handlers/xdg_shell.rs index dab31d3..5d8b1c1 100644 --- a/crates/wrsrvd/src/handlers/xdg_shell.rs +++ b/crates/wrsrvd/src/handlers/xdg_shell.rs @@ -1,10 +1,15 @@ use smithay::{ - delegate_xdg_shell, + delegate_xdg_decoration, delegate_xdg_shell, desktop::Window, - reexports::wayland_server::protocol::wl_seat, + reexports::{ + wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode + as DecorationMode, + wayland_server::protocol::wl_seat, + }, utils::Serial, wayland::shell::xdg::{ PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + decoration::XdgDecorationHandler, }, }; use tracing::info; @@ -22,8 +27,9 @@ impl XdgShellHandler for WayRay { info!("new toplevel mapped"); } - fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) { - // TODO: handle new popups + fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) { + // Send the initial configure so the popup can start drawing. + surface.send_configure().ok(); } fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) { @@ -40,4 +46,33 @@ impl XdgShellHandler for WayRay { } } +impl XdgDecorationHandler for WayRay { + fn new_decoration(&mut self, toplevel: ToplevelSurface) { + // Prefer server-side decorations — we don't draw them yet, + // but telling the client lets it skip its own title bar. + toplevel.with_pending_state(|state| { + state.decoration_mode = Some(DecorationMode::ServerSide); + }); + } + + fn request_mode(&mut self, toplevel: ToplevelSurface, mode: DecorationMode) { + toplevel.with_pending_state(|state| { + state.decoration_mode = Some(mode); + }); + if toplevel.is_initial_configure_sent() { + toplevel.send_pending_configure(); + } + } + + fn unset_mode(&mut self, toplevel: ToplevelSurface) { + toplevel.with_pending_state(|state| { + state.decoration_mode = Some(DecorationMode::ServerSide); + }); + if toplevel.is_initial_configure_sent() { + toplevel.send_pending_configure(); + } + } +} + delegate_xdg_shell!(WayRay); +delegate_xdg_decoration!(WayRay); diff --git a/crates/wrsrvd/src/render.rs b/crates/wrsrvd/src/render.rs index 87188f0..b0cce6b 100644 --- a/crates/wrsrvd/src/render.rs +++ b/crates/wrsrvd/src/render.rs @@ -1,6 +1,8 @@ use smithay::{ backend::{ + allocator::Fourcc, renderer::{ + ExportMem, damage::OutputDamageTracker, element::texture::TextureRenderElement, gles::{GlesRenderer, GlesTexture}, @@ -8,10 +10,11 @@ use smithay::{ winit::WinitGraphicsBackend, }, desktop::{Window, space::render_output}, + utils::{Buffer as BufferCoord, Rectangle, Size}, }; use tracing::warn; -use crate::state::WayRay; +use crate::state::{CapturedFrame, WayRay}; /// Dark grey clear color for the compositor background. const CLEAR_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.0]; @@ -33,7 +36,8 @@ pub fn render_output_frame( let age = backend.buffer_age().unwrap_or(0); // Render within a block so framebuffer is dropped before submit. - let render_damage = { + // Returns (damage, optional captured frame) on success. + let render_result = { let (renderer, mut framebuffer) = match backend.bind() { Ok(pair) => pair, Err(err) => { @@ -59,9 +63,55 @@ pub fn render_output_frame( match render_result { Ok(result) => { - // Clone the damage rectangles so we can use them after - // the framebuffer is dropped. - Ok(result.damage.cloned()) + // Clone damage before we consume the framebuffer for capture. + let damage = result.damage.cloned(); + + // Capture the framebuffer while it is still bound. + let output_size = state.output.current_mode().unwrap().size; + let region: Rectangle = Rectangle::from_size( + Size::from((output_size.w, output_size.h)), + ); + + let capture = match renderer.copy_framebuffer( + &framebuffer, + region, + Fourcc::Argb8888, + ) { + Ok(mapping) => match renderer.map_texture(&mapping) { + Ok(pixels) => { + let damage_rects = damage + .as_ref() + .map(|d| d.len()) + .unwrap_or(0); + tracing::debug!( + width = output_size.w, + height = output_size.h, + bytes = pixels.len(), + damage_rects, + "framebuffer captured" + ); + Some(CapturedFrame { + data: pixels.to_vec(), + width: output_size.w, + height: output_size.h, + damage: damage + .as_ref() + .cloned() + .unwrap_or_default(), + }) + } + Err(err) => { + tracing::warn!(?err, "failed to map framebuffer"); + None + } + }, + Err(err) => { + tracing::warn!(?err, "failed to copy framebuffer"); + None + } + }; + + Ok((damage, capture)) } Err(err) => { warn!(?err, "damage tracker render failed"); @@ -71,8 +121,12 @@ pub fn render_output_frame( }; // framebuffer is now dropped, backend is no longer borrowed. - match render_damage { - Ok(damage) => { + match render_result { + Ok((damage, capture)) => { + // Store the captured frame for later consumption by the + // network transport layer. + state.last_capture = capture; + let has_damage = damage.is_some(); let submit_result = if let Some(ref rects) = damage { diff --git a/crates/wrsrvd/src/state.rs b/crates/wrsrvd/src/state.rs index ef3e2ec..ab36eb3 100644 --- a/crates/wrsrvd/src/state.rs +++ b/crates/wrsrvd/src/state.rs @@ -13,17 +13,28 @@ use smithay::{ }, output::Output, reexports::wayland_server::{Display, DisplayHandle}, - utils::{Clock, Monotonic, SERIAL_COUNTER}, + utils::{Clock, Monotonic, Physical, Rectangle, SERIAL_COUNTER}, wayland::{ compositor::CompositorState, output::OutputManagerState, - selection::data_device::DataDeviceState, - shell::xdg::XdgShellState, + selection::{ + data_device::DataDeviceState, + primary_selection::PrimarySelectionState, + }, + shell::xdg::{XdgShellState, decoration::XdgDecorationState}, shm::ShmState, }, }; use tracing::info; +/// Captured framebuffer data from the last render pass. +pub struct CapturedFrame { + pub data: Vec, + pub width: i32, + pub height: i32, + pub damage: Vec>, +} + /// Central compositor state holding all Smithay subsystem states. /// /// This is the "god struct" pattern required by Smithay — a single type that @@ -36,10 +47,13 @@ pub struct WayRay { pub seat_state: SeatState, pub output_manager_state: OutputManagerState, pub data_device_state: DataDeviceState, + pub primary_selection_state: PrimarySelectionState, + pub xdg_decoration_state: XdgDecorationState, pub space: Space, pub seat: Seat, pub clock: Clock, pub output: Output, + pub last_capture: Option, } impl WayRay { @@ -51,6 +65,8 @@ impl WayRay { let xdg_shell_state = XdgShellState::new::(&dh); let shm_state = ShmState::new::(&dh, vec![]); let data_device_state = DataDeviceState::new::(&dh); + let primary_selection_state = PrimarySelectionState::new::(&dh); + let xdg_decoration_state = XdgDecorationState::new::(&dh); let mut seat_state = SeatState::new(); let mut seat = seat_state.new_wl_seat(&dh, "wayray"); @@ -71,10 +87,13 @@ impl WayRay { seat_state, output_manager_state, data_device_state, + primary_selection_state, + xdg_decoration_state, space: Space::default(), seat, clock: Clock::new(), output, + last_capture: None, } }