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.
This commit is contained in:
Sam Lantinga 2022-08-22 16:25:25 -07:00
parent 3ce3594e38
commit 92b3c53c92
6 changed files with 249 additions and 8 deletions

View file

@ -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.
*

View file

@ -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);

View file

@ -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);

View file

@ -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__)*/
}

View file

@ -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: */

View file

@ -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_ */