SDL: Make sure we don't try to turn on relative mouse mode while clicking on the window title bar.

From 88e4755c26d5fd5c9f8da17fb0e1659ff46d9203 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 27 Jul 2021 12:43:00 -0700
Subject: [PATCH] Make sure we don't try to turn on relative mouse mode while
 clicking on the window title bar.

This fixes bug https://github.com/libsdl-org/SDL/issues/4469
---
 src/video/cocoa/SDL_cocoamouse.m  | 49 +++++++++++++++++++++++++++++--
 src/video/cocoa/SDL_cocoawindow.h |  5 ++++
 src/video/cocoa/SDL_cocoawindow.m | 47 +++++++++++++++++++++++++++--
 3 files changed, 95 insertions(+), 6 deletions(-)

diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m
index b6942af084..5680ef58c4 100644
--- a/src/video/cocoa/SDL_cocoamouse.m
+++ b/src/video/cocoa/SDL_cocoamouse.m
@@ -215,8 +215,8 @@ + (NSCursor *)invisibleCursor
     SDL_Mouse *mouse = SDL_GetMouse();
     if (mouse->focus) {
         SDL_WindowData *data = (SDL_WindowData *) mouse->focus->driverdata;
-        if ([data->listener isMoving]) {
-            DLog("Postponing warp, window being moved.");
+        if ([data->listener isMovingOrFocusClickPending]) {
+            DLog("Postponing warp, window being moved or focused.");
             [data->listener setPendingMoveX:x Y:y];
             return 0;
         }
@@ -271,7 +271,7 @@ + (NSCursor *)invisibleCursor
      * if it is being moved right now.
      */
     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
-    if ([data->listener isMoving]) {
+    if ([data->listener isMovingOrFocusClickPending]) {
         return 0;
     }
 
@@ -353,6 +353,34 @@ + (NSCursor *)invisibleCursor
     return 0;
 }
 
+void
+Cocoa_HandleTitleButtonEvent(_THIS, NSEvent *event)
+{
+    SDL_Window *window;
+    NSWindow *nswindow = [event window];
+
+    for (window = _this->windows; window; window = window->next) {
+        SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
+        if (data && data->nswindow == nswindow) {
+            switch ([event type]) {
+            case NSEventTypeLeftMouseDown:
+            case NSEventTypeRightMouseDown:
+            case NSEventTypeOtherMouseDown:
+                [data->listener setFocusClickPending:[event buttonNumber]];
+                break;
+            case NSEventTypeLeftMouseUp:
+            case NSEventTypeRightMouseUp:
+            case NSEventTypeOtherMouseUp:
+                [data->listener clearFocusClickPending:[event buttonNumber]];
+                break;
+            default:
+                break;
+            }
+            break;
+        }
+    }
+}
+
 void
 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
 {
@@ -363,6 +391,21 @@ + (NSCursor *)invisibleCursor
         case NSEventTypeOtherMouseDragged:
             break;
 
+        case NSEventTypeLeftMouseDown:
+        case NSEventTypeLeftMouseUp:
+        case NSEventTypeRightMouseDown:
+        case NSEventTypeRightMouseUp:
+        case NSEventTypeOtherMouseDown:
+        case NSEventTypeOtherMouseUp:
+            if ([event window]) {
+                NSRect windowRect = [[[event window] contentView] frame];
+                if (!NSMouseInRect([event locationInWindow], windowRect, NO)) {
+                    Cocoa_HandleTitleButtonEvent(_this, event);
+                    return;
+                }
+            }
+            return;
+
         default:
             /* Ignore any other events. */
             return;
diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h
index e14bb1d473..37bec665e9 100644
--- a/src/video/cocoa/SDL_cocoawindow.h
+++ b/src/video/cocoa/SDL_cocoawindow.h
@@ -48,6 +48,7 @@ typedef enum
     BOOL inFullscreenTransition;
     PendingWindowOperation pendingWindowOperation;
     BOOL isMoving;
+    int focusClickPending;
     int pendingWindowWarpX, pendingWindowWarpY;
     BOOL isDragAreaRunning;
 }
@@ -62,8 +63,12 @@ typedef enum
 -(void) close;
 
 -(BOOL) isMoving;
+-(BOOL) isMovingOrFocusClickPending;
+-(void) setFocusClickPending:(int) button;
+-(void) clearFocusClickPending:(int) button;
 -(void) setPendingMoveX:(int)x Y:(int)y;
 -(void) windowDidFinishMoving;
+-(void) onMovingOrFocusClickPendingStateCleared;
 
 /* Window delegate functionality */
 -(BOOL) windowShouldClose:(id) sender;
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index 4586073b54..7a1446f096 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -508,6 +508,26 @@ - (BOOL)isMoving
     return isMoving;
 }
 
+- (BOOL)isMovingOrFocusClickPending
+{
+    return isMoving || (focusClickPending != 0);
+}
+
+-(void) setFocusClickPending:(int) button
+{
+    focusClickPending |= (1 << button);
+}
+
+-(void) clearFocusClickPending:(int) button
+{
+    if ((focusClickPending & (1 << button)) != 0) {
+        focusClickPending &= ~(1 << button);
+        if (focusClickPending == 0) {
+            [self onMovingOrFocusClickPendingStateCleared];
+        }
+    }
+}
+
 -(void) setPendingMoveX:(int)x Y:(int)y
 {
     pendingWindowWarpX = x;
@@ -516,15 +536,36 @@ -(void) setPendingMoveX:(int)x Y:(int)y
 
 - (void)windowDidFinishMoving
 {
-    if ([self isMoving]) {
+    if (isMoving) {
         isMoving = NO;
+        [self onMovingOrFocusClickPendingStateCleared];
+    }
+}
 
+- (void)onMovingOrFocusClickPendingStateCleared
+{
+    if (![self isMovingOrFocusClickPending]) {
         SDL_Mouse *mouse = SDL_GetMouse();
         if (pendingWindowWarpX != INT_MAX && pendingWindowWarpY != INT_MAX) {
             mouse->WarpMouseGlobal(pendingWindowWarpX, pendingWindowWarpY);
             pendingWindowWarpX = pendingWindowWarpY = INT_MAX;
         }
         if (mouse->relative_mode && !mouse->relative_mode_warp && mouse->focus == _data->window) {
+            /* Move the cursor to the nearest point in the window */
+            {
+                int x, y;
+                CGPoint cgpoint;
+
+                SDL_GetMouseState(&x, &y);
+                cgpoint.x = _data->window->x + x;
+                cgpoint.y = _data->window->y + y;
+
+                Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
+
+                DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
+                CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
+            }
+
             mouse->SetRelativeMouseMode(SDL_TRUE);
         }
     }
@@ -641,7 +682,7 @@ - (void)windowDidBecomeKey:(NSNotification *)aNotification
     /* This needs to be done before restoring the relative mouse mode. */
     SDL_SetKeyboardFocus(window);
 
-    if (mouse->relative_mode && !mouse->relative_mode_warp && ![self isMoving]) {
+    if (mouse->relative_mode && !mouse->relative_mode_warp && ![self isMovingOrFocusClickPending]) {
         mouse->SetRelativeMouseMode(SDL_TRUE);
     }
 
@@ -1970,7 +2011,7 @@ - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
 
     /* Move the cursor to the nearest point in the window */
-    if (grabbed && data && ![data->listener isMoving]) {
+    if (grabbed && data && ![data->listener isMovingOrFocusClickPending]) {
         int x, y;
         CGPoint cgpoint;