feat(wayland): add ability to hide the cursor over a surface

also now the surfaces say what the cursor over them should look like,
previously one cursor shape was selected globally for the seat
This commit is contained in:
Hojjat 2026-08-27 19:26:19 -06:00 • committed by Ashley Wulber
parent 65e9faa43c
commit aca94cf2af
7 changed files with 111 additions and 9 deletions

View file

@ -177,13 +177,41 @@ impl SctkEventLoop {
}
}
crate::platform_specific::Action::SetCursor(
surface,
icon,
) => {
if let Some(seat) = state.seats.get_mut(0) {
state.record_cursor(surface.clone(), |r| {
r.icon = Some(icon);
r.hidden = false;
});
if let Some(seat) = state
.seats
.get_mut(0)
.filter(|s| s.is_over(&surface))
{
seat.icon = Some(icon);
seat.hidden = false;
seat.set_cursor(&state.connection, icon);
}
}
crate::platform_specific::Action::SetCursorVisible(
surface,
visible,
) => {
state.record_cursor(surface.clone(), |r| {
r.hidden = !visible;
});
if let Some(seat) = state
.seats
.get_mut(0)
.filter(|s| s.is_over(&surface))
{
seat.set_cursor_visible(
&state.connection,
visible,
);
}
}
crate::platform_specific::Action::RequestRedraw(
id,
) => {
@ -383,6 +411,7 @@ impl SctkEventLoop {
touch_points: HashMap::new(),
sctk_events: Vec::new(),
frame_status: HashMap::new(),
cursor_requests: HashMap::new(),
fractional_scaling_manager,
viewporter_state,
compositor_updates: Default::default(),

View file

@ -123,6 +123,13 @@ use wayland_protocols::{
pub static TOKEN_CTR: AtomicU32 = AtomicU32::new(0);
/// The requested cursor for a surface
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) struct CursorRequest {
pub(crate) icon: Option<CursorIcon>,
pub(crate) hidden: bool,
}
#[derive(Debug)]
pub(crate) struct SctkSeat {
pub(crate) seat: WlSeat,
@ -139,6 +146,8 @@ pub(crate) struct SctkSeat {
pub(crate) active_icon: Option<CursorIcon>,
// Cursor icon set by application
pub(crate) icon: Option<CursorIcon>,
// Application asked for cursor to hide
pub(crate) hidden: bool,
}
impl SctkSeat {
@ -148,6 +157,35 @@ impl SctkSeat {
self.active_icon = Some(icon);
}
}
/// Is the pointer currently over `surface`.
pub(crate) fn is_over(&self, surface: &ObjectId) -> bool {
self.ptr_focus.as_ref().is_some_and(|s| &s.id() == surface)
}
/// Set cursor visibility over this surfaces.
pub(crate) fn set_cursor_visible(
&mut self,
conn: &Connection,
visible: bool,
) {
let Some(ptr) = self.ptr.as_ref() else {
return;
};
self.hidden = !visible;
if visible {
let icon = self.icon.unwrap_or(CursorIcon::Default);
_ = ptr.set_cursor(conn, icon);
self.active_icon = Some(icon);
} else {
// Ignoring the error
// It only fails when the pointer is not on our surface (due to serial error)
_ = ptr.hide_cursor();
self.active_icon = None;
}
}
}
#[derive(Debug, Clone)]
@ -471,6 +509,7 @@ pub struct SctkState {
pub(crate) session_lock_state: SessionLockState,
pub(crate) session_lock: Option<SessionLock>,
pub(crate) id_map: HashMap<ObjectId, core::window::Id>,
pub(crate) cursor_requests: HashMap<ObjectId, CursorRequest>,
pub(crate) to_commit: HashMap<core::window::Id, WlSurface>,
pub(crate) destroyed: HashSet<core::window::Id>,
pub(crate) pending_popup: Option<(SctkPopupSettings, usize)>,
@ -573,6 +612,17 @@ pub(crate) fn receive_frame(
}
impl SctkState {
/// What a surface wants the pointer over it to be.
pub(crate) fn record_cursor(
&mut self,
surface: ObjectId,
amend: impl FnOnce(&mut CursorRequest),
) {
self.cursor_requests
.retain(|id, _| id == &surface || self.id_map.contains_key(id));
amend(self.cursor_requests.entry(surface).or_default());
}
pub fn request_redraw(&mut self, surface: &WlSurface) {
let e = self
.frame_status

View file

@ -31,7 +31,7 @@ impl PointerHandler for SctkState {
// track events, but only forward for the active seat
for e in events {
if my_seat.active_icon != my_seat.icon {
if !my_seat.hidden && my_seat.active_icon != my_seat.icon {
// Restore cursor that was set by appliction, or default
my_seat.set_cursor(
conn,
@ -60,10 +60,19 @@ impl PointerHandler for SctkState {
match e.kind {
PointerEventKind::Enter { .. } => {
_ = my_seat.ptr_focus.replace(e.surface.clone());
if let Some(want) =
self.cursor_requests.get(&e.surface.id())
{
my_seat.icon = want.icon;
my_seat.set_cursor_visible(conn, !want.hidden);
}
}
PointerEventKind::Leave { .. } => {
_ = my_seat.ptr_focus.take();
_ = my_seat.active_icon = None;
// The next surface the pointer lands on decides what it
// looks like there
my_seat.hidden = false;
}
PointerEventKind::Press {
time,

View file

@ -38,6 +38,7 @@ impl SeatHandler for SctkState {
last_touch_down: None,
icon: None,
active_icon: None,
hidden: false,
});
}
@ -65,6 +66,7 @@ impl SeatHandler for SctkState {
last_touch_down: None,
icon: None,
active_icon: None,
hidden: false,
});
self.seats.last_mut().unwrap()
}

View file

@ -32,7 +32,8 @@ use winit::window::ImePurpose;
pub(crate) enum Action {
Action(iced_runtime::platform_specific::wayland::Action),
SetCursor(CursorIcon),
SetCursor(ObjectId, CursorIcon),
SetCursorVisible(ObjectId, bool),
RequestRedraw(ObjectId),
TrackWindow(Arc<dyn winit::window::Window>, window::Id),
ResizeWindow(window::Id),
@ -48,9 +49,14 @@ impl std::fmt::Debug for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Action(arg0) => f.debug_tuple("Action").field(arg0).finish(),
Self::SetCursor(arg0) => {
f.debug_tuple("SetCursor").field(arg0).finish()
Self::SetCursor(arg0, arg1) => {
f.debug_tuple("SetCursor").field(arg0).field(arg1).finish()
}
Self::SetCursorVisible(arg0, arg1) => f
.debug_tuple("SetCursorVisible")
.field(arg0)
.field(arg1)
.finish(),
Self::RequestRedraw(arg0) => {
f.debug_tuple("RequestRedraw").field(arg0).finish()
}

View file

@ -83,7 +83,10 @@ impl winit::window::Window for SctkWinitWindow {
fn set_cursor(&self, cursor: winit_core::cursor::Cursor) {
match cursor {
winit_core::cursor::Cursor::Icon(icon) => {
_ = self.tx.send(Action::SetCursor(icon));
_ = self.tx.send(Action::SetCursor(
self.surface.wl_surface().id(),
icon,
));
}
winit_core::cursor::Cursor::Custom(_) => {
// TODO
@ -92,7 +95,10 @@ impl winit::window::Window for SctkWinitWindow {
}
fn set_cursor_visible(&self, visible: bool) {
// TODO
_ = self.tx.send(Action::SetCursorVisible(
self.surface.wl_surface().id(),
visible,
));
}
fn surface_size(&self) -> winit::dpi::PhysicalSize<u32> {

View file

@ -291,11 +291,11 @@ where
pub fn update_mouse(&mut self, interaction: mouse::Interaction) {
if interaction != self.mouse_interaction {
if let Some(icon) = conversion::mouse_interaction(interaction) {
self.raw.set_cursor(icon.into());
if self.mouse_interaction == mouse::Interaction::Hidden {
self.raw.set_cursor_visible(true);
}
self.raw.set_cursor(icon.into());
} else {
self.raw.set_cursor_visible(false);
}