From 799d2ca80b05073d10d4344cb8cafedd1eb9c4bd Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 15 Jan 2026 22:51:29 -0500
Subject: [PATCH] wayland: Don't clear the cursor on leave events
Stop the frame callback and flag the cursor for a refresh when the pointer re-enters the surface, but don't set a null cursor, as it may have already been set after entering a surface that is part of the window decorations, resulting in an unwanted invisible cursor.
(cherry picked from commit 5e2977709b316dc25809dfe2a40469250af68e15)
---
src/video/wayland/SDL_waylandevents_c.h | 3 ++
src/video/wayland/SDL_waylandmouse.c | 59 ++++++++++++++++---------
2 files changed, 40 insertions(+), 22 deletions(-)
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 6b0bca47df78c..873ca73f8ded8 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -73,7 +73,10 @@ typedef struct SDL_WaylandCursorState
Uint64 last_frame_callback_time_ms;
Uint32 current_frame_time_ms;
+
+ // 0 or greater if a buffer is attached, -1 if in the reset state.
int current_frame;
+
SDL_HitTestResult hit_test_result;
} SDL_WaylandCursorState;
diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index 18d8ccd9e2409..61e3e71d805c7 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -900,7 +900,8 @@ static void Wayland_FreeCursorData(SDL_CursorData *d)
if (seat->pointer.current_cursor == d) {
Wayland_CursorStateDestroyFrameCallback(&seat->pointer.cursor_state);
- if (seat->pointer.cursor_state.surface) {
+ // Custom cursor buffers are about to be destroyed, so ensure they are detached.
+ if (!d->is_system_cursor && seat->pointer.cursor_state.surface) {
wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0);
}
@@ -1040,7 +1041,7 @@ static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wa
}
if (cursor) {
- if (cursor_data == state->current_cursor) {
+ if (cursor_data == state->current_cursor && state->current_frame >= 0) {
// Restart the animation sequence if the cursor didn't change.
if (cursor_data->num_frames > 1) {
Wayland_CursorStateResetAnimation(state, true);
@@ -1075,6 +1076,7 @@ static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wa
const enum wp_cursor_shape_device_v1_shape shape = Wayland_GetSystemCursorShape(cursor_data->cursor_data.system.id);
wp_cursor_shape_device_v1_set_shape(state->cursor_shape, serial, shape);
state->current_cursor = cursor_data;
+ state->current_frame = 0;
return;
}
@@ -1107,6 +1109,7 @@ static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wa
struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, 0);
wl_surface_attach(state->surface, buffer, 0, 0);
+ state->current_frame = 0;
if (state->scale != 1.0) {
if (!state->viewport) {
@@ -1156,6 +1159,13 @@ static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wa
}
}
+static void Wayland_CursorStateResetCursor(SDL_WaylandCursorState *state)
+{
+ // Stop the frame callback and set the reset status.
+ Wayland_CursorStateDestroyFrameCallback(state);
+ state->current_frame = -1;
+}
+
static bool Wayland_ShowCursor(SDL_Cursor *cursor)
{
SDL_VideoDevice *vd = SDL_GetVideoDevice();
@@ -1170,7 +1180,7 @@ static bool Wayland_ShowCursor(SDL_Cursor *cursor)
if (mouse->focus && mouse->focus->internal == seat->pointer.focus) {
Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, cursor);
} else if (!seat->pointer.focus) {
- Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, NULL);
+ Wayland_CursorStateResetCursor(&seat->pointer.cursor_state);
}
SDL_WaylandPenTool *tool;
@@ -1184,7 +1194,7 @@ static bool Wayland_ShowCursor(SDL_Cursor *cursor)
if (tool->focus && (!mouse->focus || mouse->focus->internal == tool->focus)) {
Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, mouse->cur_cursor);
} else if (!tool->focus) {
- Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, NULL);
+ Wayland_CursorStateResetCursor(&tool->cursor_state);
}
}
}
@@ -1506,24 +1516,25 @@ void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat)
.is_pointer = true
};
- if (pointer_focus && mouse->cursor_visible) {
- if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) {
- const SDL_HitTestResult rc = pointer_focus->hit_test_result;
+ if (pointer_focus) {
+ if (mouse->cursor_visible) {
+ if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) {
+ const SDL_HitTestResult rc = pointer_focus->hit_test_result;
- if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
- Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->cur_cursor);
+ if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
+ Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->cur_cursor);
+ } else {
+ Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, sys_cursors[rc]);
+ }
} else {
- Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, sys_cursors[rc]);
+ // Hide the cursor in relative mode, unless requested otherwise by the hint.
+ Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL);
}
} else {
- // Hide the cursor in relative mode, unless requested otherwise by the hint.
Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL);
}
} else {
- /* The spec states "The cursor actually changes only if the input device focus is one of the
- * requesting client's surfaces", so just clear the cursor if the seat has no pointer focus.
- */
- Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL);
+ Wayland_CursorStateResetCursor(&seat->pointer.cursor_state);
}
}
@@ -1536,18 +1547,22 @@ void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool)
.is_pointer = false
};
- if (tool_focus && mouse->cursor_visible) {
- // Relative mode is only relevant if the tool sends pointer events.
- const bool relative = mouse->pen_mouse_events && (tool_focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE);
+ if (tool_focus) {
+ if (mouse->cursor_visible) {
+ // Relative mode is only relevant if the tool sends pointer events.
+ const bool relative = mouse->pen_mouse_events && (tool_focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE);
- if (!relative || !mouse->relative_mode_hide_cursor) {
- Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, mouse->cur_cursor);
+ if (!relative || !mouse->relative_mode_hide_cursor) {
+ Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, mouse->cur_cursor);
+ } else {
+ // Hide the cursor in relative mode, unless requested otherwise by the hint.
+ Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL);
+ }
} else {
- // Hide the cursor in relative mode, unless requested otherwise by the hint.
Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL);
}
} else {
- Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL);
+ Wayland_CursorStateResetCursor(&tool->cursor_state);
}
}