SDL: wayland: Handle mouse focus when receiving touch events (867d8)

From 867d8643d7c0b0becea823ecdceb0263475c2630 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 14 Dec 2023 13:45:34 -0500
Subject: [PATCH] wayland: Handle mouse focus when receiving touch events

Compositors may switch from mouse to touch mode when a touch event is received, causing a pointer leave event and subsequent loss of mouse focus.

Don't relinquish mouse focus on surfaces with active touch events. If there are active touch events when pointer focus is lost, the keyboard focus is used as a fallback for relinquishing mouse focus: if, in this case, the keyboard focus is then lost and there are no active touches, mouse focus is lost, and if all touches are raised and there is no keyboard or pointer focus, then the window loses mouse focus.

(cherry picked from commit 21879faf4815a6acdb29e290d51a15bbe4dae03e)
---
 src/video/wayland/SDL_waylandevents.c | 52 ++++++++++++++++++++++++---
 1 file changed, 47 insertions(+), 5 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 7e6ff892801d..6b5bd17ab8ac 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -163,6 +163,21 @@ static void touch_del(SDL_TouchID id, wl_fixed_t *x, wl_fixed_t *y, struct wl_su
     }
 }
 
+static SDL_bool Wayland_SurfaceHasActiveTouches(struct wl_surface *surface)
+{
+    struct SDL_WaylandTouchPoint *tp = touch_points.head;
+
+    while (tp) {
+        if (tp->surface == surface) {
+            return SDL_TRUE;
+        }
+
+        tp = tp->next;
+    }
+
+    return SDL_FALSE;
+}
+
 /* Returns SDL_TRUE if a key repeat event was due */
 static SDL_bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t elapsed)
 {
@@ -453,7 +468,12 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
     }
 
     if (input->pointer_focus) {
-        SDL_SetMouseFocus(NULL);
+        /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event.
+         * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode.
+         */
+        if (!Wayland_SurfaceHasActiveTouches(surface)) {
+            SDL_SetMouseFocus(NULL);
+        }
         input->pointer_focus = NULL;
     }
 }
@@ -820,6 +840,8 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
         const float x = dblx / window_data->sdlwindow->w;
         const float y = dbly / window_data->sdlwindow->h;
 
+        SDL_SetMouseFocus(window_data->sdlwindow);
+
         SDL_SendTouch((SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id,
                       window_data->sdlwindow, SDL_TRUE, x, y, 1.0f);
     }
@@ -829,6 +851,7 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
                              uint32_t timestamp, int id)
 {
     wl_fixed_t fx = 0, fy = 0;
+    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
     struct wl_surface *surface = NULL;
 
     touch_del(id, &fx, &fy, &surface);
@@ -844,6 +867,14 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
 
             SDL_SendTouch((SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id,
                           window_data->sdlwindow, SDL_FALSE, x, y, 1.0f);
+
+            /* If the seat lacks pointer focus, the seat's keyboard focus is another window or NULL, this window curently
+             * has mouse focus, and the surface has no active touch events, consider mouse focus to be lost.
+             */
+            if (!input->pointer_focus && input->keyboard_focus != window_data &&
+                SDL_GetMouseFocus() == window_data->sdlwindow && !Wayland_SurfaceHasActiveTouches(surface)) {
+                SDL_SetMouseFocus(NULL);
+            }
         }
     }
 }
@@ -1127,15 +1158,18 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
                                   uint32_t serial, struct wl_surface *surface)
 {
     struct SDL_WaylandInput *input = data;
-    SDL_WindowData *window;
+    SDL_WindowData *wind;
+    SDL_Window *window = NULL;
 
     if (!surface || !SDL_WAYLAND_own_surface(surface)) {
         return;
     }
 
-    window = wl_surface_get_user_data(surface);
-    if (window) {
-        window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
+    wind = wl_surface_get_user_data(surface);
+    if (wind) {
+        wind->keyboard_device = NULL;
+        window = wind->sdlwindow;
+        window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
     }
 
     /* Stop key repeat before clearing keyboard focus */
@@ -1143,12 +1177,20 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
 
     /* This will release any keys still pressed */
     SDL_SetKeyboardFocus(NULL);
+    input->keyboard_focus = NULL;
 
 #ifdef SDL_USE_IME
     if (!input->text_input) {
         SDL_IME_SetFocus(SDL_FALSE);
     }
 #endif
+
+    /* If the surface had a pointer leave event while still having active touch events, it retained mouse focus.
+     * Clear it now if all touch events are raised.
+     */
+    if (!input->pointer_focus && SDL_GetMouseFocus() == window && !Wayland_SurfaceHasActiveTouches(surface)) {
+        SDL_SetMouseFocus(NULL);
+    }
 }
 
 static SDL_bool keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint32_t key, Uint8 state, SDL_bool *handled_by_ime)