From e9f7a1b359f3b5844053827272c26214d2836f01 Mon Sep 17 00:00:00 2001 From: expikr <77922942+expikr@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:08:09 +0800 Subject: [PATCH] fix relative system scale function on Windows source: https://web.archive.org/web/20161202223814/https://ihme.org/~orbik/random_stuff/donewmouseaccel.png Co-Authored-By: Sam Lantinga --- src/events/SDL_mouse.c | 127 ++------------ src/events/SDL_mouse_c.h | 9 +- src/video/windows/SDL_windowsmouse.c | 244 +++++++++++++++++---------- 3 files changed, 177 insertions(+), 203 deletions(-) diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index a62c1f9d1..2923e8b52 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -588,111 +588,6 @@ void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouse SDL_PrivateSendMouseMotion(timestamp, window, mouseID, relative, x, y); } -static float CalculateSystemScale(SDL_Mouse *mouse, SDL_Window *window, const float *x, const float *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) { - scale = v[0]; - } else { - speed = SDL_sqrtf((*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])); - } - } -#ifdef SDL_PLATFORM_WIN32 - { - // On Windows the mouse speed is affected by the content scale - SDL_VideoDisplay *display; - - if (window) { - display = SDL_GetVideoDisplayForWindow(window); - } else { - display = SDL_GetVideoDisplay(SDL_GetPrimaryDisplay()); - } - if (display) { - scale *= display->content_scale; - } - } -#endif - return scale; -} - -// You can set either a single scale, or a set of {speed, scale} values in ascending order -bool 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 true; - } - - 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 false; - } - SDL_memcpy(v, values, num_values * sizeof(*values)); - - mouse->num_system_scale_values = num_values; - mouse->system_scale_values = v; - return true; -} - -static void GetScaledMouseDeltas(SDL_Mouse *mouse, SDL_Window *window, float *x, float *y) -{ - if (mouse->relative_mode) { - if (mouse->enable_relative_speed_scale) { - *x *= mouse->relative_speed_scale; - *y *= mouse->relative_speed_scale; - } else if (mouse->enable_relative_system_scale && mouse->num_system_scale_values > 0) { - float relative_system_scale = CalculateSystemScale(mouse, window, x, y); - *x *= relative_system_scale; - *y *= relative_system_scale; - } - } else { - if (mouse->enable_normal_speed_scale) { - *x *= mouse->normal_speed_scale; - *y *= mouse->normal_speed_scale; - } - } -} - static void ConstrainMousePosition(SDL_Mouse *mouse, SDL_Window *window, float *x, float *y) { /* make sure that the pointers find themselves inside the windows, @@ -787,7 +682,21 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL } if (relative) { - GetScaledMouseDeltas(mouse, window, &x, &y); + if (mouse->relative_mode) { + if (mouse->enable_relative_speed_scale) { + x *= mouse->relative_speed_scale; + y *= mouse->relative_speed_scale; + } else if (mouse->enable_relative_system_scale) { + if (mouse->ApplySystemScale) { + mouse->ApplySystemScale(mouse->system_scale_data, timestamp, window, mouseID, &x, &y); + } + } + } else { + if (mouse->enable_normal_speed_scale) { + x *= mouse->normal_speed_scale; + y *= mouse->normal_speed_scale; + } + } xrel = x; yrel = y; x = (mouse->last_x + xrel); @@ -1118,12 +1027,6 @@ void SDL_QuitMouse(void) } mouse->num_clickstates = 0; - if (mouse->system_scale_values) { - SDL_free(mouse->system_scale_values); - mouse->system_scale_values = NULL; - } - mouse->num_system_scale_values = 0; - SDL_RemoveHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME, SDL_MouseDoubleClickTimeChanged, mouse); diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index 9c324b4e7..6b646a72b 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -82,6 +82,10 @@ typedef struct // Get absolute mouse coordinates. (x) and (y) are never NULL and set to zero before call. SDL_MouseButtonFlags (*GetGlobalMouseState)(float *x, float *y); + // Platform-specific system mouse transform + void (*ApplySystemScale)(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y); + void *system_scale_data; + // Data common to all mice SDL_Window *focus; float x; @@ -104,8 +108,6 @@ typedef struct bool enable_relative_speed_scale; float relative_speed_scale; bool enable_relative_system_scale; - int num_system_scale_values; - float *system_scale_values; Uint32 double_click_time; int double_click_radius; bool touch_mouse_events; @@ -162,9 +164,6 @@ extern void SDL_SetMouseFocus(SDL_Window *window); // Update the mouse capture window extern bool SDL_UpdateMouseCapture(bool force_release); -// You can set either a single scale, or a set of {speed, scale} values in sorted order -extern bool SDL_SetMouseSystemScale(int num_values, const float *values); - // Send a mouse motion event extern void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, bool relative, float x, float y); diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c index a6b807295..3e09b92be 100644 --- a/src/video/windows/SDL_windowsmouse.c +++ b/src/video/windows/SDL_windowsmouse.c @@ -29,6 +29,7 @@ #include "../SDL_video_c.h" #include "../../events/SDL_mouse_c.h" #include "../../joystick/usb_ids.h" +#include "../../core/windows/SDL_windows.h" // for checking windows version typedef struct CachedCursor @@ -47,9 +48,22 @@ struct SDL_CursorData HCURSOR cursor; }; +typedef struct +{ + Uint64 xs[5]; + Uint64 ys[5]; + Sint64 residual[2]; + Uint32 dpiscale; + Uint32 dpidenom; + int last_node; + bool enhanced; + bool dpiaware; +} WIN_MouseData; + DWORD SDL_last_warp_time = 0; HCURSOR SDL_cursor = NULL; static SDL_Cursor *SDL_blank_cursor = NULL; +static WIN_MouseData WIN_system_scale_data; static SDL_Cursor *WIN_CreateCursorAndData(HCURSOR hcursor) { @@ -521,6 +535,95 @@ static SDL_MouseButtonFlags WIN_GetGlobalMouseState(float *x, float *y) return result; } +static void WIN_ApplySystemScale(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y) +{ + if (!internal) { + return; + } + WIN_MouseData *data = (WIN_MouseData *)internal; + + SDL_VideoDisplay *display = window ? SDL_GetVideoDisplayForWindow(window) : SDL_GetVideoDisplay(SDL_GetPrimaryDisplay()); + + Sint64 ix = (Sint64)*x * 65536; + Sint64 iy = (Sint64)*y * 65536; + Uint32 dpi = display ? (Uint32)(display->content_scale * USER_DEFAULT_SCREEN_DPI) : USER_DEFAULT_SCREEN_DPI; + + if (!data->enhanced) { // early return if flat scale + dpi = data->dpiscale * (data->dpiaware ? dpi : USER_DEFAULT_SCREEN_DPI); + ix *= dpi; + iy *= dpi; + ix /= USER_DEFAULT_SCREEN_DPI; + iy /= USER_DEFAULT_SCREEN_DPI; + ix /= 32; + iy /= 32; + // data->residual[0] += ix; + // data->residual[1] += iy; + // ix = 65536 * (data->residual[0] / 65536); + // iy = 65536 * (data->residual[1] / 65536); + // data->residual[0] -= ix; + // data->residual[1] -= iy; + *x = (float)ix / 65536.0f; + *y = (float)iy / 65536.0f; + return; + } + + Uint64 *xs = data->xs; + Uint64 *ys = data->ys; + Uint64 absx = SDL_abs(ix); + Uint64 absy = SDL_abs(iy); + Uint64 speed = SDL_min(absx, absy) + (SDL_max(absx, absy) << 1); // super cursed approximation used by Windows + if (speed == 0) { + return; + } + + int i, j, k; + for (i = 1; i < 5; i++) { + j = i; + if (speed < xs[j]) { + break; + } + } + i -= 1; + j -= 1; + k = data->last_node; + data->last_node = j; + + Uint32 denom = data->dpidenom; + Sint64 scale = 0; + Sint64 xdiff = xs[j+1] - xs[j]; + Sint64 ydiff = ys[j+1] - ys[j]; + if (xdiff != 0) { + Sint64 slope = ydiff / xdiff; + Sint64 inter = slope * xs[i] - ys[i]; + scale += slope - inter / speed; + } + + if (j > k) { + denom <<= 1; + xdiff = xs[k+1] - xs[k]; + ydiff = ys[k+1] - ys[k]; + if (xdiff != 0) { + Sint64 slope = ydiff / xdiff; + Sint64 inter = slope * xs[k] - ys[k]; + scale += slope - inter / speed; + } + } + + scale *= dpi; + ix *= scale; + iy *= scale; + ix /= denom; + iy /= denom; + // data->residual[0] += ix; + // data->residual[1] += iy; + // ix = 65536 * (data->residual[0] / 65536); + // iy = 65536 * (data->residual[1] / 65536); + // data->residual[0] -= ix; + // data->residual[1] -= iy; + *x = (float)ix / 65536.0f; + *y = (float)iy / 65536.0f; +} + void WIN_InitMouse(SDL_VideoDevice *_this) { SDL_Mouse *mouse = SDL_GetMouse(); @@ -534,6 +637,8 @@ void WIN_InitMouse(SDL_VideoDevice *_this) mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode; mouse->CaptureMouse = WIN_CaptureMouse; mouse->GetGlobalMouseState = WIN_GetGlobalMouseState; + mouse->ApplySystemScale = WIN_ApplySystemScale; + mouse->system_scale_data = &WIN_system_scale_data; SDL_SetDefaultCursor(WIN_CreateDefaultCursor()); @@ -550,101 +655,68 @@ void WIN_QuitMouse(SDL_VideoDevice *_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 bool LoadFiveFixedPointFloats(const BYTE *bytes, float *values) +static void ReadMouseCurve(int v, Uint64 xs[5], Uint64 ys[5]) { - 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 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 = USER_DEFAULT_SCREEN_DPI; // 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 + bool win8 = WIN_IsWindows8OrGreater(); + DWORD xbuff[10] = { + 0x00000000, 0, + 0x00006e15, 0, + 0x00014000, 0, + 0x0003dc29, 0, + 0x00280000, 0 }; - - if (mouse_speed > 0 && mouse_speed < SDL_arraysize(mouse_speed_scale)) { - SDL_SetMouseSystemScale(1, &mouse_speed_scale[mouse_speed]); + DWORD ybuff[10] = { + 0x00000000, 0, + win8 ? 0x000111fd : 0x00015eb8, 0, + win8 ? 0x00042400 : 0x00054ccd, 0, + win8 ? 0x0012fc00 : 0x00184ccd, 0, + win8 ? 0x01bbc000 : 0x02380000, 0 + }; + DWORD xsize = sizeof(xbuff); + DWORD ysize = sizeof(ybuff); + HKEY open_handle; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Mouse", 0, KEY_READ, &open_handle) == ERROR_SUCCESS) { + RegQueryValueExW(open_handle, L"SmoothMouseXCurve", NULL, NULL, (BYTE*)xbuff, &xsize); + RegQueryValueExW(open_handle, L"SmoothMouseYCurve", NULL, NULL, (BYTE*)ybuff, &ysize); + RegCloseKey(open_handle); + } + xs[0] = 0; // first node must always be origin + ys[0] = 0; // first node must always be origin + int i; + for (i = 1; i < 5; i++) { + xs[i] = (7 * (Uint64)xbuff[i*2]); + ys[i] = (v * (Uint64)ybuff[i*2]) << 17; } } void WIN_UpdateMouseSystemScale(void) { - int mouse_speed; - int params[3] = { 0, 0, 0 }; + SDL_Mouse *mouse = SDL_GetMouse(); - if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &mouse_speed, 0) && - SystemParametersInfo(SPI_GETMOUSE, 0, params, 0)) { + if (mouse->ApplySystemScale == WIN_ApplySystemScale) { + mouse->system_scale_data = &WIN_system_scale_data; + } + + // always reinitialize to valid defaults, whether fetch was successful or not. + WIN_MouseData *data = &WIN_system_scale_data; + data->residual[0] = 0; + data->residual[1] = 0; + data->dpiscale = 32; + data->dpidenom = (10 * (WIN_IsWindows8OrGreater() ? 120 : 150)) << 16; + data->dpiaware = WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice()); + data->enhanced = false; + + int v = 10; + if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &v, 0)) { + v = SDL_max(1, SDL_min(v, 20)); + data->dpiscale = SDL_max(SDL_max(v, (v - 2) << 2), (v - 6) << 3); + } + + int params[3]; + if (SystemParametersInfo(SPI_GETMOUSE, 0, ¶ms, 0)) { + data->enhanced = params[2] ? true : false; if (params[2]) { - WIN_SetEnhancedMouseScale(mouse_speed); - } else { - WIN_SetLinearMouseScale(mouse_speed); + ReadMouseCurve(v, data->xs, data->ys); } } }