use std::{ collections::HashMap, os::unix::io::OwnedFd, sync::{atomic::AtomicBool, Arc}, time::Duration, }; use tracing::{info, warn}; use smithay::{ backend::{ input::TabletToolDescriptor, renderer::element::{ default_primary_scanout_output_compare, utils::select_dmabuf_feedback, RenderElementStates, }, }, delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale, delegate_input_method_manager, delegate_keyboard_shortcuts_inhibit, delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_pointer_gestures, delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_security_context, delegate_shm, delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter, delegate_virtual_keyboard_manager, delegate_xdg_activation, delegate_xdg_decoration, delegate_xdg_shell, desktop::{ space::SpaceElement, utils::{ surface_presentation_feedback_flags_from_states, surface_primary_scanout_output, update_surface_primary_scanout_output, with_surfaces_surface_tree, OutputPresentationFeedback, }, PopupKind, PopupManager, Space, }, input::{ keyboard::{Keysym, LedState, XkbConfig}, pointer::{CursorImageStatus, CursorImageSurfaceData, PointerHandle}, Seat, SeatHandler, SeatState, }, output::Output, reexports::{ calloop::{generic::Generic, Interest, LoopHandle, Mode, PostAction}, wayland_protocols::xdg::decoration::{ self as xdg_decoration, zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode, }, wayland_server::{ backend::{ClientData, ClientId, DisconnectReason}, protocol::{wl_data_source::WlDataSource, wl_surface::WlSurface}, Client, Display, DisplayHandle, Resource, }, }, utils::{Clock, Logical, Monotonic, Point, Rectangle, Time}, wayland::{ commit_timing::{CommitTimerBarrierStateUserData, CommitTimingManagerState}, compositor::{get_parent, with_states, CompositorClientState, CompositorHandler, CompositorState}, dmabuf::DmabufFeedback, fifo::{FifoBarrierCachedState, FifoManagerState}, fractional_scale::{with_fractional_scale, FractionalScaleHandler, FractionalScaleManagerState}, input_method::{InputMethodHandler, InputMethodManagerState, PopupSurface}, keyboard_shortcuts_inhibit::{ KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor, }, output::{OutputHandler, OutputManagerState}, pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler, PointerConstraintsState}, pointer_gestures::PointerGesturesState, presentation::PresentationState, relative_pointer::RelativePointerManagerState, seat::WaylandFocus, security_context::{ SecurityContext, SecurityContextHandler, SecurityContextListenerSource, SecurityContextState, }, selection::{ data_device::{ set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, }, primary_selection::{set_primary_focus, PrimarySelectionHandler, PrimarySelectionState}, wlr_data_control::{DataControlHandler, DataControlState}, SelectionHandler, }, shell::{ wlr_layer::WlrLayerShellState, xdg::{ decoration::{XdgDecorationHandler, XdgDecorationState}, ToplevelSurface, XdgShellState, }, }, shm::{ShmHandler, ShmState}, single_pixel_buffer::SinglePixelBufferState, socket::ListeningSocketSource, tablet_manager::{TabletManagerState, TabletSeatHandler}, text_input::TextInputManagerState, viewporter::ViewporterState, virtual_keyboard::VirtualKeyboardManagerState, xdg_activation::{ XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData, }, xdg_foreign::{XdgForeignHandler, XdgForeignState}, }, }; #[cfg(feature = "xwayland")] use crate::cursor::Cursor; use crate::{ focus::{KeyboardFocusTarget, PointerFocusTarget}, shell::WindowElement, }; #[cfg(feature = "xwayland")] use smithay::{ delegate_xwayland_keyboard_grab, delegate_xwayland_shell, utils::Size, wayland::selection::{SelectionSource, SelectionTarget}, wayland::xwayland_keyboard_grab::{XWaylandKeyboardGrabHandler, XWaylandKeyboardGrabState}, wayland::xwayland_shell, xwayland::{X11Wm, XWayland, XWaylandEvent}, }; #[derive(Debug, Default)] pub struct ClientState { pub compositor_state: CompositorClientState, pub security_context: Option, } impl ClientData for ClientState { /// Notification that a client was initialized fn initialized(&self, _client_id: ClientId) {} /// Notification that a client is disconnected fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} } #[derive(Debug)] pub struct AnvilState { pub backend_data: BackendData, pub socket_name: Option, pub display_handle: DisplayHandle, pub running: Arc, pub handle: LoopHandle<'static, AnvilState>, // desktop pub space: Space, pub popups: PopupManager, // smithay state pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, pub layer_shell_state: WlrLayerShellState, pub output_manager_state: OutputManagerState, pub primary_selection_state: PrimarySelectionState, pub data_control_state: DataControlState, pub seat_state: SeatState>, pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub shm_state: ShmState, pub viewporter_state: ViewporterState, pub xdg_activation_state: XdgActivationState, pub xdg_decoration_state: XdgDecorationState, pub xdg_shell_state: XdgShellState, pub presentation_state: PresentationState, pub fractional_scale_manager_state: FractionalScaleManagerState, pub xdg_foreign_state: XdgForeignState, #[cfg(feature = "xwayland")] pub xwayland_shell_state: xwayland_shell::XWaylandShellState, pub single_pixel_buffer_state: SinglePixelBufferState, pub fifo_manager_state: FifoManagerState, pub commit_timing_manager_state: CommitTimingManagerState, pub dnd_icon: Option, // input-related fields pub suppressed_keys: Vec, pub cursor_status: CursorImageStatus, pub seat_name: String, pub seat: Seat>, pub clock: Clock, pub pointer: PointerHandle>, #[cfg(feature = "xwayland")] pub xwm: Option, #[cfg(feature = "xwayland")] pub xdisplay: Option, #[cfg(feature = "debug")] pub renderdoc: Option>, pub show_window_preview: bool, } #[derive(Debug)] pub struct DndIcon { pub surface: WlSurface, pub offset: Point, } delegate_compositor!(@ AnvilState); impl DataDeviceHandler for AnvilState { fn data_device_state(&self) -> &DataDeviceState { &self.data_device_state } } impl ClientDndGrabHandler for AnvilState { fn started(&mut self, _source: Option, icon: Option, _seat: Seat) { let offset = if let CursorImageStatus::Surface(ref surface) = self.cursor_status { with_states(surface, |states| { let hotspot = states .data_map .get::() .unwrap() .lock() .unwrap() .hotspot; Point::from((-hotspot.x, -hotspot.y)) }) } else { (0, 0).into() }; self.dnd_icon = icon.map(|surface| DndIcon { surface, offset }); } fn dropped(&mut self, _target: Option, _validated: bool, _seat: Seat) { self.dnd_icon = None; } } impl ServerDndGrabHandler for AnvilState { fn send(&mut self, _mime_type: String, _fd: OwnedFd, _seat: Seat) { unreachable!("Anvil doesn't do server-side grabs"); } } delegate_data_device!(@ AnvilState); impl OutputHandler for AnvilState {} delegate_output!(@ AnvilState); impl SelectionHandler for AnvilState { type SelectionUserData = (); #[cfg(feature = "xwayland")] fn new_selection(&mut self, ty: SelectionTarget, source: Option, _seat: Seat) { if let Some(xwm) = self.xwm.as_mut() { if let Err(err) = xwm.new_selection(ty, source.map(|source| source.mime_types())) { warn!(?err, ?ty, "Failed to set Xwayland selection"); } } } #[cfg(feature = "xwayland")] fn send_selection( &mut self, ty: SelectionTarget, mime_type: String, fd: OwnedFd, _seat: Seat, _user_data: &(), ) { if let Some(xwm) = self.xwm.as_mut() { if let Err(err) = xwm.send_selection(ty, mime_type, fd, self.handle.clone()) { warn!(?err, "Failed to send primary (X11 -> Wayland)"); } } } } impl PrimarySelectionHandler for AnvilState { fn primary_selection_state(&self) -> &PrimarySelectionState { &self.primary_selection_state } } delegate_primary_selection!(@ AnvilState); impl DataControlHandler for AnvilState { fn data_control_state(&self) -> &DataControlState { &self.data_control_state } } delegate_data_control!(@ AnvilState); impl ShmHandler for AnvilState { fn shm_state(&self) -> &ShmState { &self.shm_state } } delegate_shm!(@ AnvilState); impl SeatHandler for AnvilState { type KeyboardFocus = KeyboardFocusTarget; type PointerFocus = PointerFocusTarget; type TouchFocus = PointerFocusTarget; fn seat_state(&mut self) -> &mut SeatState> { &mut self.seat_state } fn focus_changed(&mut self, seat: &Seat, target: Option<&KeyboardFocusTarget>) { let dh = &self.display_handle; let wl_surface = target.and_then(WaylandFocus::wl_surface); let focus = wl_surface.and_then(|s| dh.get_client(s.id()).ok()); set_data_device_focus(dh, seat, focus.clone()); set_primary_focus(dh, seat, focus); } fn cursor_image(&mut self, _seat: &Seat, image: CursorImageStatus) { self.cursor_status = image; } fn led_state_changed(&mut self, _seat: &Seat, led_state: LedState) { self.backend_data.update_led_state(led_state) } } delegate_seat!(@ AnvilState); impl TabletSeatHandler for AnvilState { fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, image: CursorImageStatus) { // TODO: tablet tools should have their own cursors self.cursor_status = image; } } delegate_tablet_manager!(@ AnvilState); delegate_text_input_manager!(@ AnvilState); impl InputMethodHandler for AnvilState { fn new_popup(&mut self, surface: PopupSurface) { if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) { warn!("Failed to track popup: {}", err); } } fn popup_repositioned(&mut self, _: PopupSurface) {} fn dismiss_popup(&mut self, surface: PopupSurface) { if let Some(parent) = surface.get_parent().map(|parent| parent.surface.clone()) { let _ = PopupManager::dismiss_popup(&parent, &PopupKind::from(surface)); } } fn parent_geometry(&self, parent: &WlSurface) -> Rectangle { self.space .elements() .find_map(|window| (window.wl_surface().as_deref() == Some(parent)).then(|| window.geometry())) .unwrap_or_default() } } delegate_input_method_manager!(@ AnvilState); impl KeyboardShortcutsInhibitHandler for AnvilState { fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState { &mut self.keyboard_shortcuts_inhibit_state } fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) { // Just grant the wish for everyone inhibitor.activate(); } } delegate_keyboard_shortcuts_inhibit!(@ AnvilState); delegate_virtual_keyboard_manager!(@ AnvilState); delegate_pointer_gestures!(@ AnvilState); delegate_relative_pointer!(@ AnvilState); impl PointerConstraintsHandler for AnvilState { fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { // XXX region let Some(current_focus) = pointer.current_focus() else { return; }; if current_focus.wl_surface().as_deref() == Some(surface) { with_pointer_constraint(surface, pointer, |constraint| { constraint.unwrap().activate(); }); } } fn cursor_position_hint( &mut self, surface: &WlSurface, pointer: &PointerHandle, location: Point, ) { if with_pointer_constraint(surface, pointer, |constraint| { constraint.is_some_and(|c| c.is_active()) }) { let origin = self .space .elements() .find_map(|window| { (window.wl_surface().as_deref() == Some(surface)).then(|| window.geometry()) }) .unwrap_or_default() .loc .to_f64(); pointer.set_location(origin + location); } } } delegate_pointer_constraints!(@ AnvilState); delegate_viewporter!(@ AnvilState); impl XdgActivationHandler for AnvilState { fn activation_state(&mut self) -> &mut XdgActivationState { &mut self.xdg_activation_state } fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool { if let Some((serial, seat)) = data.serial { let keyboard = self.seat.get_keyboard().unwrap(); Seat::from_resource(&seat) == Some(self.seat.clone()) && keyboard .last_enter() .map(|last_enter| serial.is_no_older_than(&last_enter)) .unwrap_or(false) } else { false } } fn request_activation( &mut self, _token: XdgActivationToken, token_data: XdgActivationTokenData, surface: WlSurface, ) { if token_data.timestamp.elapsed().as_secs() < 10 { // Just grant the wish let w = self .space .elements() .find(|window| window.wl_surface().map(|s| *s == surface).unwrap_or(false)) .cloned(); if let Some(window) = w { self.space.raise_element(&window, true); } } } } delegate_xdg_activation!(@ AnvilState); impl XdgDecorationHandler for AnvilState { fn new_decoration(&mut self, toplevel: ToplevelSurface) { use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode; // Set the default to client side toplevel.with_pending_state(|state| { state.decoration_mode = Some(Mode::ClientSide); }); } fn request_mode(&mut self, toplevel: ToplevelSurface, mode: DecorationMode) { use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode; toplevel.with_pending_state(|state| { state.decoration_mode = Some(match mode { DecorationMode::ServerSide => Mode::ServerSide, _ => Mode::ClientSide, }); }); if toplevel.is_initial_configure_sent() { toplevel.send_pending_configure(); } } fn unset_mode(&mut self, toplevel: ToplevelSurface) { use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode; toplevel.with_pending_state(|state| { state.decoration_mode = Some(Mode::ClientSide); }); if toplevel.is_initial_configure_sent() { toplevel.send_pending_configure(); } } } delegate_xdg_decoration!(@ AnvilState); delegate_xdg_shell!(@ AnvilState); delegate_layer_shell!(@ AnvilState); delegate_presentation!(@ AnvilState); impl FractionalScaleHandler for AnvilState { fn new_fractional_scale( &mut self, surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface, ) { // Here we can set the initial fractional scale // // First we look if the surface already has a primary scan-out output, if not // we test if the surface is a subsurface and try to use the primary scan-out output // of the root surface. If the root also has no primary scan-out output we just try // to use the first output of the toplevel. // If the surface is the root we also try to use the first output of the toplevel. // // If all the above tests do not lead to a output we just use the first output // of the space (which in case of anvil will also be the output a toplevel will // initially be placed on) #[allow(clippy::redundant_clone)] let mut root = surface.clone(); while let Some(parent) = get_parent(&root) { root = parent; } with_states(&surface, |states| { let primary_scanout_output = surface_primary_scanout_output(&surface, states) .or_else(|| { if root != surface { with_states(&root, |states| { surface_primary_scanout_output(&root, states).or_else(|| { self.window_for_surface(&root).and_then(|window| { self.space.outputs_for_element(&window).first().cloned() }) }) }) } else { self.window_for_surface(&root) .and_then(|window| self.space.outputs_for_element(&window).first().cloned()) } }) .or_else(|| self.space.outputs().next().cloned()); if let Some(output) = primary_scanout_output { with_fractional_scale(states, |fractional_scale| { fractional_scale.set_preferred_scale(output.current_scale().fractional_scale()); }); } }); } } delegate_fractional_scale!(@ AnvilState); impl SecurityContextHandler for AnvilState { fn context_created(&mut self, source: SecurityContextListenerSource, security_context: SecurityContext) { self.handle .insert_source(source, move |client_stream, _, data| { let client_state = ClientState { security_context: Some(security_context.clone()), ..ClientState::default() }; if let Err(err) = data .display_handle .insert_client(client_stream, Arc::new(client_state)) { warn!("Error adding wayland client: {}", err); }; }) .expect("Failed to init wayland socket source"); } } delegate_security_context!(@ AnvilState); #[cfg(feature = "xwayland")] impl XWaylandKeyboardGrabHandler for AnvilState { fn keyboard_focus_for_xsurface(&self, surface: &WlSurface) -> Option { let elem = self .space .elements() .find(|elem| elem.wl_surface().as_deref() == Some(surface))?; Some(KeyboardFocusTarget::Window(elem.0.clone())) } } #[cfg(feature = "xwayland")] delegate_xwayland_keyboard_grab!(@ AnvilState); #[cfg(feature = "xwayland")] delegate_xwayland_shell!(@ AnvilState); impl XdgForeignHandler for AnvilState { fn xdg_foreign_state(&mut self) -> &mut XdgForeignState { &mut self.xdg_foreign_state } } smithay::delegate_xdg_foreign!(@ AnvilState); smithay::delegate_single_pixel_buffer!(@ AnvilState); smithay::delegate_fifo!(@ AnvilState); smithay::delegate_commit_timing!(@ AnvilState); impl AnvilState { pub fn init( display: Display>, handle: LoopHandle<'static, AnvilState>, backend_data: BackendData, listen_on_socket: bool, ) -> AnvilState { let dh = display.handle(); let clock = Clock::new(); // init wayland clients let socket_name = if listen_on_socket { let source = ListeningSocketSource::new_auto().unwrap(); let socket_name = source.socket_name().to_string_lossy().into_owned(); handle .insert_source(source, |client_stream, _, data| { if let Err(err) = data .display_handle .insert_client(client_stream, Arc::new(ClientState::default())) { warn!("Error adding wayland client: {}", err); }; }) .expect("Failed to init wayland socket source"); info!(name = socket_name, "Listening on wayland socket"); Some(socket_name) } else { None }; handle .insert_source( Generic::new(display, Interest::READ, Mode::Level), |_, display, data| { profiling::scope!("dispatch_clients"); // Safety: we don't drop the display unsafe { display.get_mut().dispatch_clients(data).unwrap(); } Ok(PostAction::Continue) }, ) .expect("Failed to init wayland server source"); // init globals let compositor_state = CompositorState::new::(&dh); let data_device_state = DataDeviceState::new::(&dh); let layer_shell_state = WlrLayerShellState::new::(&dh); let output_manager_state = OutputManagerState::new_with_xdg_output::(&dh); let primary_selection_state = PrimarySelectionState::new::(&dh); let data_control_state = DataControlState::new::(&dh, Some(&primary_selection_state), |_| true); let mut seat_state = SeatState::new(); let shm_state = ShmState::new::(&dh, vec![]); let viewporter_state = ViewporterState::new::(&dh); let xdg_activation_state = XdgActivationState::new::(&dh); let xdg_decoration_state = XdgDecorationState::new::(&dh); let xdg_shell_state = XdgShellState::new::(&dh); let presentation_state = PresentationState::new::(&dh, clock.id() as u32); let fractional_scale_manager_state = FractionalScaleManagerState::new::(&dh); let xdg_foreign_state = XdgForeignState::new::(&dh); let single_pixel_buffer_state = SinglePixelBufferState::new::(&dh); let fifo_manager_state = FifoManagerState::new::(&dh); let commit_timing_manager_state = CommitTimingManagerState::new::(&dh); TextInputManagerState::new::(&dh); InputMethodManagerState::new::(&dh, |_client| true); VirtualKeyboardManagerState::new::(&dh, |_client| true); // Expose global only if backend supports relative motion events if BackendData::HAS_RELATIVE_MOTION { RelativePointerManagerState::new::(&dh); } PointerConstraintsState::new::(&dh); if BackendData::HAS_GESTURES { PointerGesturesState::new::(&dh); } TabletManagerState::new::(&dh); SecurityContextState::new::(&dh, |client| { client .get_data::() .map_or(true, |client_state| client_state.security_context.is_none()) }); // init input let seat_name = backend_data.seat_name(); let mut seat = seat_state.new_wl_seat(&dh, seat_name.clone()); let pointer = seat.add_pointer(); seat.add_keyboard(XkbConfig::default(), 200, 25) .expect("Failed to initialize the keyboard"); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(&dh); #[cfg(feature = "xwayland")] let xwayland_shell_state = xwayland_shell::XWaylandShellState::new::(&dh.clone()); #[cfg(feature = "xwayland")] XWaylandKeyboardGrabState::new::(&dh.clone()); AnvilState { backend_data, display_handle: dh, socket_name, running: Arc::new(AtomicBool::new(true)), handle, space: Space::default(), popups: PopupManager::default(), compositor_state, data_device_state, layer_shell_state, output_manager_state, primary_selection_state, data_control_state, seat_state, keyboard_shortcuts_inhibit_state, shm_state, viewporter_state, xdg_activation_state, xdg_decoration_state, xdg_shell_state, presentation_state, fractional_scale_manager_state, xdg_foreign_state, single_pixel_buffer_state, fifo_manager_state, commit_timing_manager_state, dnd_icon: None, suppressed_keys: Vec::new(), cursor_status: CursorImageStatus::default_named(), seat_name, seat, pointer, clock, #[cfg(feature = "xwayland")] xwayland_shell_state, #[cfg(feature = "xwayland")] xwm: None, #[cfg(feature = "xwayland")] xdisplay: None, #[cfg(feature = "debug")] renderdoc: renderdoc::RenderDoc::new().ok(), show_window_preview: false, } } #[cfg(feature = "xwayland")] pub fn start_xwayland(&mut self) { use std::process::Stdio; use smithay::wayland::compositor::CompositorHandler; let (xwayland, client) = XWayland::spawn( &self.display_handle, None, std::iter::empty::<(String, String)>(), true, Stdio::null(), Stdio::null(), |_| (), ) .expect("failed to start XWayland"); let ret = self .handle .insert_source(xwayland, move |event, _, data| match event { XWaylandEvent::Ready { x11_socket, display_number, } => { let xwayland_scale = std::env::var("ANVIL_XWAYLAND_SCALE") .ok() .and_then(|s| s.parse::().ok()) .unwrap_or(1.); data.client_compositor_state(&client) .set_client_scale(xwayland_scale); let mut wm = X11Wm::start_wm(data.handle.clone(), x11_socket, client.clone()) .expect("Failed to attach X11 Window Manager"); let cursor = Cursor::load(); let image = cursor.get_image(1, Duration::ZERO); wm.set_cursor( &image.pixels_rgba, Size::from((image.width as u16, image.height as u16)), Point::from((image.xhot as u16, image.yhot as u16)), ) .expect("Failed to set xwayland default cursor"); data.xwm = Some(wm); data.xdisplay = Some(display_number); } XWaylandEvent::Error => { warn!("XWayland crashed on startup"); } }); if let Err(e) = ret { tracing::error!("Failed to insert the XWaylandSource into the event loop: {}", e); } } } impl AnvilState { pub fn pre_repaint(&mut self, output: &Output, frame_target: impl Into>) { let frame_target = frame_target.into(); #[allow(clippy::mutable_key_type)] let mut clients: HashMap = HashMap::new(); self.space.elements().for_each(|window| { window.with_surfaces(|surface, states| { if let Some(mut commit_timer_state) = states .data_map .get::() .map(|commit_timer| commit_timer.lock().unwrap()) { commit_timer_state.signal_until(frame_target); let client = surface.client().unwrap(); clients.insert(client.id(), client); } }); }); let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { layer_surface.with_surfaces(|surface, states| { if let Some(mut commit_timer_state) = states .data_map .get::() .map(|commit_timer| commit_timer.lock().unwrap()) { commit_timer_state.signal_until(frame_target); let client = surface.client().unwrap(); clients.insert(client.id(), client); } }); } // Drop the lock to the layer map before calling blocker_cleared, which might end up // calling the commit handler which in turn again could access the layer map. std::mem::drop(map); if let CursorImageStatus::Surface(ref surface) = self.cursor_status { with_surfaces_surface_tree(surface, |surface, states| { if let Some(mut commit_timer_state) = states .data_map .get::() .map(|commit_timer| commit_timer.lock().unwrap()) { commit_timer_state.signal_until(frame_target); let client = surface.client().unwrap(); clients.insert(client.id(), client); } }); } if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) { with_surfaces_surface_tree(surface, |surface, states| { if let Some(mut commit_timer_state) = states .data_map .get::() .map(|commit_timer| commit_timer.lock().unwrap()) { commit_timer_state.signal_until(frame_target); let client = surface.client().unwrap(); clients.insert(client.id(), client); } }); } let dh = self.display_handle.clone(); for client in clients.into_values() { self.client_compositor_state(&client).blocker_cleared(self, &dh); } } pub fn post_repaint( &mut self, output: &Output, time: impl Into, dmabuf_feedback: Option, render_element_states: &RenderElementStates, ) { let time = time.into(); let throttle = Some(Duration::from_secs(1)); #[allow(clippy::mutable_key_type)] let mut clients: HashMap = HashMap::new(); self.space.elements().for_each(|window| { window.with_surfaces(|surface, states| { let primary_scanout_output = surface_primary_scanout_output(surface, states); if let Some(output) = primary_scanout_output.as_ref() { with_fractional_scale(states, |fraction_scale| { fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); }); } if primary_scanout_output .as_ref() .map(|o| o == output) .unwrap_or(true) { let fifo_barrier = states .cached_state .get::() .current() .barrier .take(); if let Some(fifo_barrier) = fifo_barrier { fifo_barrier.signal(); let client = surface.client().unwrap(); clients.insert(client.id(), client); } } }); if self.space.outputs_for_element(window).contains(output) { window.send_frame(output, time, throttle, surface_primary_scanout_output); if let Some(dmabuf_feedback) = dmabuf_feedback.as_ref() { window.send_dmabuf_feedback(output, surface_primary_scanout_output, |surface, _| { select_dmabuf_feedback( surface, render_element_states, &dmabuf_feedback.render_feedback, &dmabuf_feedback.scanout_feedback, ) }); } } }); let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { layer_surface.with_surfaces(|surface, states| { let primary_scanout_output = surface_primary_scanout_output(surface, states); if let Some(output) = primary_scanout_output.as_ref() { with_fractional_scale(states, |fraction_scale| { fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); }); } if primary_scanout_output .as_ref() .map(|o| o == output) .unwrap_or(true) { let fifo_barrier = states .cached_state .get::() .current() .barrier .take(); if let Some(fifo_barrier) = fifo_barrier { fifo_barrier.signal(); let client = surface.client().unwrap(); clients.insert(client.id(), client); } } }); layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output); if let Some(dmabuf_feedback) = dmabuf_feedback.as_ref() { layer_surface.send_dmabuf_feedback(output, surface_primary_scanout_output, |surface, _| { select_dmabuf_feedback( surface, render_element_states, &dmabuf_feedback.render_feedback, &dmabuf_feedback.scanout_feedback, ) }); } } // Drop the lock to the layer map before calling blocker_cleared, which might end up // calling the commit handler which in turn again could access the layer map. std::mem::drop(map); if let CursorImageStatus::Surface(ref surface) = self.cursor_status { with_surfaces_surface_tree(surface, |surface, states| { let primary_scanout_output = surface_primary_scanout_output(surface, states); if let Some(output) = primary_scanout_output.as_ref() { with_fractional_scale(states, |fraction_scale| { fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); }); } if primary_scanout_output .as_ref() .map(|o| o == output) .unwrap_or(true) { let fifo_barrier = states .cached_state .get::() .current() .barrier .take(); if let Some(fifo_barrier) = fifo_barrier { fifo_barrier.signal(); let client = surface.client().unwrap(); clients.insert(client.id(), client); } } }); } if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) { with_surfaces_surface_tree(surface, |surface, states| { let primary_scanout_output = surface_primary_scanout_output(surface, states); if let Some(output) = primary_scanout_output.as_ref() { with_fractional_scale(states, |fraction_scale| { fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); }); } if primary_scanout_output .as_ref() .map(|o| o == output) .unwrap_or(true) { let fifo_barrier = states .cached_state .get::() .current() .barrier .take(); if let Some(fifo_barrier) = fifo_barrier { fifo_barrier.signal(); let client = surface.client().unwrap(); clients.insert(client.id(), client); } } }); } let dh = self.display_handle.clone(); for client in clients.into_values() { self.client_compositor_state(&client).blocker_cleared(self, &dh); } } } pub fn update_primary_scanout_output( space: &Space, output: &Output, dnd_icon: &Option, cursor_status: &CursorImageStatus, render_element_states: &RenderElementStates, ) { space.elements().for_each(|window| { window.with_surfaces(|surface, states| { update_surface_primary_scanout_output( surface, output, states, render_element_states, default_primary_scanout_output_compare, ); }); }); let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { layer_surface.with_surfaces(|surface, states| { update_surface_primary_scanout_output( surface, output, states, render_element_states, default_primary_scanout_output_compare, ); }); } if let CursorImageStatus::Surface(ref surface) = cursor_status { with_surfaces_surface_tree(surface, |surface, states| { update_surface_primary_scanout_output( surface, output, states, render_element_states, default_primary_scanout_output_compare, ); }); } if let Some(surface) = dnd_icon.as_ref().map(|icon| &icon.surface) { with_surfaces_surface_tree(surface, |surface, states| { update_surface_primary_scanout_output( surface, output, states, render_element_states, default_primary_scanout_output_compare, ); }); } } #[derive(Debug, Clone)] pub struct SurfaceDmabufFeedback { pub render_feedback: DmabufFeedback, pub scanout_feedback: DmabufFeedback, } #[profiling::function] pub fn take_presentation_feedback( output: &Output, space: &Space, render_element_states: &RenderElementStates, ) -> OutputPresentationFeedback { let mut output_presentation_feedback = OutputPresentationFeedback::new(output); space.elements().for_each(|window| { if space.outputs_for_element(window).contains(output) { window.take_presentation_feedback( &mut output_presentation_feedback, surface_primary_scanout_output, |surface, _| surface_presentation_feedback_flags_from_states(surface, render_element_states), ); } }); let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { layer_surface.take_presentation_feedback( &mut output_presentation_feedback, surface_primary_scanout_output, |surface, _| surface_presentation_feedback_flags_from_states(surface, render_element_states), ); } output_presentation_feedback } pub trait Backend { const HAS_RELATIVE_MOTION: bool = false; const HAS_GESTURES: bool = false; fn seat_name(&self) -> String; fn reset_buffers(&mut self, output: &Output); fn early_import(&mut self, surface: &WlSurface); fn update_led_state(&mut self, led_state: LedState); }