From ee9da5d8c9e3e7de0d992e33b01349c96734d13e Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 24 Sep 2026 16:42:50 +0100 Subject: [PATCH] feat: compose client cursor surfaces --- compositor/src/handlers/compositor.rs | 2 +- compositor/src/handlers/mod.rs | 5 +- compositor/src/input.rs | 2 + compositor/src/state.rs | 4 +- compositor/src/winit.rs | 141 +++++++++++++++++--------- 5 files changed, 105 insertions(+), 49 deletions(-) diff --git a/compositor/src/handlers/compositor.rs b/compositor/src/handlers/compositor.rs index 55a72be..f56089a 100644 --- a/compositor/src/handlers/compositor.rs +++ b/compositor/src/handlers/compositor.rs @@ -50,10 +50,10 @@ impl CompositorHandler for Ferese { if let Some(window) = window { window.on_commit(); self.record_client_commit(&window); - crate::backends::direct::render_all(self); } } xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface); + crate::backends::direct::render_all(self); } } diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index 7bae2e0..64745d4 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -40,7 +40,10 @@ impl SeatHandler for Ferese { &mut self.seat_state } - fn cursor_image(&mut self, _seat: &Seat, _image: CursorImageStatus) {} + fn cursor_image(&mut self, _seat: &Seat, image: CursorImageStatus) { + self.cursor_status = image; + crate::backends::direct::render_all(self); + } fn focus_changed(&mut self, seat: &Seat, focused: Option<&WlSurface>) { let client = focused.and_then(|surface| self.display_handle.get_client(surface.id()).ok()); diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 08cdf70..44fabde 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -132,6 +132,7 @@ impl Ferese { ); pointer.frame(self); self.activate_focused_pointer_constraint(&pointer); + crate::backends::direct::render_all(self); } InputEvent::PointerMotion { event, .. } => { let pointer = self.seat.get_pointer().expect("seat has a pointer"); @@ -167,6 +168,7 @@ impl Ferese { ); pointer.frame(self); self.activate_focused_pointer_constraint(&pointer); + crate::backends::direct::render_all(self); } InputEvent::PointerButton { event, .. } => { let pointer = self.seat.get_pointer().expect("seat has a pointer"); diff --git a/compositor/src/state.rs b/compositor/src/state.rs index f35c84b..f8e014e 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -13,7 +13,7 @@ use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstrai use smithay::{ backend::drm::DrmEventTime, desktop::{PopupManager, Space, Window, WindowSurfaceType}, - input::{Seat, SeatState}, + input::{Seat, SeatState, pointer::CursorImageStatus}, reexports::{ calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic}, wayland_protocols::xdg::shell::server::xdg_toplevel, @@ -51,6 +51,7 @@ pub struct Ferese { pub window_ids: HashMap, pub window_geometry: HashMap, pub focused_window: Option, + pub cursor_status: CursorImageStatus, pub intercepted_keys: HashSet, pub direct_backend: Option, next_window_id: u64, @@ -111,6 +112,7 @@ impl Ferese { window_ids: HashMap::new(), window_geometry: HashMap::new(), focused_window: None, + cursor_status: CursorImageStatus::default_named(), intercepted_keys: HashSet::new(), direct_backend: None, next_window_id: 1, diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index a00883a..16769b9 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -8,9 +8,10 @@ use smithay::{ renderer::{ damage::OutputDamageTracker, element::{ - surface::WaylandSurfaceRenderElement, + Kind as RenderElementKind, + surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, utils::{ - ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, + ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement, }, }, @@ -22,11 +23,12 @@ use smithay::{ space::{ConstrainBehavior, ConstrainReference, constrain_space_element}, utils::OutputPresentationFeedback, }, + input::pointer::{CursorImageStatus, CursorImageSurfaceData}, output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, reexports::calloop::EventLoop, - reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind, - utils::{Clock, Logical, Monotonic, Rectangle, Transform}, - wayland::presentation::Refresh, + reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind, + utils::{Clock, Logical, Monotonic, Physical, Point, Rectangle, Transform}, + wayland::{compositor::with_states, presentation::Refresh}, }; use crate::Ferese; @@ -98,7 +100,7 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() window.take_presentation_feedback( &mut presentation, |_, _| Some(output.clone()), - |_, _| Kind::Vsync, + |_, _| PresentationKind::Vsync, ); }); let rendered = (|| -> Result<(), Box> { @@ -126,7 +128,7 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() clock.now(), Refresh::fixed(Duration::from_nanos(1_000_000_000 / 60)), sequence, - Kind::Vsync, + PresentationKind::Vsync, ); state.space.elements().for_each(|window| { window.send_frame( @@ -159,46 +161,93 @@ pub(crate) fn animated_window_elements( }; let scale = output.current_scale().fractional_scale(); - state - .space - .elements() - .rev() - .filter_map(|window| { - let id = state.window_ids.get(window)?; - let visual = state.window_geometry.get(id)?.visual.current; - let constrain = Rectangle::::new( - ( - (visual.x - f64::from(output_geometry.loc.x)).round() as i32, - (visual.y - f64::from(output_geometry.loc.y)).round() as i32, - ) - .into(), - ( - visual.width.round().max(1.0) as i32, - visual.height.round().max(1.0) as i32, - ) - .into(), - ); + let mut elements = cursor_elements(state, renderer, output_geometry, scale); + elements.extend( + state + .space + .elements() + .rev() + .filter_map(|window| { + let id = state.window_ids.get(window)?; + let visual = state.window_geometry.get(id)?.visual.current; + let constrain = Rectangle::::new( + ( + (visual.x - f64::from(output_geometry.loc.x)).round() as i32, + (visual.y - f64::from(output_geometry.loc.y)).round() as i32, + ) + .into(), + ( + visual.width.round().max(1.0) as i32, + visual.height.round().max(1.0) as i32, + ) + .into(), + ); - Some(constrain_space_element::< - GlesRenderer, - _, - AnimatedWindowRenderElement, - >( - renderer, - window, - constrain.loc, - 1.0, - scale, - constrain, - ConstrainBehavior { - reference: ConstrainReference::Geometry, - behavior: ConstrainScaleBehavior::Zoom, - align: ConstrainAlign::TOP | ConstrainAlign::LEFT, - }, - )) - }) - .flatten() - .collect() + Some(constrain_space_element::< + GlesRenderer, + _, + AnimatedWindowRenderElement, + >( + renderer, + window, + constrain.loc, + 1.0, + scale, + constrain, + ConstrainBehavior { + reference: ConstrainReference::Geometry, + behavior: ConstrainScaleBehavior::Zoom, + align: ConstrainAlign::TOP | ConstrainAlign::LEFT, + }, + )) + }) + .flatten(), + ); + elements +} + +fn cursor_elements( + state: &Ferese, + renderer: &mut GlesRenderer, + output_geometry: Rectangle, + scale: f64, +) -> Vec { + let CursorImageStatus::Surface(surface) = &state.cursor_status else { + return Vec::new(); + }; + let Some(pointer) = state.seat.get_pointer() else { + return Vec::new(); + }; + let hotspot = with_states(surface, |states| { + states + .data_map + .get::() + .map(|attributes| attributes.lock().unwrap().hotspot) + .unwrap_or_default() + }); + let logical_location = + pointer.current_location() - output_geometry.loc.to_f64() - hotspot.to_f64(); + let physical_location = logical_location.to_physical_precise_round(scale); + let output_crop = + Rectangle::::from_size(output_geometry.size).to_physical_precise_round(scale); + let origin = Point::::default(); + + render_elements_from_surface_tree::>( + renderer, + surface, + physical_location, + scale, + 1.0, + RenderElementKind::Cursor, + ) + .into_iter() + .filter_map(|element| { + let element = RescaleRenderElement::from_element(element, origin, 1.0); + let element = RelocateRenderElement::from_element(element, origin, Relocate::Relative); + + CropRenderElement::from_element(element, scale, output_crop) + }) + .collect() } fn normalized_scale(scale: f64) -> f64 {