From ee1c942a92f236380cc92a417475614c1f6d5b7c Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sun, 12 Apr 2026 12:04:58 -0400
Subject: [PATCH] mouse: Handle explicit warp events
The warp event in Wayland can occur without explicitly warping the mouse, such as if a window changes position under the cursor while the cursor is stationary. Introduce the internal SDL_SendMouseWarp() function to handle updating the absolute cursor position without generating relative motion for these cases.
---
src/events/SDL_mouse.c | 66 ++++++++++++++++++-------
src/events/SDL_mouse_c.h | 4 ++
src/video/wayland/SDL_waylandevents.c | 15 ++++--
src/video/wayland/SDL_waylandevents_c.h | 1 +
src/video/wayland/SDL_waylandmouse.c | 4 +-
5 files changed, 66 insertions(+), 24 deletions(-)
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 62cfb362d6776..e00e67c64ef8d 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -662,6 +662,31 @@ void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouse
SDL_PrivateSendMouseMotion(timestamp, window, mouseID, relative, x, y);
}
+void SDL_SendMouseWarp(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float x, float y)
+{
+ SDL_Mouse *mouse = SDL_GetMouse();
+
+ // Ignore the previous position when we warp, as warps don't generate relative motion.
+ mouse->last_x = x;
+ mouse->last_y = y;
+ mouse->has_position = false;
+
+ if (mouse->relative_mode) {
+ /* Sending motion events when warping while relative mode is active can confuse
+ * clients that don't expect it, so just update the absolute position and don't
+ * generate a motion event unless SDL_HINT_MOUSE_RELATIVE_WARP_MOTION is set.
+ */
+ if (!mouse->relative_mode_warp_motion) {
+ mouse->x = x;
+ mouse->y = y;
+ mouse->has_position = true;
+ return;
+ }
+ }
+
+ SDL_SendMouseMotion(timestamp, window, mouseID, false, x, y);
+}
+
static void ConstrainMousePosition(SDL_Mouse *mouse, SDL_Window *window, float *x, float *y)
{
/* make sure that the pointers find themselves inside the windows,
@@ -1263,24 +1288,29 @@ void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, bool ign
return;
}
- // Ignore the previous position when we warp
- mouse->last_x = x;
- mouse->last_y = y;
- mouse->has_position = false;
-
- if (mouse->relative_mode && !ignore_relative_mode) {
- /* 2.0.22 made warping in relative mode actually functional, which
- * surprised many applications that weren't expecting the additional
- * mouse motion.
- *
- * So for now, warping in relative mode adjusts the absolution position
- * but doesn't generate motion events, unless SDL_HINT_MOUSE_RELATIVE_WARP_MOTION is set.
- */
- if (!mouse->relative_mode_warp_motion) {
- mouse->x = x;
- mouse->y = y;
- mouse->has_position = true;
- return;
+ /* If the backend sends explicit warp events, this will be taken care of if/when the pointer actually warps,
+ * Warps when in relative save the position to be applied when leaving relative mode.
+ */
+ if (!mouse->have_explicit_warp_event || mouse->relative_mode) {
+ // Ignore the previous position when we warp, as warps don't generate relative motion.
+ mouse->last_x = x;
+ mouse->last_y = y;
+ mouse->has_position = false;
+
+ if (mouse->relative_mode && !ignore_relative_mode) {
+ /* 2.0.22 made warping in relative mode actually functional, which
+ * surprised many applications that weren't expecting the additional
+ * mouse motion.
+ *
+ * So for now, warping in relative mode adjusts the absolute position, but
+ * doesn't generate motion events, unless SDL_HINT_MOUSE_RELATIVE_WARP_MOTION is set.
+ */
+ if (!mouse->relative_mode_warp_motion) {
+ mouse->x = x;
+ mouse->y = y;
+ mouse->has_position = true;
+ return;
+ }
}
}
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index 36c458df4335d..26a9d0751aa3f 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -136,6 +136,7 @@ typedef struct
bool warp_emulation_hint;
bool warp_emulation_active;
bool warp_emulation_prohibited;
+ bool have_explicit_warp_event;
Uint64 last_center_warp_time_ns;
bool enable_normal_speed_scale;
float normal_speed_scale;
@@ -210,6 +211,9 @@ extern bool SDL_UpdateMouseCapture(bool force_release);
// Send a mouse motion event
extern void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, bool relative, float x, float y);
+// Send a mouse motion event resulting from a pointer warp
+void SDL_SendMouseWarp(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float x, float y);
+
// Send a mouse button event
extern void SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 button, bool down);
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 2d31def0c7408..2d2e05e7d5978 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -640,7 +640,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
}
}
-static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat)
+static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat, bool warp)
{
SDL_WindowData *window_data = seat->pointer.focus;
SDL_Window *window = window_data ? window_data->sdlwindow : NULL;
@@ -656,7 +656,11 @@ static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat)
sx *= window_data->pointer_scale.x;
sy *= window_data->pointer_scale.y;
- SDL_SendMouseMotion(seat->pointer.pending_frame.timestamp_ns, window_data->sdlwindow, seat->pointer.sdl_id, false, (float)sx, (float)sy);
+ if (!warp) {
+ SDL_SendMouseMotion(seat->pointer.pending_frame.timestamp_ns, window_data->sdlwindow, seat->pointer.sdl_id, false, (float)sx, (float)sy);
+ } else {
+ SDL_SendMouseWarp(seat->pointer.pending_frame.timestamp_ns, window_data->sdlwindow, seat->pointer.sdl_id, (float)sx, (float)sy);
+ }
seat->pointer.last_motion.x = (int)SDL_floor(sx);
seat->pointer.last_motion.y = (int)SDL_floor(sy);
@@ -763,7 +767,7 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
}
} else {
seat->pointer.pending_frame.timestamp_ns = timestamp;
- pointer_dispatch_absolute_motion(seat);
+ pointer_dispatch_absolute_motion(seat, false);
}
}
@@ -790,7 +794,7 @@ static void pointer_dispatch_enter(SDL_WaylandSeat *seat)
SDL_SetMouseFocus(window->sdlwindow);
// Send the initial position.
- pointer_dispatch_absolute_motion(seat);
+ pointer_dispatch_absolute_motion(seat, false);
// Update the pointer grab state.
Wayland_SeatUpdatePointerGrab(seat);
@@ -1280,7 +1284,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
}
if (seat->pointer.pending_frame.have_absolute) {
- pointer_dispatch_absolute_motion(seat);
+ pointer_dispatch_absolute_motion(seat, seat->pointer.pending_frame.have_warp);
}
if (seat->pointer.pending_frame.have_relative) {
@@ -1341,6 +1345,7 @@ static void pointer_handle_warp(void *data, struct wl_pointer *wl_pointer, wl_fi
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
seat->pointer.pending_frame.have_absolute = true;
+ seat->pointer.pending_frame.have_warp = true;
seat->pointer.pending_frame.absolute.sx = surface_x;
seat->pointer.pending_frame.absolute.sy = surface_y;
}
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 42faffd7fab7f..49bae2d8e671b 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -205,6 +205,7 @@ typedef struct SDL_WaylandSeat
{
bool have_absolute;
bool have_relative;
+ bool have_warp;
bool have_axis;
Uint32 buttons_pressed;
diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index dea9d3ae05ca0..c1eaa9b8f3b47 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -1199,7 +1199,7 @@ void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float
}
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_WARP_SINCE_VERSION) {
- SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y);
+ SDL_SendMouseWarp(0, window->sdlwindow, seat->pointer.sdl_id, x, y);
}
}
}
@@ -1383,6 +1383,8 @@ void Wayland_InitMouse(SDL_VideoData *data)
mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
+ mouse->have_explicit_warp_event = true;
+
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
while (r <= SDL_HITTEST_RESIZE_LEFT) {
switch (r) {