From 92b3c53c92971e685254fd89f89ce6bde8cea60e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 22 Aug 2022 16:25:25 -0700
Subject: [PATCH] Added a hint SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE to control
whether to use system mouse acceleration on raw relative motion.
This is currently only implemented on Windows, and "Enhanced pointer
precision" mode is not quite correct.
---
include/SDL_hints.h | 11 +++
src/events/SDL_mouse.c | 125 ++++++++++++++++++++++++--
src/events/SDL_mouse_c.h | 8 ++
src/video/windows/SDL_windowsevents.c | 7 ++
src/video/windows/SDL_windowsmouse.c | 105 ++++++++++++++++++++++
src/video/windows/SDL_windowsmouse.h | 1 +
6 files changed, 249 insertions(+), 8 deletions(-)
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index d99fe1373f5..738f55044b0 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1106,6 +1106,17 @@ extern "C" {
*/
#define SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE "SDL_MOUSE_RELATIVE_SPEED_SCALE"
+/**
+ * \brief A variable controlling whether the system mouse acceleration curve is used for relative mouse motion.
+ *
+ * This variable can be set to the following values:
+ * "0" - Relative mouse motion will be unscaled (the default)
+ * "1" - Relative mouse motion will be scaled using the system mouse acceleration curve.
+ *
+ * If SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE is set, that will override the system speed scale.
+ */
+#define SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE "SDL_MOUSE_RELATIVE_SYSTEM_SCALE"
+
/**
* \brief A variable controlling whether a motion event should be generated for mouse warping in relative mode.
*
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 76433fc6502..ffdda915461 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -83,8 +83,10 @@ SDL_MouseNormalSpeedScaleChanged(void *userdata, const char *name, const char *o
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
if (hint && *hint) {
+ mouse->enable_normal_speed_scale = SDL_TRUE;
mouse->normal_speed_scale = (float)SDL_atof(hint);
} else {
+ mouse->enable_normal_speed_scale = SDL_FALSE;
mouse->normal_speed_scale = 1.0f;
}
}
@@ -95,12 +97,22 @@ SDL_MouseRelativeSpeedScaleChanged(void *userdata, const char *name, const char
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
if (hint && *hint) {
+ mouse->enable_relative_speed_scale = SDL_TRUE;
mouse->relative_speed_scale = (float)SDL_atof(hint);
} else {
+ mouse->enable_relative_speed_scale = SDL_FALSE;
mouse->relative_speed_scale = 1.0f;
}
}
+static void SDLCALL
+SDL_MouseRelativeSystemScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+ SDL_Mouse *mouse = (SDL_Mouse *)userdata;
+
+ mouse->enable_relative_system_scale = SDL_GetStringBoolean(hint, SDL_FALSE);
+}
+
static void SDLCALL
SDL_TouchMouseEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
@@ -189,6 +201,9 @@ SDL_MouseInit(void)
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE,
SDL_MouseRelativeSpeedScaleChanged, mouse);
+ SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
+ SDL_MouseRelativeSystemScaleChanged, mouse);
+
SDL_AddHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
SDL_TouchMouseEventsChanged, mouse);
@@ -344,7 +359,10 @@ SDL_SendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int
static int
GetScaledMouseDelta(float scale, int value, float *accum)
{
- if (scale != 1.0f) {
+ if (value && scale != 1.0f) {
+ if ((value > 0) != (*accum > 0)) {
+ *accum = 0.0f;
+ }
*accum += scale * value;
if (*accum >= 0.0f) {
value = (int)SDL_floor(*accum);
@@ -356,6 +374,100 @@ GetScaledMouseDelta(float scale, int value, float *accum)
return value;
}
+static float
+CalculateSystemScale(SDL_Mouse *mouse, int *x, int *y)
+{
+ int i;
+ int n = mouse->num_system_scale_values;
+ float *v = mouse->system_scale_values;
+ float speed, coef, scale;
+
+ /* If we're using a single scale value, return that */
+ if (n == 1) {
+ return v[0];
+ }
+
+ speed = SDL_sqrtf((float)(*x * *x) + (*y * *y));
+ for (i = 0; i < (n - 2); i += 2) {
+ if (speed < v[i + 2]) {
+ break;
+ }
+ }
+ if (i == (n - 2)) {
+ scale = v[n - 1];
+ } else if (speed <= v[i]) {
+ scale = v[i + 1];
+ } else {
+ coef = (speed - v[i]) / (v[i + 2] - v[i]);
+ scale = v[i + 1] + (coef * (v[i + 3] - v[i + 1]));
+ }
+ SDL_Log("speed = %.2f, scale = %.2f\n", speed, scale);
+ return scale;
+}
+
+/* You can set either a single scale, or a set of {speed, scale} values in ascending order */
+int
+SDL_SetMouseSystemScale(int num_values, const float *values)
+{
+ SDL_Mouse *mouse = SDL_GetMouse();
+ float *v;
+
+ if (num_values == mouse->num_system_scale_values &&
+ SDL_memcmp(values, mouse->system_scale_values, num_values * sizeof(*values)) == 0) {
+ /* Nothing has changed */
+ return 0;
+ }
+
+ if (num_values < 1) {
+ return SDL_SetError("You must have at least one scale value");
+ }
+
+ if (num_values > 1) {
+ /* Validate the values */
+ int i;
+
+ if (num_values < 4 || (num_values % 2) != 0) {
+ return SDL_SetError("You must pass a set of {speed, scale} values");
+ }
+
+ for (i = 0; i < (num_values - 2); i += 2) {
+ if (values[i] >= values[i + 2]) {
+ return SDL_SetError("Speed values must be in ascending order");
+ }
+ }
+ }
+
+ v = (float *)SDL_realloc(mouse->system_scale_values, num_values * sizeof(*values));
+ if (!v) {
+ return SDL_OutOfMemory();
+ }
+ SDL_memcpy(v, values, num_values * sizeof(*values));
+
+ mouse->num_system_scale_values = num_values;
+ mouse->system_scale_values = v;
+ return 0;
+}
+
+static void
+GetScaledMouseDeltas(SDL_Mouse *mouse, int *x, int *y)
+{
+ if (mouse->relative_mode) {
+ if (mouse->enable_relative_speed_scale) {
+ *x = GetScaledMouseDelta(mouse->relative_speed_scale, *x, &mouse->scale_accum_x);
+ *y = GetScaledMouseDelta(mouse->relative_speed_scale, *y, &mouse->scale_accum_y);
+ } else if (mouse->enable_relative_system_scale && mouse->num_system_scale_values > 0) {
+ float relative_system_scale = CalculateSystemScale(mouse, x, y);
+ *x = GetScaledMouseDelta(relative_system_scale, *x, &mouse->scale_accum_x);
+ *y = GetScaledMouseDelta(relative_system_scale, *y, &mouse->scale_accum_y);
+ }
+ } else {
+ if (mouse->enable_normal_speed_scale) {
+ *x = GetScaledMouseDelta(mouse->normal_speed_scale, *x, &mouse->scale_accum_x);
+ *y = GetScaledMouseDelta(mouse->normal_speed_scale, *y, &mouse->scale_accum_y);
+ }
+ }
+}
+
static int
SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
{
@@ -405,13 +517,7 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
}
if (relative) {
- if (mouse->relative_mode) {
- x = GetScaledMouseDelta(mouse->relative_speed_scale, x, &mouse->scale_accum_x);
- y = GetScaledMouseDelta(mouse->relative_speed_scale, y, &mouse->scale_accum_y);
- } else {
- x = GetScaledMouseDelta(mouse->normal_speed_scale, x, &mouse->scale_accum_x);
- y = GetScaledMouseDelta(mouse->normal_speed_scale, y, &mouse->scale_accum_y);
- }
+ GetScaledMouseDeltas(mouse, &x, &y);
xrel = x;
yrel = y;
x = (mouse->last_x + xrel);
@@ -818,6 +924,9 @@ SDL_MouseQuit(void)
SDL_DelHintCallback(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE,
SDL_MouseRelativeSpeedScaleChanged, mouse);
+ SDL_DelHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
+ SDL_MouseRelativeSystemScaleChanged, mouse);
+
SDL_DelHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
SDL_TouchMouseEventsChanged, mouse);
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index 6b937b5dce7..e9dda276bf4 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -92,8 +92,13 @@ typedef struct
SDL_bool relative_mode;
SDL_bool relative_mode_warp;
SDL_bool relative_mode_warp_motion;
+ SDL_bool enable_normal_speed_scale;
float normal_speed_scale;
+ SDL_bool enable_relative_speed_scale;
float relative_speed_scale;
+ SDL_bool enable_relative_system_scale;
+ int num_system_scale_values;
+ float *system_scale_values;
float scale_accum_x;
float scale_accum_y;
Uint32 double_click_time;
@@ -141,6 +146,9 @@ extern void SDL_SetMouseFocus(SDL_Window * window);
/* Update the mouse capture window */
extern int SDL_UpdateMouseCapture(SDL_bool force_release);
+/* You can set either a single scale, or a set of {speed, scale} values in sorted order */
+extern int SDL_SetMouseSystemScale(int num_values, const float *values);
+
/* Send a mouse motion event */
extern int SDL_SendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y);
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 60a563e1890..caba8b4c316 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -1628,6 +1628,13 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return 0;
}
break;
+
+ case WM_SETTINGCHANGE:
+ if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
+ WIN_UpdateMouseSystemScale();
+ }
+ break;
+
#endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/
}
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index a52c4407948..2ef0f14520b 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -363,6 +363,8 @@ WIN_InitMouse(_THIS)
SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
SDL_blank_cursor = WIN_CreateBlankCursor();
+
+ WIN_UpdateMouseSystemScale();
}
void
@@ -379,6 +381,109 @@ WIN_QuitMouse(_THIS)
}
}
+/* For a great description of how the enhanced mouse curve works, see:
+ * https://superuser.com/questions/278362/windows-mouse-acceleration-curve-smoothmousexcurve-and-smoothmouseycurve
+ * http://www.esreality.com/?a=post&id=1846538/
+ */
+static SDL_bool
+LoadFiveFixedPointFloats(BYTE *bytes, float *values)
+{
+ int i;
+
+ for (i = 0; i < 5; ++i) {
+ float fraction = (float)((Uint16) bytes[1] << 8 | bytes[0]) / 65535.0f;
+ float value = (float)(((Uint16)bytes[3] << 8) | bytes[2]) + fraction;
+ *values++ = value;
+ bytes += 8;
+ }
+ return SDL_TRUE;
+}
+
+static void
+WIN_SetEnhancedMouseScale(int mouse_speed)
+{
+ float scale = (float) mouse_speed / 10.0f;
+ HKEY hKey;
+ DWORD dwType = REG_BINARY;
+ BYTE value[40];
+ DWORD length = sizeof(value);
+ int i;
+ float xpoints[5];
+ float ypoints[5];
+ float scale_points[10];
+ const int dpi = 96; // FIXME, how do we handle different monitors with different DPI?
+ const float display_factor = 3.5f * (150.0f / dpi);
+
+ if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Mouse", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
+ if (RegQueryValueExW(hKey, L"SmoothMouseXCurve", 0, &dwType, value, &length) == ERROR_SUCCESS &&
+ LoadFiveFixedPointFloats(value, xpoints) &&
+ RegQueryValueExW(hKey, L"SmoothMouseYCurve", 0, &dwType, value, &length) == ERROR_SUCCESS &&
+ LoadFiveFixedPointFloats(value, ypoints)) {
+ for (i = 0; i < 5; ++i) {
+ float gain;
+ if (xpoints[i] > 0.0f) {
+ gain = (ypoints[i] / xpoints[i]) * scale;
+ } else {
+ gain = 0.0f;
+ }
+ scale_points[i * 2] = xpoints[i];
+ scale_points[i * 2 + 1] = gain / display_factor;
+ //SDL_Log("Point %d = %f,%f\n", i, scale_points[i * 2], scale_points[i * 2 + 1]);
+ }
+ SDL_SetMouseSystemScale(SDL_arraysize(scale_points), scale_points);
+ }
+ RegCloseKey(hKey);
+ }
+}
+
+static void
+WIN_SetLinearMouseScale(int mouse_speed)
+{
+ static float mouse_speed_scale[] = {
+ 0.0f,
+ 1 / 32.0f,
+ 1 / 16.0f,
+ 1 / 8.0f,
+ 2 / 8.0f,
+ 3 / 8.0f,
+ 4 / 8.0f,
+ 5 / 8.0f,
+ 6 / 8.0f,
+ 7 / 8.0f,
+ 1.0f,
+ 1.25f,
+ 1.5f,
+ 1.75f,
+ 2.0f,
+ 2.25f,
+ 2.5f,
+ 2.75f,
+ 3.0f,
+ 3.25f,
+ 3.5f
+ };
+
+ if (mouse_speed > 0 && mouse_speed < SDL_arraysize(mouse_speed_scale)) {
+ SDL_SetMouseSystemScale(1, &mouse_speed_scale[mouse_speed]);
+ }
+}
+
+void
+WIN_UpdateMouseSystemScale()
+{
+ int mouse_speed;
+ int params[3] = { 0, 0, 0 };
+
+ if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &mouse_speed, 0) &&
+ SystemParametersInfo(SPI_GETMOUSE, 0, params, 0)) {
+ if (params[2]) {
+ WIN_SetEnhancedMouseScale(mouse_speed);
+ } else {
+ WIN_SetLinearMouseScale(mouse_speed);
+ }
+ }
+}
+
#endif /* SDL_VIDEO_DRIVER_WINDOWS */
/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/video/windows/SDL_windowsmouse.h b/src/video/windows/SDL_windowsmouse.h
index fd1a8a323b0..e67f706c923 100644
--- a/src/video/windows/SDL_windowsmouse.h
+++ b/src/video/windows/SDL_windowsmouse.h
@@ -29,6 +29,7 @@ extern HCURSOR SDL_cursor;
extern void WIN_InitMouse(_THIS);
extern void WIN_QuitMouse(_THIS);
extern void WIN_SetCursorPos(int x, int y);
+extern void WIN_UpdateMouseSystemScale();
#endif /* SDL_windowsmouse_h_ */