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)
This commit is contained in:
Frank Praznik 2023-12-14 13:45:34 -05:00
parent 239b5278bf
commit 867d8643d7

View File

@ -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 */ /* Returns SDL_TRUE if a key repeat event was due */
static SDL_bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t elapsed) 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) { 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; 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 x = dblx / window_data->sdlwindow->w;
const float y = dbly / window_data->sdlwindow->h; const float y = dbly / window_data->sdlwindow->h;
SDL_SetMouseFocus(window_data->sdlwindow);
SDL_SendTouch((SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id, SDL_SendTouch((SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id,
window_data->sdlwindow, SDL_TRUE, x, y, 1.0f); 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) uint32_t timestamp, int id)
{ {
wl_fixed_t fx = 0, fy = 0; wl_fixed_t fx = 0, fy = 0;
struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
struct wl_surface *surface = NULL; struct wl_surface *surface = NULL;
touch_del(id, &fx, &fy, &surface); 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, SDL_SendTouch((SDL_TouchID)(intptr_t)touch, (SDL_FingerID)id,
window_data->sdlwindow, SDL_FALSE, x, y, 1.0f); 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) uint32_t serial, struct wl_surface *surface)
{ {
struct SDL_WaylandInput *input = data; struct SDL_WaylandInput *input = data;
SDL_WindowData *window; SDL_WindowData *wind;
SDL_Window *window = NULL;
if (!surface || !SDL_WAYLAND_own_surface(surface)) { if (!surface || !SDL_WAYLAND_own_surface(surface)) {
return; return;
} }
window = wl_surface_get_user_data(surface); wind = wl_surface_get_user_data(surface);
if (window) { if (wind) {
window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; wind->keyboard_device = NULL;
window = wind->sdlwindow;
window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
} }
/* Stop key repeat before clearing keyboard focus */ /* 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 */ /* This will release any keys still pressed */
SDL_SetKeyboardFocus(NULL); SDL_SetKeyboardFocus(NULL);
input->keyboard_focus = NULL;
#ifdef SDL_USE_IME #ifdef SDL_USE_IME
if (!input->text_input) { if (!input->text_input) {
SDL_IME_SetFocus(SDL_FALSE); SDL_IME_SetFocus(SDL_FALSE);
} }
#endif #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) 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)