SDL: Track button state for each mouse input source separately

From dfb834d3d4d06c9529d7577127d65b88a9ffc52d Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 10 Nov 2021 13:41:44 -0800
Subject: [PATCH] Track button state for each mouse input source separately

This way we'll get button down and up events for each mouseID
individually.

Fixes https://github.com/libsdl-org/SDL/issues/4518
---
 src/events/SDL_mouse.c   | 82 +++++++++++++++++++++++++++-------------
 src/events/SDL_mouse_c.h | 11 +++++-
 2 files changed, 66 insertions(+), 27 deletions(-)

diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 45fb092f76..9d2ea9a65d 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -177,32 +177,24 @@ SDL_GetMouse(void)
     return &SDL_mouse;
 }
 
-SDL_Window *
-SDL_GetMouseFocus(void)
+static Uint32 GetButtonState(SDL_Mouse *mouse)
 {
-    SDL_Mouse *mouse = SDL_GetMouse();
+    int i;
+    Uint32 buttonstate = 0;
 
-    return mouse->focus;
+    for (i = 0; i < mouse->num_sources; ++i) {
+        buttonstate |= mouse->sources[i].buttonstate;
+    }
+    return buttonstate;
 }
 
-#if 0
-void
-SDL_ResetMouse(void)
+SDL_Window *
+SDL_GetMouseFocus(void)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
-    Uint8 i;
 
-#ifdef DEBUG_MOUSE
-    printf("Resetting mouse\n");
-#endif
-    for (i = 1; i <= sizeof(mouse->buttonstate)*8; ++i) {
-        if (mouse->buttonstate & SDL_BUTTON(i)) {
-            SDL_SendMouseButton(mouse->focus, mouse->mouseID, SDL_RELEASED, i);
-        }
-    }
-    SDL_assert(mouse->buttonstate == 0);
+    return mouse->focus;
 }
-#endif
 
 void
 SDL_SetMouseFocus(SDL_Window * window)
@@ -299,7 +291,7 @@ SDL_SendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int
 {
     if (window && !relative) {
         SDL_Mouse *mouse = SDL_GetMouse();
-        if (!SDL_UpdateMouseFocus(window, x, y, mouse->buttonstate, (mouseID == SDL_TOUCH_MOUSEID) ? SDL_FALSE : SDL_TRUE)) {
+        if (!SDL_UpdateMouseFocus(window, x, y, GetButtonState(mouse), (mouseID == SDL_TOUCH_MOUSEID) ? SDL_FALSE : SDL_TRUE)) {
             return 0;
         }
     }
@@ -399,7 +391,7 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
     }
 
     /* Ignore relative motion positioning the first touch */
-    if (mouseID == SDL_TOUCH_MOUSEID && !mouse->buttonstate) {
+    if (mouseID == SDL_TOUCH_MOUSEID && !GetButtonState(mouse)) {
         xrel = 0;
         yrel = 0;
     }
@@ -473,7 +465,7 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
         event.motion.which = mouseID;
         /* Set us pending (or clear during a normal mouse movement event) as having triggered */
         mouse->was_touch_mouse_events = (mouseID == SDL_TOUCH_MOUSEID)? SDL_TRUE : SDL_FALSE;
-        event.motion.state = mouse->buttonstate;
+        event.motion.state = GetButtonState(mouse);
         event.motion.x = mouse->x;
         event.motion.y = mouse->y;
         event.motion.xrel = xrel;
@@ -491,6 +483,30 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
     return posted;
 }
 
+static SDL_MouseInputSource *GetMouseInputSource(SDL_Mouse *mouse, SDL_MouseID mouseID)
+{
+    SDL_MouseInputSource *source, *sources;
+    int i;
+
+    for (i = 0; i < mouse->num_sources; ++i) {
+        source = &mouse->sources[i];
+        if (source->mouseID == mouseID) {
+            return source;
+        }
+    }
+
+    sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1)*sizeof(*mouse->sources));
+    if (sources) {
+        mouse->sources = sources;
+        ++mouse->num_sources;
+        source = &sources[mouse->num_sources - 1];
+        source->mouseID = mouseID;
+        source->buttonstate = 0;
+        return source;
+    }
+    return NULL;
+}
+
 static SDL_MouseClickState *GetMouseClickState(SDL_Mouse *mouse, Uint8 button)
 {
     if (button >= mouse->num_clickstates) {
@@ -515,7 +531,14 @@ SDL_PrivateSendMouseButton(SDL_Window * window, SDL_MouseID mouseID, Uint8 state
     SDL_Mouse *mouse = SDL_GetMouse();
     int posted;
     Uint32 type;
-    Uint32 buttonstate = mouse->buttonstate;
+    Uint32 buttonstate;
+    SDL_MouseInputSource *source;
+   
+    source = GetMouseInputSource(mouse, mouseID);
+    if (!source) {
+        return 0;
+    }
+    buttonstate = source->buttonstate;
 
     /* SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events */
     if (mouse->mouse_touch_events) {
@@ -560,11 +583,11 @@ SDL_PrivateSendMouseButton(SDL_Window * window, SDL_MouseID mouseID, Uint8 state
         SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate, SDL_TRUE);
     }
 
-    if (buttonstate == mouse->buttonstate) {
+    if (buttonstate == source->buttonstate) {
         /* Ignore this event, no state change */
         return 0;
     }
-    mouse->buttonstate = buttonstate;
+    source->buttonstate = buttonstate;
 
     if (clicks < 0) {
         SDL_MouseClickState *clickstate = GetMouseClickState(mouse, button);
@@ -722,10 +745,17 @@ SDL_MouseQuit(void)
         mouse->def_cursor = NULL;
     }
 
+    if (mouse->sources) {
+        SDL_free(mouse->sources);
+        mouse->sources = NULL;
+    }
+    mouse->num_sources = 0;
+
     if (mouse->clickstate) {
         SDL_free(mouse->clickstate);
         mouse->clickstate = NULL;
     }
+    mouse->num_clickstates = 0;
 
     SDL_DelHintCallback(SDL_HINT_MOUSE_NORMAL_SPEED_SCALE,
                         SDL_MouseNormalSpeedScaleChanged, mouse);
@@ -745,7 +775,7 @@ SDL_GetMouseState(int *x, int *y)
     if (y) {
         *y = mouse->y;
     }
-    return mouse->buttonstate;
+    return GetButtonState(mouse);
 }
 
 Uint32
@@ -761,7 +791,7 @@ SDL_GetRelativeMouseState(int *x, int *y)
     }
     mouse->xdelta = 0;
     mouse->ydelta = 0;
-    return mouse->buttonstate;
+    return GetButtonState(mouse);
 }
 
 Uint32
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index 9c4c62816d..0915c37313 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -33,6 +33,12 @@ struct SDL_Cursor
     void *driverdata;
 };
 
+typedef struct
+{
+    SDL_MouseID mouseID;
+    Uint32 buttonstate;
+} SDL_MouseInputSource;
+
 typedef struct
 {
     int last_x, last_y;
@@ -82,7 +88,6 @@ typedef struct
     int last_x, last_y;         /* the last reported x and y coordinates */
     float accumulated_wheel_x;
     float accumulated_wheel_y;
-    Uint32 buttonstate;
     SDL_bool has_position;
     SDL_bool relative_mode;
     SDL_bool relative_mode_warp;
@@ -96,6 +101,10 @@ typedef struct
     SDL_bool mouse_touch_events;
     SDL_bool was_touch_mouse_events; /* Was a touch-mouse event pending? */
 
+    /* Data for input source state */
+    int num_sources;
+    SDL_MouseInputSource *sources;
+
     /* Data for double-click tracking */
     int num_clickstates;
     SDL_MouseClickState *clickstate;