feat: compose client cursor surfaces

This commit is contained in:
Samuel Onoja committed 2026-09-24 16:42:50 +01:00
1 parent bf10a2f838
commit ee9da5d8c9
5 files changed
+67 -11

No files matched your search

+1 -1
View File
@@ -50,10 +50,10 @@ impl CompositorHandler for Ferese {
if let Some(window) = window { if let Some(window) = window {
window.on_commit(); window.on_commit();
self.record_client_commit(&window); self.record_client_commit(&window);
crate::backends::direct::render_all(self);
} }
} }
xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface); xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface);
crate::backends::direct::render_all(self);
} }
} }
+4 -1
View File
@@ -40,7 +40,10 @@ impl SeatHandler for Ferese {
&mut self.seat_state &mut self.seat_state
} }
fn cursor_image(&mut self, _seat: &Seat<Self>, _image: CursorImageStatus) {} fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
self.cursor_status = image;
crate::backends::direct::render_all(self);
}
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) { fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
let client = focused.and_then(|surface| self.display_handle.get_client(surface.id()).ok()); let client = focused.and_then(|surface| self.display_handle.get_client(surface.id()).ok());
+2
View File
@@ -132,6 +132,7 @@ impl Ferese {
); );
pointer.frame(self); pointer.frame(self);
self.activate_focused_pointer_constraint(&pointer); self.activate_focused_pointer_constraint(&pointer);
crate::backends::direct::render_all(self);
} }
InputEvent::PointerMotion { event, .. } => { InputEvent::PointerMotion { event, .. } => {
let pointer = self.seat.get_pointer().expect("seat has a pointer"); let pointer = self.seat.get_pointer().expect("seat has a pointer");
@@ -167,6 +168,7 @@ impl Ferese {
); );
pointer.frame(self); pointer.frame(self);
self.activate_focused_pointer_constraint(&pointer); self.activate_focused_pointer_constraint(&pointer);
crate::backends::direct::render_all(self);
} }
InputEvent::PointerButton { event, .. } => { InputEvent::PointerButton { event, .. } => {
let pointer = self.seat.get_pointer().expect("seat has a pointer"); let pointer = self.seat.get_pointer().expect("seat has a pointer");
+3 -1
View File
@@ -13,7 +13,7 @@ use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstrai
use smithay::{ use smithay::{
backend::drm::DrmEventTime, backend::drm::DrmEventTime,
desktop::{PopupManager, Space, Window, WindowSurfaceType}, desktop::{PopupManager, Space, Window, WindowSurfaceType},
input::{Seat, SeatState}, input::{Seat, SeatState, pointer::CursorImageStatus},
reexports::{ reexports::{
calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic}, calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic},
wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_protocols::xdg::shell::server::xdg_toplevel,
@@ -51,6 +51,7 @@ pub struct Ferese {
pub window_ids: HashMap<Window, WindowId>, pub window_ids: HashMap<Window, WindowId>,
pub window_geometry: HashMap<WindowId, WindowGeometry>, pub window_geometry: HashMap<WindowId, WindowGeometry>,
pub focused_window: Option<WindowId>, pub focused_window: Option<WindowId>,
pub cursor_status: CursorImageStatus,
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>, pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
pub direct_backend: Option<crate::backends::direct::DirectBackendState>, pub direct_backend: Option<crate::backends::direct::DirectBackendState>,
next_window_id: u64, next_window_id: u64,
@@ -111,6 +112,7 @@ impl Ferese {
window_ids: HashMap::new(), window_ids: HashMap::new(),
window_geometry: HashMap::new(), window_geometry: HashMap::new(),
focused_window: None, focused_window: None,
cursor_status: CursorImageStatus::default_named(),
intercepted_keys: HashSet::new(), intercepted_keys: HashSet::new(),
direct_backend: None, direct_backend: None,
next_window_id: 1, next_window_id: 1,
+57 -8
View File
@@ -8,9 +8,10 @@ use smithay::{
renderer::{ renderer::{
damage::OutputDamageTracker, damage::OutputDamageTracker,
element::{ element::{
surface::WaylandSurfaceRenderElement, Kind as RenderElementKind,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
utils::{ utils::{
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate,
RelocateRenderElement, RescaleRenderElement, RelocateRenderElement, RescaleRenderElement,
}, },
}, },
@@ -22,11 +23,12 @@ use smithay::{
space::{ConstrainBehavior, ConstrainReference, constrain_space_element}, space::{ConstrainBehavior, ConstrainReference, constrain_space_element},
utils::OutputPresentationFeedback, utils::OutputPresentationFeedback,
}, },
input::pointer::{CursorImageStatus, CursorImageSurfaceData},
output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
reexports::calloop::EventLoop, reexports::calloop::EventLoop,
reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind, reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind,
utils::{Clock, Logical, Monotonic, Rectangle, Transform}, utils::{Clock, Logical, Monotonic, Physical, Point, Rectangle, Transform},
wayland::presentation::Refresh, wayland::{compositor::with_states, presentation::Refresh},
}; };
use crate::Ferese; use crate::Ferese;
@@ -98,7 +100,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
window.take_presentation_feedback( window.take_presentation_feedback(
&mut presentation, &mut presentation,
|_, _| Some(output.clone()), |_, _| Some(output.clone()),
|_, _| Kind::Vsync, |_, _| PresentationKind::Vsync,
); );
}); });
let rendered = (|| -> Result<(), Box<dyn Error>> { let rendered = (|| -> Result<(), Box<dyn Error>> {
@@ -126,7 +128,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
clock.now(), clock.now(),
Refresh::fixed(Duration::from_nanos(1_000_000_000 / 60)), Refresh::fixed(Duration::from_nanos(1_000_000_000 / 60)),
sequence, sequence,
Kind::Vsync, PresentationKind::Vsync,
); );
state.space.elements().for_each(|window| { state.space.elements().for_each(|window| {
window.send_frame( window.send_frame(
@@ -159,6 +161,8 @@ pub(crate) fn animated_window_elements(
}; };
let scale = output.current_scale().fractional_scale(); let scale = output.current_scale().fractional_scale();
let mut elements = cursor_elements(state, renderer, output_geometry, scale);
elements.extend(
state state
.space .space
.elements() .elements()
@@ -197,7 +201,52 @@ pub(crate) fn animated_window_elements(
}, },
)) ))
}) })
.flatten() .flatten(),
);
elements
}
fn cursor_elements(
state: &Ferese,
renderer: &mut GlesRenderer,
output_geometry: Rectangle<i32, Logical>,
scale: f64,
) -> Vec<AnimatedWindowRenderElement> {
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::<CursorImageSurfaceData>()
.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::<i32, Logical>::from_size(output_geometry.size).to_physical_precise_round(scale);
let origin = Point::<i32, Physical>::default();
render_elements_from_surface_tree::<GlesRenderer, WaylandSurfaceRenderElement<GlesRenderer>>(
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() .collect()
} }