From f70a998f671db2e4b678badd8978bb4d93a329a5 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Tue, 10 Mar 2026 21:09:54 +0100 Subject: [PATCH] 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 --- crates/weft-compositor/Cargo.toml | 4 - crates/weft-compositor/src/backend/winit.rs | 202 +++++++++----------- crates/weft-compositor/src/state.rs | 26 +-- 3 files changed, 103 insertions(+), 129 deletions(-) diff --git a/crates/weft-compositor/Cargo.toml b/crates/weft-compositor/Cargo.toml index 90417da..c10e0f2 100644 --- a/crates/weft-compositor/Cargo.toml +++ b/crates/weft-compositor/Cargo.toml @@ -22,10 +22,6 @@ smithay = { version = "0.7", default-features = false, features = [ "desktop", ] } 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-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1" diff --git a/crates/weft-compositor/src/backend/winit.rs b/crates/weft-compositor/src/backend/winit.rs index b78a30f..7e95001 100644 --- a/crates/weft-compositor/src/backend/winit.rs +++ b/crates/weft-compositor/src/backend/winit.rs @@ -2,28 +2,35 @@ use std::time::Duration; use smithay::{ backend::{ - renderer::{damage::OutputDamageTracker, gles::GlesRenderer}, - winit::{self, WinitEvent, WinitEventLoop, WinitGraphicsBackend}, + renderer::{ + damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, + gles::GlesRenderer, + }, + winit::{self, WinitEvent}, }, - desktop::{space::space_render_elements, Space, Window}, - output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel}, - reexports::{calloop::EventLoop, wayland_server::Display}, - utils::Transform, + output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel}, + reexports::calloop::EventLoop, + utils::{Rectangle, Transform}, }; use crate::{input, state::WeftCompositorState}; pub fn run() -> anyhow::Result<()> { - let mut display: Display = Display::new()?; + let mut display = + smithay::reexports::wayland_server::Display::::new()?; let display_handle = display.handle(); let mut event_loop: EventLoop<'static, WeftCompositorState> = EventLoop::try_new()?; let loop_handle = event_loop.handle(); let loop_signal = event_loop.get_signal(); - let (mut winit_backend, mut winit_evt_loop) = winit::init::() - .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( "WEFT-winit".to_string(), PhysicalProperties { @@ -33,138 +40,107 @@ pub fn run() -> anyhow::Result<()> { model: "Winit".to_string(), }, ); - let _wl_output_global = output.create_global::(&display_handle); - - let initial_mode = OutputMode { - size: initial_size, - refresh: 60_000, - }; + let _global = output.create_global::(&display_handle); output.change_current_state( - Some(initial_mode), + Some(mode), Some(Transform::Flipped180), None, 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( display_handle, loop_signal, - loop_handle, + loop_handle.clone(), "seat-0".to_string(), ); state.space.map_output(&output, (0, 0)); let mut damage_tracker = OutputDamageTracker::from_output(&output); - let start = std::time::Instant::now(); + let start_time = std::time::Instant::now(); - loop { - let dispatch_result = dispatch_winit_events( - &mut winit_evt_loop, - &mut state, - &output, - &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 } => { + // WinitEventLoop implements calloop's EventSource; insert it so Winit events + // arrive through the same dispatch loop as all other compositor sources. + loop_handle + .insert_source(winit, move |event, _, state| match event { + WinitEvent::Resized { size, .. } => { let new_mode = OutputMode { size, refresh: 60_000, }; - output.change_current_state( - Some(new_mode), - None, - Some(Scale::Fractional(scale_factor)), - None, - ); + output.change_current_state(Some(new_mode), None, None, None); output.set_preferred(new_mode); - state.space.map_output(output, (0, 0)); - *damage_tracker = OutputDamageTracker::from_output(output); + state.space.map_output(&output, (0, 0)); + damage_tracker = OutputDamageTracker::from_output(&output); } WinitEvent::Input(input_event) => { input::process_input_event(state, input_event); } - WinitEvent::Focus(_focused) => {} - WinitEvent::Refresh => {} + WinitEvent::Redraw => { + 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, + _, + _, + >( + &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 => { state.running = false; + state.loop_signal.stop(); } + _ => (), }) - .map_err(|_| ()) -} + .map_err(|e| anyhow::anyhow!("winit source insertion failed: {e}"))?; -fn render_frame( - backend: &mut WinitGraphicsBackend, - damage_tracker: &mut OutputDamageTracker, - state: &mut WeftCompositorState, - output: &Output, - 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::>( - 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()), - ); - } + // The idle callback dispatches pending Wayland client requests after each + // calloop iteration so protocol handlers in state receive them promptly. + event_loop.run(None, &mut state, move |state| { + if let Err(e) = display.dispatch_clients(state) { + tracing::error!("Wayland client dispatch failed: {e}"); + } + })?; Ok(()) } diff --git a/crates/weft-compositor/src/state.rs b/crates/weft-compositor/src/state.rs index 8739365..2889e5e 100644 --- a/crates/weft-compositor/src/state.rs +++ b/crates/weft-compositor/src/state.rs @@ -1,8 +1,9 @@ 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_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::{ layer_map_for_output, PopupKind, PopupManager, Space, Window, WindowSurfaceType, }, @@ -23,13 +24,13 @@ use smithay::{ utils::{Logical, Point, Rectangle}, wayland::{ compositor::{CompositorClientState, CompositorHandler, CompositorState}, - cursor_shape::{CursorShapeHandler, CursorShapeManagerState}, + cursor_shape::CursorShapeManagerState, dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier}, - input_method::{InputMethodHandler, InputMethodManagerState}, + input_method::{InputMethodHandler, InputMethodManagerState, PopupSurface as ImPopupSurface}, + tablet_manager::TabletSeatHandler, output::OutputManagerState, pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState}, presentation::{PresentationHandler, PresentationState}, - seat::WaylandFocus, shell::{ wlr_layer::{Layer, LayerSurface, WlrLayerShellHandler, WlrLayerShellState}, xdg::{PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState}, @@ -322,9 +323,9 @@ delegate_text_input_manager!(WeftCompositorState); // --- InputMethodHandler --- impl InputMethodHandler for WeftCompositorState { - fn new_popup(&mut self, _surface: PopupSurface) {} - fn popup_repositioned(&mut self, _surface: PopupSurface) {} - fn popup_done(&mut self, _surface: PopupSurface) {} + fn new_popup(&mut self, _surface: ImPopupSurface) {} + fn dismiss_popup(&mut self, _surface: ImPopupSurface) {} + fn popup_repositioned(&mut self, _surface: ImPopupSurface) {} fn parent_geometry(&self, parent_surface: &WlSurface) -> Rectangle { self.space @@ -355,12 +356,13 @@ impl PointerConstraintsHandler for WeftCompositorState { delegate_pointer_constraints!(WeftCompositorState); -// --- CursorShapeHandler --- +// --- TabletSeatHandler (required by delegate_cursor_shape!) --- -impl CursorShapeHandler for WeftCompositorState { - fn cursor_shape_state(&mut self) -> &mut CursorShapeManagerState { - &mut self.cursor_shape_state +impl TabletSeatHandler for WeftCompositorState { + fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, image: CursorImageStatus) { + self.cursor_image_status = image; } } +// CursorShapeManagerState has no handler trait; it calls SeatHandler::cursor_image directly. delegate_cursor_shape!(WeftCompositorState);