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
+105 -49

No files matched your search

+1 -1
View File
@@ -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);
}
}
+4 -1
View File
@@ -40,7 +40,10 @@ impl SeatHandler for Ferese {
&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>) {
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);
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");
+3 -1
View File
@@ -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<Window, WindowId>,
pub window_geometry: HashMap<WindowId, WindowGeometry>,
pub focused_window: Option<WindowId>,
pub cursor_status: CursorImageStatus,
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
pub direct_backend: Option<crate::backends::direct::DirectBackendState>,
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,
+95 -46
View File
@@ -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<Ferese>, state: &mut Ferese) -> Result<()
window.take_presentation_feedback(
&mut presentation,
|_, _| Some(output.clone()),
|_, _| Kind::Vsync,
|_, _| PresentationKind::Vsync,
);
});
let rendered = (|| -> Result<(), Box<dyn Error>> {
@@ -126,7 +128,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, 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::<i32, Logical>::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::<i32, Logical>::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<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()
}
fn normalized_scale(scale: f64) -> f64 {