diff --git a/include/SDL_hints.h b/include/SDL_hints.h index d99fe1373..738f55044 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 76433fc65..ffdda9154 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 6b937b5dc..e9dda276b 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 60a563e18..caba8b4c3 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 a52c44079..2ef0f1452 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 fd1a8a323..e67f706c9 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_ */