streamline cursor clipping logic on windows (#11237)

This commit does the following:
- add logic in the `WM_MOUSEMOVE` case of the Window to conditionally call `WIN_UpdateClipCursor` upon receiving cursor motion if SDL is expecting the mouse to be clipped in some way (Fixes #7890)
- remove Windows-specific periodic refresh of cursor clipping and its `SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL` hint (superceded by the above bullet point)
- streamline the processing logic within `WIN_UpdateClipCursor` for better readability of each branch, and avoid calling the Platform API until it is absolutely necessary.
- move `relative_mouse_center` field from Windows-specific per-window `SDL_WindowData` to the global `SDL_Mouse` struct, and the corresponding hint callbacks to `SDL_mouse.c` instead of `SDL_windowswindow.c`
This commit is contained in:
expikr
2024-12-19 09:25:06 +08:00
committed by GitHub
parent 35a9d156a6
commit 345cab1e36
6 changed files with 144 additions and 166 deletions

View File

@@ -2614,23 +2614,6 @@ extern "C" {
*/
#define SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE "SDL_MOUSE_RELATIVE_CURSOR_VISIBLE"
/**
* Controls how often SDL issues cursor confinement commands to the operating
* system while relative mode is active, in case the desired confinement state
* became out-of-sync due to interference from other running programs.
*
* The variable can be integers representing milliseconds between each
* refresh. A value of zero means SDL will not automatically refresh the
* confinement. The default value varies depending on the operating system,
* this variable might not have any effects on inapplicable platforms such as
* those without a cursor.
*
* This hint can be set anytime.
*
* \since This hint is available since SDL 3.1.3.
*/
#define SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL "SDL_MOUSE_RELATIVE_CLIP_INTERVAL"
/**
* A variable controlling whether mouse events should generate synthetic touch
* events.

View File

@@ -65,17 +65,6 @@ static void SDLCALL SDL_MouseDoubleClickTimeChanged(void *userdata, const char *
}
}
static void SDLCALL SDL_MouseRelativeClipIntervalChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
if (hint && *hint) {
mouse->relative_mode_clip_interval = SDL_atoi(hint);
} else {
mouse->relative_mode_clip_interval = 3000;
}
}
static void SDLCALL SDL_MouseDoubleClickRadiusChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
@@ -113,6 +102,13 @@ static void SDLCALL SDL_MouseRelativeSpeedScaleChanged(void *userdata, const cha
}
}
static void SDLCALL SDL_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
mouse->relative_mode_center = SDL_GetStringBoolean(hint, true);
}
static void SDLCALL SDL_MouseRelativeSystemScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
@@ -226,6 +222,9 @@ bool SDL_PreInitMouse(void)
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
SDL_MouseRelativeSystemScaleChanged, mouse);
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER,
SDL_MouseRelativeModeCenterChanged, mouse);
SDL_AddHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
SDL_MouseWarpEmulationChanged, mouse);
@@ -249,9 +248,6 @@ bool SDL_PreInitMouse(void)
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
SDL_MouseRelativeCursorVisibleChanged, mouse);
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL,
SDL_MouseRelativeClipIntervalChanged, mouse);
mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending
mouse->cursor_shown = true;
@@ -1055,6 +1051,9 @@ void SDL_QuitMouse(void)
SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
SDL_MouseRelativeSystemScaleChanged, mouse);
SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER,
SDL_MouseRelativeModeCenterChanged, mouse);
SDL_RemoveHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
SDL_MouseWarpEmulationChanged, mouse);
@@ -1073,9 +1072,6 @@ void SDL_QuitMouse(void)
SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
SDL_MouseRelativeCursorVisibleChanged, mouse);
SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL,
SDL_MouseRelativeClipIntervalChanged, mouse);
for (int i = SDL_mouse_count; i--; ) {
SDL_RemoveMouse(SDL_mice[i].instance_id, false);
}

View File

@@ -98,11 +98,11 @@ typedef struct
bool relative_mode_warp;
bool relative_mode_warp_motion;
bool relative_mode_cursor_visible;
bool relative_mode_center;
bool warp_emulation_hint;
bool warp_emulation_active;
bool warp_emulation_prohibited;
Uint64 last_center_warp_time_ns;
int relative_mode_clip_interval;
bool enable_normal_speed_scale;
float normal_speed_scale;
bool enable_relative_speed_scale;

View File

@@ -350,8 +350,6 @@ static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus)
WIN_UpdateWindowICCProfile(data->window, true);
} else {
RECT rect;
data->in_window_deactivation = true;
SDL_SetKeyboardFocus(NULL);
@@ -361,10 +359,7 @@ static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus)
}
WIN_ResetDeadKeys();
if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) {
ClipCursor(NULL);
SDL_zero(data->cursor_clipped_rect);
}
WIN_UnclipCursorForWindow(window);
data->in_window_deactivation = false;
}
@@ -1078,6 +1073,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
case WM_NCACTIVATE:
{
// Don't immediately clip the cursor in case we're clicking minimize/maximize buttons
// This is the only place that this flag is set. This causes all subsequent calls to
// WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping.
// This flag is unset at the end of message pumping each frame for every window, and
// should never be carried over between frames.
data->skip_update_clipcursor = true;
/* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without
@@ -1120,7 +1119,18 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
case WM_MOUSEMOVE:
{
/* SDL_Mouse *mouse = SDL_GetMouse(); */
SDL_Window *window = data->window;
if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
bool wish_clip_cursor = (
window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) ||
(window->mouse_rect.w > 0 && window->mouse_rect.h > 0)
);
if (wish_clip_cursor) {
data->skip_update_clipcursor = false;
WIN_UpdateClipCursor(window);
}
}
if (!data->mouse_tracked) {
TRACKMOUSEEVENT trackMouseEvent;
@@ -1138,7 +1148,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
// Only generate mouse events for real mouse
if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
lParam != data->last_pointer_update) {
SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
}
}
} break;
@@ -2068,28 +2078,6 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
static void WIN_UpdateClipCursorForWindows(void)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
SDL_Window *window;
Uint64 now = SDL_GetTicks();
const int CLIPCURSOR_UPDATE_INTERVAL_MS = SDL_GetMouse()->relative_mode_clip_interval;
if (_this) {
for (window = _this->windows; window; window = window->next) {
SDL_WindowData *data = window->internal;
if (data) {
if (data->skip_update_clipcursor) {
data->skip_update_clipcursor = false;
WIN_UpdateClipCursor(window);
} else if (CLIPCURSOR_UPDATE_INTERVAL_MS > 0 && now >= (data->last_updated_clipcursor + CLIPCURSOR_UPDATE_INTERVAL_MS)) {
WIN_UpdateClipCursor(window);
}
}
}
}
}
static void WIN_UpdateMouseCapture(void)
{
SDL_Window *focusWindow = SDL_GetKeyboardFocus();
@@ -2284,7 +2272,17 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
}
// Update the clipping rect in case someone else has stolen it
WIN_UpdateClipCursorForWindows();
if (_this) {
SDL_Window *window = _this->windows;
while (window) {
SDL_WindowData *data = window->internal;
if (data && data->skip_update_clipcursor) {
data->skip_update_clipcursor = false;
WIN_UpdateClipCursor(window);
}
window = window->next;
}
}
// Update mouse capture
WIN_UpdateMouseCapture();

View File

@@ -386,12 +386,6 @@ bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRec
return result;
}
static void SDLCALL WIN_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_WindowData *data = (SDL_WindowData *)userdata;
data->mouse_relative_mode_center = SDL_GetStringBoolean(hint, true);
}
static SDL_WindowEraseBackgroundMode GetEraseBackgroundModeHint(void)
{
const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE);
@@ -443,6 +437,14 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn
data->dwma_border_color = DWMWA_COLOR_DEFAULT;
data->hint_erase_background_mode = GetEraseBackgroundModeHint();
// WIN_WarpCursor() jitters by +1, and remote desktop warp wobble is +/- 1
LONG remote_desktop_adjustment = GetSystemMetrics(SM_REMOTESESSION) ? 2 : 0;
data->cursor_ctrlock_rect.left = 0 - remote_desktop_adjustment;
data->cursor_ctrlock_rect.top = 0;
data->cursor_ctrlock_rect.right = 1 + remote_desktop_adjustment;
data->cursor_ctrlock_rect.bottom = 1;
if (SDL_GetHintBoolean("SDL_WINDOW_RETAIN_CONTENT", false)) {
data->copybits_flag = 0;
} else {
@@ -453,8 +455,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn
SDL_Log("SetupWindowData: initialized data->scaling_dpi to %d", data->scaling_dpi);
#endif
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, WIN_MouseRelativeModeCenterChanged, data);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
// Associate the data with the window
if (!SetProp(hwnd, TEXT("SDL_WindowData"), data)) {
@@ -626,7 +626,6 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window)
SDL_WindowData *data = window->internal;
if (data) {
SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, WIN_MouseRelativeModeCenterChanged, data);
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
if (data->drop_target) {
@@ -1588,107 +1587,109 @@ static BOOL GetClientScreenRect(HWND hwnd, RECT *rect)
ClientToScreen(hwnd, (LPPOINT)rect + 1); // POINT( right , bottom )
}
void WIN_UnclipCursorForWindow(SDL_Window *window) {
SDL_WindowData *data = window->internal;
RECT rect;
if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) {
ClipCursor(NULL);
SDL_zero(data->cursor_clipped_rect);
}
}
void WIN_UpdateClipCursor(SDL_Window *window)
{
SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
SDL_WindowData *data = window->internal;
SDL_Mouse *mouse = SDL_GetMouse();
RECT rect, clipped_rect;
if (data->in_title_click || data->focus_click_pending) {
return;
}
if (data->skip_update_clipcursor) {
return;
}
if (!GetClipCursor(&clipped_rect)) {
if (data->in_title_click || data->focus_click_pending || data->skip_update_clipcursor) {
return;
}
if ((mouse->relative_mode || (window->flags & SDL_WINDOW_MOUSE_GRABBED) ||
(window->mouse_rect.w > 0 && window->mouse_rect.h > 0)) &&
(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
if (mouse->relative_mode && !mouse->relative_mode_warp && data->mouse_relative_mode_center) {
if (GetClientScreenRect(data->hwnd, &rect)) {
// WIN_WarpCursor() jitters by +1, and remote desktop warp wobble is +/- 1
LONG remote_desktop_adjustment = GetSystemMetrics(SM_REMOTESESSION) ? 2 : 0;
LONG cx, cy;
SDL_Rect mouse_rect = window->mouse_rect;
bool win_mouse_rect = (mouse_rect.w > 0 && mouse_rect.h > 0);
bool win_have_focus = (window->flags & SDL_WINDOW_INPUT_FOCUS);
bool win_is_grabbed = (window->flags & SDL_WINDOW_MOUSE_GRABBED);
bool win_in_relmode = (window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE);
bool cursor_confine = win_in_relmode || win_is_grabbed || win_mouse_rect;
cx = (rect.left + rect.right) / 2;
cy = (rect.top + rect.bottom) / 2;
// Make an absurdly small clip rect
rect.left = cx - remote_desktop_adjustment;
rect.right = cx + 1 + remote_desktop_adjustment;
rect.top = cy;
rect.bottom = cy + 1;
if (SDL_memcmp(&rect, &clipped_rect, sizeof(rect)) != 0) {
if (ClipCursor(&rect)) {
data->cursor_clipped_rect = rect;
}
}
}
} else {
if (GetClientScreenRect(data->hwnd, &rect)) {
if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
SDL_Rect mouse_rect_win_client;
RECT mouse_rect, intersection;
// mouse_rect_win_client is the mouse rect in Windows client space
mouse_rect_win_client = window->mouse_rect;
// mouse_rect is the rect in Windows screen space
mouse_rect.left = rect.left + mouse_rect_win_client.x;
mouse_rect.top = rect.top + mouse_rect_win_client.y;
mouse_rect.right = mouse_rect.left + mouse_rect_win_client.w;
mouse_rect.bottom = mouse_rect.top + mouse_rect_win_client.h;
if (IntersectRect(&intersection, &rect, &mouse_rect)) {
SDL_memcpy(&rect, &intersection, sizeof(rect));
} else if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
// Mouse rect was invalid, just do the normal grab
} else {
SDL_zero(rect);
}
}
if (SDL_memcmp(&rect, &clipped_rect, sizeof(rect)) != 0) {
if (!WIN_IsRectEmpty(&rect)) {
if (ClipCursor(&rect)) {
data->cursor_clipped_rect = rect;
}
} else {
ClipCursor(NULL);
SDL_zero(data->cursor_clipped_rect);
}
}
}
// This is verbatim translation of the old logic,
// but I don't quite get what it's trying to do.
// A clean-room implementation according to MSDN
// documentation of GetClipCursor is provided in
// a commented-out block below.
if (!win_have_focus || !cursor_confine) {
SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
RECT current;
if (!GetClipCursor(&current)) {
return;
}
} else {
bool unclip_cursor = false;
// If the cursor is clipped to the screen, clear the clip state
if (!videodevice ||
(clipped_rect.left == videodevice->desktop_bounds.x &&
clipped_rect.top == videodevice->desktop_bounds.y)) {
unclip_cursor = true;
} else {
if (videodevice && (
current.left != videodevice->desktop_bounds.x ||
current.top != videodevice->desktop_bounds.y
)) {
POINT first, second;
first.x = clipped_rect.left;
first.y = clipped_rect.top;
second.x = clipped_rect.right - 1;
second.y = clipped_rect.bottom - 1;
if (PtInRect(&data->cursor_clipped_rect, first) &&
PtInRect(&data->cursor_clipped_rect, second)) {
unclip_cursor = true;
first.x = current.left;
first.y = current.top;
second.x = current.right - 1;
second.y = current.bottom - 1;
if (!PtInRect(&data->cursor_clipped_rect, first) ||
!PtInRect(&data->cursor_clipped_rect, second)) {
return;
}
}
if (unclip_cursor) {
ClipCursor(NULL);
SDL_zero(data->cursor_clipped_rect);
ClipCursor(NULL);
SDL_zero(data->cursor_clipped_rect);
return;
}
// if (!win_have_focus || !cursor_confine) {
// RECT current;
// SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
// if (GetClipCursor(&current) && (!videodevice ||
// current.left != videodevice->desktop_bounds.x ||
// current.top != videodevice->desktop_bounds.y ||
// current.right != videodevice->desktop_bounds.x + videodevice->desktop_bounds.w ||
// current.bottom != videodevice->desktop_bounds.y + videodevice->desktop_bounds.h )) {
// ClipCursor(NULL);
// SDL_zero(data->cursor_clipped_rect);
// }
// return;
// }
SDL_Mouse *mouse = SDL_GetMouse();
bool lock_to_ctr = (mouse->relative_mode_center && mouse->relative_mode && !mouse->relative_mode_warp);
RECT client;
if (!GetClientScreenRect(data->hwnd, &client)) {
return;
}
RECT target = client;
if (lock_to_ctr) {
LONG cx = (client.left + client.right ) / 2;
LONG cy = (client.top + client.bottom) / 2;
target = data->cursor_ctrlock_rect;
target.left += cx;
target.right += cx;
target.top += cy;
target.bottom += cy;
} else if (win_mouse_rect) {
RECT custom, overlap;
custom.left = client.left + mouse_rect.x;
custom.top = client.top + mouse_rect.y;
custom.right = client.left + mouse_rect.x + mouse_rect.w;
custom.bottom = client.top + mouse_rect.y + mouse_rect.h;
if (IntersectRect(&overlap, &client, &custom)) {
target = overlap;
} else if (!win_is_grabbed) {
WIN_UnclipCursorForWindow(window);
return;
}
}
data->last_updated_clipcursor = SDL_GetTicks();
if (GetClipCursor(&client) &&
0 != SDL_memcmp(&target, &client, sizeof(client)) &&
ClipCursor(&target)) {
data->cursor_clipped_rect = target; // ClipCursor may fail if rect beyond screen
}
}
bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled)

View File

@@ -79,11 +79,10 @@ struct SDL_WindowData
bool in_title_click;
Uint8 focus_click_pending;
bool skip_update_clipcursor;
Uint64 last_updated_clipcursor;
bool mouse_relative_mode_center;
bool windowed_mode_was_maximized;
bool in_window_deactivation;
RECT cursor_clipped_rect;
RECT cursor_clipped_rect; // last successfully committed clipping rect for this window
RECT cursor_ctrlock_rect; // this is Windows-specific, but probably does not need to be per-window
UINT windowed_mode_corner_rounding;
COLORREF dwma_border_color;
bool mouse_tracked;
@@ -128,6 +127,7 @@ extern bool WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window
extern void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
extern void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window);
extern void WIN_UpdateClipCursor(SDL_Window *window);
extern void WIN_UnclipCursorForWindow(SDL_Window *window);
extern bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled);
extern void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept);
extern bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);