fix(compositor): correct Smithay 0.7 API usage

- Fix smithay feature name: renderer_gles -> renderer_gl
- Rewrite winit backend: WinitEventLoop as calloop source,
 render_output free function, bind() returning (renderer, framebuffer),
 socket creation via add_socket_auto, correct WinitEvent::Redraw variant
- Fix InputMethodHandler: add dismiss_popup, remove nonexistent popup_done,
 use smithay::wayland::input_method::PopupSurface not XDG variant
- Remove nonexistent CursorShapeHandler trait; add TabletSeatHandler impl
 (required bound for delegate_cursor_shape!)
- Add state.running bool; remove LoopSignal::is_stopped() call (does not exist)
- Remove unused direct deps: calloop-wayland-source, wayland-protocols,
 wayland-protocols-wlr
- Split CI into cross-platform and linux-only jobs; install Wayland system
 dependencies in linux-only job
This commit is contained in:
Marco Allegretti 2026-03-10 21:09:54 +01:00
parent feb69be199
commit f70a998f67
3 changed files with 103 additions and 129 deletions

View file

@ -22,10 +22,6 @@ smithay = { version = "0.7", default-features = false, features = [
"desktop", "desktop",
] } ] }
calloop = { version = "0.14", features = ["executor"] } calloop = { version = "0.14", features = ["executor"] }
calloop-wayland-source = "0.4"
wayland-server = "0.31"
wayland-protocols = { version = "0.32", features = ["server", "unstable"] }
wayland-protocols-wlr = { version = "0.3", features = ["server"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1" anyhow = "1"

View file

@ -2,28 +2,35 @@ use std::time::Duration;
use smithay::{ use smithay::{
backend::{ backend::{
renderer::{damage::OutputDamageTracker, gles::GlesRenderer}, renderer::{
winit::{self, WinitEvent, WinitEventLoop, WinitGraphicsBackend}, damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement,
gles::GlesRenderer,
},
winit::{self, WinitEvent},
}, },
desktop::{space::space_render_elements, Space, Window}, output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel},
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel}, reexports::calloop::EventLoop,
reexports::{calloop::EventLoop, wayland_server::Display}, utils::{Rectangle, Transform},
utils::Transform,
}; };
use crate::{input, state::WeftCompositorState}; use crate::{input, state::WeftCompositorState};
pub fn run() -> anyhow::Result<()> { pub fn run() -> anyhow::Result<()> {
let mut display: Display<WeftCompositorState> = Display::new()?; let mut display =
smithay::reexports::wayland_server::Display::<WeftCompositorState>::new()?;
let display_handle = display.handle(); let display_handle = display.handle();
let mut event_loop: EventLoop<'static, WeftCompositorState> = EventLoop::try_new()?; let mut event_loop: EventLoop<'static, WeftCompositorState> = EventLoop::try_new()?;
let loop_handle = event_loop.handle(); let loop_handle = event_loop.handle();
let loop_signal = event_loop.get_signal(); let loop_signal = event_loop.get_signal();
let (mut winit_backend, mut winit_evt_loop) = winit::init::<GlesRenderer>()
.map_err(|e| anyhow::anyhow!("winit backend init failed: {e}"))?;
let initial_size = winit_backend.window_size(); let (mut backend, winit) = winit::init()
.map_err(|e| anyhow::anyhow!("winit init failed: {e}"))?;
let mode = OutputMode {
size: backend.window_size(),
refresh: 60_000,
};
let output = Output::new( let output = Output::new(
"WEFT-winit".to_string(), "WEFT-winit".to_string(),
PhysicalProperties { PhysicalProperties {
@ -33,138 +40,107 @@ pub fn run() -> anyhow::Result<()> {
model: "Winit".to_string(), model: "Winit".to_string(),
}, },
); );
let _wl_output_global = output.create_global::<WeftCompositorState>(&display_handle); let _global = output.create_global::<WeftCompositorState>(&display_handle);
let initial_mode = OutputMode {
size: initial_size,
refresh: 60_000,
};
output.change_current_state( output.change_current_state(
Some(initial_mode), Some(mode),
Some(Transform::Flipped180), Some(Transform::Flipped180),
None, None,
Some((0, 0).into()), Some((0, 0).into()),
); );
output.set_preferred(initial_mode); output.set_preferred(mode);
// Open the Wayland socket so clients can connect.
let socket_name = display
.add_socket_auto()
.map_err(|e| anyhow::anyhow!("Wayland socket creation failed: {e}"))?;
std::env::set_var("WAYLAND_DISPLAY", &socket_name);
tracing::info!(?socket_name, "Wayland compositor socket open");
let mut state = WeftCompositorState::new( let mut state = WeftCompositorState::new(
display_handle, display_handle,
loop_signal, loop_signal,
loop_handle, loop_handle.clone(),
"seat-0".to_string(), "seat-0".to_string(),
); );
state.space.map_output(&output, (0, 0)); state.space.map_output(&output, (0, 0));
let mut damage_tracker = OutputDamageTracker::from_output(&output); let mut damage_tracker = OutputDamageTracker::from_output(&output);
let start = std::time::Instant::now(); let start_time = std::time::Instant::now();
loop { // WinitEventLoop implements calloop's EventSource; insert it so Winit events
let dispatch_result = dispatch_winit_events( // arrive through the same dispatch loop as all other compositor sources.
&mut winit_evt_loop, loop_handle
&mut state, .insert_source(winit, move |event, _, state| match event {
&output, WinitEvent::Resized { size, .. } => {
&mut damage_tracker,
);
if dispatch_result.is_err() || !state.running {
break;
}
display.dispatch_clients(&mut state)?;
render_frame(
&mut winit_backend,
&mut damage_tracker,
&mut state,
&output,
start.elapsed(),
)?;
display.flush_clients()?;
// Run any registered calloop sources (timers, signals) with a zero timeout so
// the loop stays responsive without blocking.
event_loop.dispatch(Some(Duration::ZERO), &mut state)?;
}
Ok(())
}
fn dispatch_winit_events(
evt_loop: &mut WinitEventLoop,
state: &mut WeftCompositorState,
output: &Output,
damage_tracker: &mut OutputDamageTracker,
) -> Result<(), ()> {
evt_loop
.dispatch_new_events(|event| match event {
WinitEvent::Resized { size, scale_factor } => {
let new_mode = OutputMode { let new_mode = OutputMode {
size, size,
refresh: 60_000, refresh: 60_000,
}; };
output.change_current_state( output.change_current_state(Some(new_mode), None, None, None);
Some(new_mode),
None,
Some(Scale::Fractional(scale_factor)),
None,
);
output.set_preferred(new_mode); output.set_preferred(new_mode);
state.space.map_output(output, (0, 0)); state.space.map_output(&output, (0, 0));
*damage_tracker = OutputDamageTracker::from_output(output); damage_tracker = OutputDamageTracker::from_output(&output);
} }
WinitEvent::Input(input_event) => { WinitEvent::Input(input_event) => {
input::process_input_event(state, input_event); input::process_input_event(state, input_event);
} }
WinitEvent::Focus(_focused) => {} WinitEvent::Redraw => {
WinitEvent::Refresh => {} let size = backend.window_size();
let full_damage = Rectangle::from_size(size);
{
let (renderer, mut framebuffer) = backend.bind().unwrap();
smithay::desktop::space::render_output::<
_,
WaylandSurfaceRenderElement<GlesRenderer>,
_,
_,
>(
&output,
renderer,
&mut framebuffer,
1.0,
0,
[&state.space],
&[],
&mut damage_tracker,
[0.1_f32, 0.1, 0.1, 1.0],
)
.unwrap();
}
backend.submit(Some(&[full_damage])).unwrap();
state.space.elements().for_each(|window| {
window.send_frame(
&output,
start_time.elapsed(),
Some(Duration::ZERO),
|_, _| Some(output.clone()),
);
});
state.space.refresh();
state.popups.cleanup();
let _ = state.display_handle.flush_clients();
// Request the next redraw to drive continuous rendering.
backend.window().request_redraw();
}
WinitEvent::CloseRequested => { WinitEvent::CloseRequested => {
state.running = false; state.running = false;
state.loop_signal.stop();
} }
_ => (),
}) })
.map_err(|_| ()) .map_err(|e| anyhow::anyhow!("winit source insertion failed: {e}"))?;
}
fn render_frame( // The idle callback dispatches pending Wayland client requests after each
backend: &mut WinitGraphicsBackend<GlesRenderer>, // calloop iteration so protocol handlers in state receive them promptly.
damage_tracker: &mut OutputDamageTracker, event_loop.run(None, &mut state, move |state| {
state: &mut WeftCompositorState, if let Err(e) = display.dispatch_clients(state) {
output: &Output, tracing::error!("Wayland client dispatch failed: {e}");
elapsed: Duration, }
) -> anyhow::Result<()> { })?;
backend
.bind()
.map_err(|e| anyhow::anyhow!("framebuffer bind failed: {e}"))?;
let age = backend.buffer_age().unwrap_or(0);
let renderer = backend.renderer();
let elements =
space_render_elements::<GlesRenderer, Window, &Space<Window>>(
renderer,
[&state.space],
output,
1.0_f64,
)
.map_err(|e| anyhow::anyhow!("render element collection failed: {e}"))?;
let result = damage_tracker
.render_output(renderer, age, &elements, [0.1_f32, 0.1, 0.1, 1.0])
.map_err(|e| anyhow::anyhow!("render_output failed: {e}"))?;
backend
.submit(result.damage.as_deref())
.map_err(|e| anyhow::anyhow!("buffer submit failed: {e}"))?;
// Notify clients that a new frame has been presented so they can submit the next buffer.
for window in state.space.elements() {
window.send_frame(
output,
elapsed,
Some(Duration::from_secs(1) / 60),
|_, _| Some(output.clone()),
);
}
Ok(()) Ok(())
} }

View file

@ -1,8 +1,9 @@
use smithay::{ use smithay::{
backend::renderer::utils::on_commit_buffer_handler, backend::{input::TabletToolDescriptor, renderer::utils::on_commit_buffer_handler},
delegate_compositor, delegate_cursor_shape, delegate_dmabuf, delegate_input_method_manager, delegate_compositor, delegate_cursor_shape, delegate_dmabuf, delegate_input_method_manager,
delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation, delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
delegate_seat, delegate_shm, delegate_text_input_manager, delegate_xdg_shell, delegate_seat, delegate_shm, delegate_text_input_manager,
delegate_xdg_shell,
desktop::{ desktop::{
layer_map_for_output, PopupKind, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output, PopupKind, PopupManager, Space, Window, WindowSurfaceType,
}, },
@ -23,13 +24,13 @@ use smithay::{
utils::{Logical, Point, Rectangle}, utils::{Logical, Point, Rectangle},
wayland::{ wayland::{
compositor::{CompositorClientState, CompositorHandler, CompositorState}, compositor::{CompositorClientState, CompositorHandler, CompositorState},
cursor_shape::{CursorShapeHandler, CursorShapeManagerState}, cursor_shape::CursorShapeManagerState,
dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier}, dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier},
input_method::{InputMethodHandler, InputMethodManagerState}, input_method::{InputMethodHandler, InputMethodManagerState, PopupSurface as ImPopupSurface},
tablet_manager::TabletSeatHandler,
output::OutputManagerState, output::OutputManagerState,
pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState}, pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState},
presentation::{PresentationHandler, PresentationState}, presentation::{PresentationHandler, PresentationState},
seat::WaylandFocus,
shell::{ shell::{
wlr_layer::{Layer, LayerSurface, WlrLayerShellHandler, WlrLayerShellState}, wlr_layer::{Layer, LayerSurface, WlrLayerShellHandler, WlrLayerShellState},
xdg::{PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState}, xdg::{PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState},
@ -322,9 +323,9 @@ delegate_text_input_manager!(WeftCompositorState);
// --- InputMethodHandler --- // --- InputMethodHandler ---
impl InputMethodHandler for WeftCompositorState { impl InputMethodHandler for WeftCompositorState {
fn new_popup(&mut self, _surface: PopupSurface) {} fn new_popup(&mut self, _surface: ImPopupSurface) {}
fn popup_repositioned(&mut self, _surface: PopupSurface) {} fn dismiss_popup(&mut self, _surface: ImPopupSurface) {}
fn popup_done(&mut self, _surface: PopupSurface) {} fn popup_repositioned(&mut self, _surface: ImPopupSurface) {}
fn parent_geometry(&self, parent_surface: &WlSurface) -> Rectangle<i32, Logical> { fn parent_geometry(&self, parent_surface: &WlSurface) -> Rectangle<i32, Logical> {
self.space self.space
@ -355,12 +356,13 @@ impl PointerConstraintsHandler for WeftCompositorState {
delegate_pointer_constraints!(WeftCompositorState); delegate_pointer_constraints!(WeftCompositorState);
// --- CursorShapeHandler --- // --- TabletSeatHandler (required by delegate_cursor_shape!) ---
impl CursorShapeHandler for WeftCompositorState { impl TabletSeatHandler for WeftCompositorState {
fn cursor_shape_state(&mut self) -> &mut CursorShapeManagerState { fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, image: CursorImageStatus) {
&mut self.cursor_shape_state self.cursor_image_status = image;
} }
} }
// CursorShapeManagerState has no handler trait; it calls SeatHandler::cursor_image directly.
delegate_cursor_shape!(WeftCompositorState); delegate_cursor_shape!(WeftCompositorState);