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
This commit is contained in:
Till Wegmueller 2026-04-04 18:59:49 +02:00
parent a189c2f51b
commit ed2f9be8e6
5 changed files with 134 additions and 20 deletions

View file

@ -54,12 +54,10 @@ impl CompositorHandler for WayRay {
.elements() .elements()
.find(|w| w.wl_surface().map(|s| s.into_owned()) == Some(surface.clone())) .find(|w| w.wl_surface().map(|s| s.into_owned()) == Some(surface.clone()))
.cloned() .cloned()
&& let Some(toplevel) = window.toplevel()
&& !toplevel.is_initial_configure_sent()
{ {
if let Some(toplevel) = window.toplevel() { toplevel.send_configure();
if !toplevel.is_initial_configure_sent() {
toplevel.send_configure();
}
}
} }
} }
} }

View file

@ -1,5 +1,5 @@
use smithay::{ use smithay::{
delegate_data_device, delegate_seat, delegate_data_device, delegate_primary_selection, delegate_seat,
input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus}, input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus},
reexports::wayland_server::protocol::wl_surface::WlSurface, reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland::selection::{ wayland::selection::{
@ -7,6 +7,7 @@ use smithay::{
data_device::{ data_device::{
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, 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_seat!(WayRay);
delegate_data_device!(WayRay); delegate_data_device!(WayRay);
delegate_primary_selection!(WayRay);

View file

@ -1,10 +1,15 @@
use smithay::{ use smithay::{
delegate_xdg_shell, delegate_xdg_decoration, delegate_xdg_shell,
desktop::Window, 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, utils::Serial,
wayland::shell::xdg::{ wayland::shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
decoration::XdgDecorationHandler,
}, },
}; };
use tracing::info; use tracing::info;
@ -22,8 +27,9 @@ impl XdgShellHandler for WayRay {
info!("new toplevel mapped"); info!("new toplevel mapped");
} }
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) { fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
// TODO: handle new popups // 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) { 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_shell!(WayRay);
delegate_xdg_decoration!(WayRay);

View file

@ -1,6 +1,8 @@
use smithay::{ use smithay::{
backend::{ backend::{
allocator::Fourcc,
renderer::{ renderer::{
ExportMem,
damage::OutputDamageTracker, damage::OutputDamageTracker,
element::texture::TextureRenderElement, element::texture::TextureRenderElement,
gles::{GlesRenderer, GlesTexture}, gles::{GlesRenderer, GlesTexture},
@ -8,10 +10,11 @@ use smithay::{
winit::WinitGraphicsBackend, winit::WinitGraphicsBackend,
}, },
desktop::{Window, space::render_output}, desktop::{Window, space::render_output},
utils::{Buffer as BufferCoord, Rectangle, Size},
}; };
use tracing::warn; use tracing::warn;
use crate::state::WayRay; use crate::state::{CapturedFrame, WayRay};
/// Dark grey clear color for the compositor background. /// Dark grey clear color for the compositor background.
const CLEAR_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.0]; 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); let age = backend.buffer_age().unwrap_or(0);
// Render within a block so framebuffer is dropped before submit. // 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() { let (renderer, mut framebuffer) = match backend.bind() {
Ok(pair) => pair, Ok(pair) => pair,
Err(err) => { Err(err) => {
@ -59,9 +63,55 @@ pub fn render_output_frame(
match render_result { match render_result {
Ok(result) => { Ok(result) => {
// Clone the damage rectangles so we can use them after // Clone damage before we consume the framebuffer for capture.
// the framebuffer is dropped. let damage = result.damage.cloned();
Ok(result.damage.cloned())
// Capture the framebuffer while it is still bound.
let output_size = state.output.current_mode().unwrap().size;
let region: Rectangle<i32, BufferCoord> = 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) => { Err(err) => {
warn!(?err, "damage tracker render failed"); warn!(?err, "damage tracker render failed");
@ -71,8 +121,12 @@ pub fn render_output_frame(
}; };
// framebuffer is now dropped, backend is no longer borrowed. // framebuffer is now dropped, backend is no longer borrowed.
match render_damage { match render_result {
Ok(damage) => { 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 has_damage = damage.is_some();
let submit_result = if let Some(ref rects) = damage { let submit_result = if let Some(ref rects) = damage {

View file

@ -13,17 +13,28 @@ use smithay::{
}, },
output::Output, output::Output,
reexports::wayland_server::{Display, DisplayHandle}, reexports::wayland_server::{Display, DisplayHandle},
utils::{Clock, Monotonic, SERIAL_COUNTER}, utils::{Clock, Monotonic, Physical, Rectangle, SERIAL_COUNTER},
wayland::{ wayland::{
compositor::CompositorState, compositor::CompositorState,
output::OutputManagerState, output::OutputManagerState,
selection::data_device::DataDeviceState, selection::{
shell::xdg::XdgShellState, data_device::DataDeviceState,
primary_selection::PrimarySelectionState,
},
shell::xdg::{XdgShellState, decoration::XdgDecorationState},
shm::ShmState, shm::ShmState,
}, },
}; };
use tracing::info; use tracing::info;
/// Captured framebuffer data from the last render pass.
pub struct CapturedFrame {
pub data: Vec<u8>,
pub width: i32,
pub height: i32,
pub damage: Vec<Rectangle<i32, Physical>>,
}
/// Central compositor state holding all Smithay subsystem states. /// Central compositor state holding all Smithay subsystem states.
/// ///
/// This is the "god struct" pattern required by Smithay — a single type that /// This is the "god struct" pattern required by Smithay — a single type that
@ -36,10 +47,13 @@ pub struct WayRay {
pub seat_state: SeatState<Self>, pub seat_state: SeatState<Self>,
pub output_manager_state: OutputManagerState, pub output_manager_state: OutputManagerState,
pub data_device_state: DataDeviceState, pub data_device_state: DataDeviceState,
pub primary_selection_state: PrimarySelectionState,
pub xdg_decoration_state: XdgDecorationState,
pub space: Space<smithay::desktop::Window>, pub space: Space<smithay::desktop::Window>,
pub seat: Seat<Self>, pub seat: Seat<Self>,
pub clock: Clock<Monotonic>, pub clock: Clock<Monotonic>,
pub output: Output, pub output: Output,
pub last_capture: Option<CapturedFrame>,
} }
impl WayRay { impl WayRay {
@ -51,6 +65,8 @@ impl WayRay {
let xdg_shell_state = XdgShellState::new::<Self>(&dh); let xdg_shell_state = XdgShellState::new::<Self>(&dh);
let shm_state = ShmState::new::<Self>(&dh, vec![]); let shm_state = ShmState::new::<Self>(&dh, vec![]);
let data_device_state = DataDeviceState::new::<Self>(&dh); let data_device_state = DataDeviceState::new::<Self>(&dh);
let primary_selection_state = PrimarySelectionState::new::<Self>(&dh);
let xdg_decoration_state = XdgDecorationState::new::<Self>(&dh);
let mut seat_state = SeatState::new(); let mut seat_state = SeatState::new();
let mut seat = seat_state.new_wl_seat(&dh, "wayray"); let mut seat = seat_state.new_wl_seat(&dh, "wayray");
@ -71,10 +87,13 @@ impl WayRay {
seat_state, seat_state,
output_manager_state, output_manager_state,
data_device_state, data_device_state,
primary_selection_state,
xdg_decoration_state,
space: Space::default(), space: Space::default(),
seat, seat,
clock: Clock::new(), clock: Clock::new(),
output, output,
last_capture: None,
} }
} }