SDL: Added the hint SDL_HINT_MOUSE_RELATIVE_WARP_MOTION

From 3861c557da1494e91f33e328ba29ad5d25246e1c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 11 Aug 2022 13:58:39 -0700
Subject: [PATCH] Added the hint SDL_HINT_MOUSE_RELATIVE_WARP_MOTION

This hint controls whether mouse warping generates motion events in relative mode, and defaults off.

Fixes https://github.com/libsdl-org/SDL/issues/6034
Fixes https://github.com/libsdl-org/SDL/issues/5741
---
 WhatsNew.txt             |  1 +
 include/SDL_hints.h      | 11 +++++++++
 include/SDL_mouse.h      |  2 +-
 src/events/SDL_mouse.c   | 51 ++++++++++++++++++++++++++++------------
 src/events/SDL_mouse_c.h |  1 +
 5 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index 2b780d09c03..198ae783c4e 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -21,6 +21,7 @@ General:
 * Added SDL_bsearch(), SDL_crc16(), and  SDL_utf8strnlen() to the stdlib routines
 * Added SDL_size_mul_overflow() and SDL_size_add_overflow() for better size overflow protection
 * Added SDL_ResetHint() to reset a hint to the default value
+* Added the hint SDL_HINT_MOUSE_RELATIVE_WARP_MOTION to control whether mouse warping generates motion events in relative mode. This hint defaults off.
 * The hint SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS now defaults on
 * Added support for mini-gamepad mode for Nintendo Joy-Con controllers using the HIDAPI driver
 * Added the hint SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS to control whether Joy-Con controllers are automatically merged into a unified gamepad when using the HIDAPI driver. This hint defaults on.
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 32f65c3d9cc..e7cddba0391 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1064,6 +1064,17 @@ extern "C" {
  */
 #define SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE    "SDL_MOUSE_RELATIVE_SPEED_SCALE"
 
+/**
+ *  \brief  A variable controlling whether a motion event should be generated for mouse warping in relative mode.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - Warping the mouse will not generate a motion event in relative mode
+ *    "1"       - Warping the mouse will generate a motion event in relative mode
+ *
+ *  By default warping the mouse will not generate motion events in relative mode. This avoids the application having to filter out large relative motion due to warping.
+ */
+#define SDL_HINT_MOUSE_RELATIVE_WARP_MOTION  "SDL_MOUSE_RELATIVE_WARP_MOTION"
+
 /**
  *  \brief  A variable controlling whether mouse events should generate synthetic touch events
  *
diff --git a/include/SDL_mouse.h b/include/SDL_mouse.h
index 4683e2597d4..a9a00bd96e3 100644
--- a/include/SDL_mouse.h
+++ b/include/SDL_mouse.h
@@ -154,7 +154,7 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetRelativeMouseState(int *x, int *y);
 /**
  * Move the mouse cursor to the given position within the window.
  *
- * This function generates a mouse motion event.
+ * This function generates a mouse motion event if relative mode is not enabled. If relative mode is enabled, you can force mouse events for the warp by setting the SDL_HINT_MOUSE_RELATIVE_WARP_MOTION hint.
  *
  * Note that this function will appear to succeed, but not actually move the
  * mouse when used over Microsoft Remote Desktop.
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 45dffc77968..ba486eefe59 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -161,6 +161,14 @@ SDL_MouseAutoCaptureChanged(void *userdata, const char *name, const char *oldVal
     }
 }
 
+static void SDLCALL
+SDL_MouseRelativeWarpMotionChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_Mouse *mouse = (SDL_Mouse *)userdata;
+
+    mouse->relative_mode_warp_motion = SDL_GetStringBoolean(hint, SDL_FALSE);
+}
+
 /* Public functions */
 int
 SDL_MouseInit(void)
@@ -195,6 +203,9 @@ SDL_MouseInit(void)
     SDL_AddHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE,
                         SDL_MouseAutoCaptureChanged, mouse);
 
+    SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION,
+                        SDL_MouseRelativeWarpMotionChanged, mouse);
+
     mouse->was_touch_mouse_events = SDL_FALSE; /* no touch to mouse movement event pending */
 
     mouse->cursor_shown = SDL_TRUE;
@@ -377,13 +388,16 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
         if (x == center_x && y == center_y) {
             mouse->last_x = center_x;
             mouse->last_y = center_y;
-            return 0;
-        }
-        if (window && (window->flags & SDL_WINDOW_INPUT_FOCUS) != 0) {
-            if (mouse->WarpMouse) {
-                mouse->WarpMouse(window, center_x, center_y);
-            } else {
-                SDL_PrivateSendMouseMotion(window, mouseID, 0, center_x, center_y);
+            if (!mouse->relative_mode_warp_motion) {
+                return 0;
+            }
+        } else {
+            if (window && (window->flags & SDL_WINDOW_INPUT_FOCUS) != 0) {
+                if (mouse->WarpMouse) {
+                    mouse->WarpMouse(window, center_x, center_y);
+                } else {
+                    SDL_PrivateSendMouseMotion(window, mouseID, 0, center_x, center_y);
+                }
             }
         }
     }
@@ -810,6 +824,9 @@ SDL_MouseQuit(void)
 
     SDL_DelHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE,
                         SDL_MouseAutoCaptureChanged, mouse);
+
+    SDL_DelHintCallback(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION,
+                        SDL_MouseRelativeWarpMotionChanged, mouse);
 }
 
 Uint32
@@ -883,23 +900,27 @@ SDL_PerformWarpMouseInWindow(SDL_Window *window, int x, int y, SDL_bool ignore_r
         return;
     }
 
+    /* Ignore the previous position when we warp */
+    mouse->last_x = x;
+    mouse->last_y = y;
+    mouse->has_position = SDL_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.
+         * but doesn't generate motion events, unless SDL_HINT_MOUSE_RELATIVE_WARP_MOTION is set.
          */
-        mouse->x = x;
-        mouse->y = y;
-        mouse->has_position = SDL_TRUE;
-        return;
+        if (!mouse->relative_mode_warp_motion) {
+            mouse->x = x;
+            mouse->y = y;
+            mouse->has_position = SDL_TRUE;
+            return;
+        }
     }
 
-    /* Ignore the previous position when we warp */
-    mouse->has_position = SDL_FALSE;
-
     if (mouse->WarpMouse &&
         (!mouse->relative_mode || mouse->relative_mode_warp)) {
         mouse->WarpMouse(window, x, y);
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index 70c320851ba..6b937b5dce7 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -91,6 +91,7 @@ typedef struct
     SDL_bool has_position;
     SDL_bool relative_mode;
     SDL_bool relative_mode_warp;
+    SDL_bool relative_mode_warp_motion;
     float normal_speed_scale;
     float relative_speed_scale;
     float scale_accum_x;