Переглянути джерело

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.
Frank Praznik 1 рік тому
батько
коміт
21879faf48
1 змінених файлів з 47 додано та 6 видалено
  1. 47 6
      src/video/wayland/SDL_waylandevents.c

+ 47 - 6
src/video/wayland/SDL_waylandevents.c

@@ -148,6 +148,19 @@ static void touch_del(SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_
     }
 }
 
+static SDL_bool Wayland_SurfaceHasActiveTouches(struct wl_surface *surface)
+{
+    struct SDL_WaylandTouchPoint *tp;
+
+    wl_list_for_each (tp, &touch_points, link) {
+        if (tp->surface == surface) {
+            return SDL_TRUE;
+        }
+    }
+
+    return SDL_FALSE;
+}
+
 static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp)
 {
     static Uint64 last;
@@ -581,7 +594,13 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
             SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_X2);
         }
 
-        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;
     }
 }
@@ -984,6 +1003,8 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
             y = wl_fixed_to_double(fy) / (window_data->wl_window_height - 1);
         }
 
+        SDL_SetMouseFocus(window_data->sdlwindow);
+
         SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
                       (SDL_FingerID)id, window_data->sdlwindow, SDL_TRUE, x, y, 1.0f);
     }
@@ -1007,6 +1028,14 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
 
             SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
                           (SDL_FingerID)id, window_data->sdlwindow, SDL_FALSE, x, y, 0.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);
+            }
         }
     }
 }
@@ -1447,15 +1476,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 */
@@ -1463,6 +1495,7 @@ 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;
 
     /* Clear the pressed modifiers. */
     input->pressed_modifiers = SDL_KMOD_NONE;
@@ -1472,6 +1505,13 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
         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)
@@ -1651,7 +1691,6 @@ static void seat_handle_capabilities(void *data, struct wl_seat *seat,
     }
 
     if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) {
-        WAYLAND_wl_list_init(&touch_points);
         input->touch = wl_seat_get_touch(seat);
         SDL_AddTouch((SDL_TouchID)(intptr_t)input->touch, SDL_TOUCH_DEVICE_DIRECT, "wayland_touch");
         wl_touch_set_user_data(input->touch, input);
@@ -2955,6 +2994,8 @@ void Wayland_display_add_input(SDL_VideoData *d, uint32_t id, uint32_t version)
         return;
     }
 
+    WAYLAND_wl_list_init(&touch_points);
+
     input->display = d;
     input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version));
     input->sx_w = wl_fixed_from_int(0);