diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 868a93ee3e..1be6d0a1e6 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -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. diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 657bad9a6c..dea5fc0d03 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -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); } diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index d77d0a7e10..5bc9a53755 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -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; diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 6878bbe1c9..15057e02ad 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -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(); diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index d1c8a51391..b7a05f6c45 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -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(¤t)) { + 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(¤t) && (!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) diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 9814d10acf..ddc3ddd6d9 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -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);