SDL: Reduce the likelyhood that the mouse will hover over the taskbar or toast notification while in relative mode, which causes a...

From db68af8032e1fb988d08b7253839b17e532e1db7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 24 Sep 2021 10:49:44 -0700
Subject: [PATCH] Reduce the likelyhood that the mouse will hover over the
 taskbar or toast notification while in relative mode, which causes a mouse
 leave event.

This will still happen occasionally as the mouse is whipped around, if there is a window overlapping the game window, but it should happen less often now. This could even happen with the original code that warped the mouse every frame, so this should be a good compromise where we don't warp the mouse continously and we still keep the mouse in the safe area of the game window.

Note that notifications can be any size, so the safe area may need to be adjusted or even dynamically defined via a hint.
---
 src/core/windows/SDL_windows.c        |  18 ++++
 src/core/windows/SDL_windows.h        |   6 ++
 src/video/windows/SDL_windowsevents.c | 141 ++++++++++++++++++++------
 3 files changed, 135 insertions(+), 30 deletions(-)

diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c
index e111ec9c19..a0c789a63c 100644
--- a/src/core/windows/SDL_windows.c
+++ b/src/core/windows/SDL_windows.c
@@ -248,6 +248,24 @@ WIN_IsEqualIID(REFIID a, REFIID b)
     return (SDL_memcmp(a, b, sizeof (*a)) == 0);
 }
 
+void
+WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect)
+{
+    sdlrect->x = winrect->left;
+    sdlrect->w = (winrect->right - winrect->left) + 1;
+    sdlrect->y = winrect->top;
+    sdlrect->h = (winrect->bottom - winrect->top) + 1;
+}
+
+void
+WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)
+{
+    winrect->left = sdlrect->x;
+    winrect->right = sdlrect->x + sdlrect->w - 1;
+    winrect->top = sdlrect->y;
+    winrect->bottom = sdlrect->y + sdlrect->h - 1;
+}
+
 #endif /* __WIN32__ || __WINRT__ */
 
 /* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/core/windows/SDL_windows.h b/src/core/windows/SDL_windows.h
index 1d5e06b0df..e81681ef9b 100644
--- a/src/core/windows/SDL_windows.h
+++ b/src/core/windows/SDL_windows.h
@@ -37,6 +37,8 @@
 #include <windows.h>
 #include <basetyps.h>   /* for REFIID with broken mingw.org headers */
 
+#include "SDL_rect.h"
+
 /* Routines to convert from UTF8 to native Windows text */
 #define WIN_StringToUTF8W(S) SDL_iconv_string("UTF-8", "UTF-16LE", (char *)(S), (SDL_wcslen(S)+1)*sizeof(WCHAR))
 #define WIN_UTF8ToStringW(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (char *)(S), SDL_strlen(S)+1)
@@ -81,6 +83,10 @@ extern char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid);
 extern BOOL WIN_IsEqualGUID(const GUID * a, const GUID * b);
 extern BOOL WIN_IsEqualIID(REFIID a, REFIID b);
 
+/* Convert between SDL_rect and RECT */
+extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect);
+extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect);
+
 #endif /* _INCLUDED_WINDOWS_H */
 
 /* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index a37d06acf7..c9da2e0533 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -446,6 +446,79 @@ static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource()
     return SDL_MOUSE_EVENT_SOURCE_MOUSE;
 }
 
+static void
+GetDisplayBoundsForPoint(int x, int y, RECT *bounds)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    int i, dist;
+    int closest = -1;
+    int closest_dist = 0x7FFFFFFF;
+    SDL_Point point;
+    SDL_Point delta;
+    SDL_Rect rect;
+
+    point.x = x;
+    point.y = y;
+    for (i = 0; i < _this->num_displays; ++i) {
+        SDL_GetDisplayBounds(i, &rect);
+        if (SDL_EnclosePoints(&point, 1, &rect, NULL)) {
+            WIN_RectToRECT(&rect, bounds);
+            return;
+        }
+
+        delta.x = point.x - (rect.x + rect.w / 2);
+        delta.y = point.y - (rect.y + rect.h / 2);
+        dist = (delta.x*delta.x + delta.y*delta.y);
+        if (dist < closest_dist) {
+            closest = i;
+            closest_dist = dist;
+            WIN_RectToRECT(&rect, bounds);
+        }
+    }
+    if (closest < 0) {
+        bounds->left = 0;
+        bounds->right = GetSystemMetrics(SM_CXSCREEN) - 1;
+        bounds->top = 0;
+        bounds->bottom = GetSystemMetrics(SM_CYSCREEN) - 1;
+    }
+}
+
+static void
+WarpWithinBoundsRect(int x, int y, RECT *bounds)
+{
+    if (x < bounds->left || x > bounds->right || y < bounds->top || y > bounds->bottom) {
+        const int MIN_BOUNDS_SIZE = 32;
+        int boundsWidth = (bounds->right - bounds->left) + 1;
+        int boundsHeight = (bounds->bottom - bounds->top) + 1;
+        if (boundsWidth >= MIN_BOUNDS_SIZE && boundsHeight >= MIN_BOUNDS_SIZE) {
+            /* Warp back to the opposite side, assuming more motion in the current direction */
+            int targetLeft = bounds->right - (boundsWidth * 3) / 4;
+            int targetRight = bounds->left + (boundsWidth * 3) / 4;
+            int targetTop = bounds->bottom - (boundsHeight * 3) / 4;
+            int targetBottom = bounds->top + (boundsHeight * 3) / 4;
+            int warpX;
+            int warpY;
+
+            if (x < bounds->left) {
+                warpX = targetRight;
+            } else if (x > bounds->right) {
+                warpX = targetLeft;
+            } else {
+                warpX = SDL_clamp(x, targetLeft, targetRight);
+            }
+
+            if (y < bounds->top) {
+                warpY = targetBottom;
+            } else if (y > bounds->bottom) {
+                warpY = targetTop;
+            } else {
+                warpY = SDL_clamp(y, targetTop, targetBottom);
+            }
+            SetCursorPos(warpX, warpY);
+        }
+    }
+}
+
 static SDL_WindowData *
 WIN_GetWindowDataFromHWND(HWND hwnd)
 {
@@ -742,10 +815,42 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                 }
                 mouseID = (SDL_MouseID)(uintptr_t)inp.header.hDevice;
                 if (isRelative) {
+                    /* FIXME: Add a hint to control this? */
+                    const int SAFE_AREA_X = 64;
+                    const int SAFE_AREA_Y = 256;
+
                     RAWMOUSE* rawmouse = &inp.data.mouse;
 
                     if ((rawmouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
+                        POINT pt;
+
                         SDL_SendMouseMotion(data->window, mouseID, 1, (int)rawmouse->lLastX, (int)rawmouse->lLastY);
+
+                        /* Make sure that the mouse doesn't hover over notifications and so forth */
+                        if (GetCursorPos(&pt)) {
+                            int x = pt.x;
+                            int y = pt.y;
+                            RECT screenRect;
+                            RECT hwndRect;
+                            RECT boundsRect;
+
+                            /* Calculate screen rect */
+                            GetDisplayBoundsForPoint(x, y, &screenRect);
+
+                            /* Calculate client rect */
+                            GetClientRect(hwnd, &hwndRect);
+                            ClientToScreen(hwnd, (LPPOINT) & hwndRect);
+                            ClientToScreen(hwnd, (LPPOINT) & hwndRect + 1);
+
+                            /* Calculate bounds rect */
+                            IntersectRect(&boundsRect, &screenRect, &hwndRect);
+                            InflateRect(&boundsRect, -SAFE_AREA_X, -SAFE_AREA_Y);
+
+                            if (!data->in_title_click && !data->focus_click_pending) {
+                                WarpWithinBoundsRect(x, y, &boundsRect);
+                            }
+                        }
+
                     } else if (rawmouse->lLastX || rawmouse->lLastY) {
                         /* This is absolute motion, either using a tablet or mouse over RDP
 
@@ -781,10 +886,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                             int boundsWidth, boundsHeight;
 
                             /* Calculate screen rect */
-                            screenRect.left = 0;
-                            screenRect.right = w;
-                            screenRect.top = 0;
-                            screenRect.bottom = h;
+                            GetDisplayBoundsForPoint(x, y, &screenRect);
 
                             /* Calculate client rect */
                             GetClientRect(hwnd, &hwndRect);
@@ -793,9 +895,9 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 
                             /* Calculate bounds rect */
                             IntersectRect(&boundsRect, &screenRect, &hwndRect);
-                            InflateRect(&boundsRect, -32, -32);
-                            boundsWidth = (boundsRect.right - boundsRect.left);
-                            boundsHeight = (boundsRect.bottom - boundsRect.top);
+                            InflateRect(&boundsRect, -SAFE_AREA_X, -SAFE_AREA_Y);
+                            boundsWidth = (boundsRect.right - boundsRect.left) + 1;
+                            boundsHeight = (boundsRect.bottom - boundsRect.top) + 1;
 
                             if ((boundsWidth > 0 && SDL_abs(relX) > (boundsWidth / 2)) ||
                                 (boundsHeight > 0 && SDL_abs(relY) > (boundsHeight / 2))) {
@@ -803,29 +905,8 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                             } else {
                                 SDL_SendMouseMotion(data->window, mouseID, 1, relX, relY);
 
-								if (!data->in_title_click && !data->focus_click_pending &&
-									(x < boundsRect.left || x > boundsRect.right ||
-									 y < boundsRect.top || y > boundsRect.bottom)) {
-                                    /* Warp back to the opposite side, assuming more motion in the current direction */
-                                    int warpX;
-                                    int warpY;
-
-                                    if (x < boundsRect.left) {
-                                        warpX = boundsRect.right;
-                                    } else if (x > boundsRect.right) {
-                                        warpX = boundsRect.left;
-                                    } else {
-                                        warpX = x;
-                                    }
-
-                                    if (y < boundsRect.top) {
-                                        warpY = boundsRect.bottom;
-                                    } else if (y > boundsRect.bottom) {
-                                        warpY = boundsRect.top;
-                                    } else {
-                                        warpY = y;
-                                    }
-                                    SetCursorPos(warpX, warpY);
+								if (!data->in_title_click && !data->focus_click_pending) {
+                                    WarpWithinBoundsRect(x, y, &boundsRect);
                                 }
                             }
                         } else {