Skip to content

Commit

Permalink
Added a hint SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE to control whether …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
slouken committed Aug 22, 2022
1 parent 3ce3594 commit 92b3c53
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 8 deletions.
11 changes: 11 additions & 0 deletions include/SDL_hints.h
Expand Up @@ -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.
*
Expand Down
125 changes: 117 additions & 8 deletions src/events/SDL_mouse.c
Expand Up @@ -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;
}
}
Expand All @@ -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)
{
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
8 changes: 8 additions & 0 deletions src/events/SDL_mouse_c.h
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions src/video/windows/SDL_windowsevents.c
Expand Up @@ -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__)*/
}

Expand Down
105 changes: 105 additions & 0 deletions src/video/windows/SDL_windowsmouse.c
Expand Up @@ -363,6 +363,8 @@ WIN_InitMouse(_THIS)
SDL_SetDefaultCursor(WIN_CreateDefaultCursor());

SDL_blank_cursor = WIN_CreateBlankCursor();

WIN_UpdateMouseSystemScale();
}

void
Expand All @@ -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: */
1 change: 1 addition & 0 deletions src/video/windows/SDL_windowsmouse.h
Expand Up @@ -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_ */

Expand Down

0 comments on commit 92b3c53

Please sign in to comment.