From 2bf36bfac4aeccfa38cf57b3c7ee0b9a963b5220 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 24 Oct 2021 21:28:04 -0500 Subject: [PATCH] wayland: Implement WaitEventTimeout() and SendWakeupEvent() We can have spurious wakeups in WaitEventTimeout() due to Wayland events that don't end up causing us to generate an SDL event. Fortunately for us, SDL_WaitEventTimeout_Device() handles this situation properly by calling WaitEventTimeout() again with an adjusted timeout. --- src/video/wayland/SDL_waylandevents.c | 80 ++++++++++++++++++++++++- src/video/wayland/SDL_waylandevents_c.h | 2 + src/video/wayland/SDL_waylandvideo.c | 6 ++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 7a03039f2..9a17b0ae8 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -169,12 +169,13 @@ touch_surface(SDL_TouchID id) return NULL; } -/* Returns the time till next repeat, or 0 if no key is down. */ -static void +/* Returns SDL_TRUE if a key repeat event was due */ +static SDL_bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat* repeat_info, uint32_t now) { + SDL_bool ret = SDL_FALSE; if (!repeat_info->is_key_down || !repeat_info->is_initialized) { - return; + return ret; } while (repeat_info->next_repeat_ms <= now) { if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) { @@ -184,7 +185,9 @@ keyboard_repeat_handle(SDL_WaylandKeyboardRepeat* repeat_info, uint32_t now) SDL_SendKeyboardText(repeat_info->text); } repeat_info->next_repeat_ms += 1000 / repeat_info->repeat_rate; + ret = SDL_TRUE; } + return ret; } static void @@ -211,6 +214,77 @@ keyboard_repeat_set(SDL_WaylandKeyboardRepeat* repeat_info, } } +void +Wayland_SendWakeupEvent(_THIS, SDL_Window *window) +{ + SDL_VideoData *d = _this->driverdata; + + /* TODO: Maybe use a pipe to avoid the compositor round trip? */ + wl_display_sync(d->display); + WAYLAND_wl_display_flush(d->display); +} + +int +Wayland_WaitEventTimeout(_THIS, int timeout) +{ + SDL_VideoData *d = _this->driverdata; + struct SDL_WaylandInput *input = d->input; + SDL_bool key_repeat_active = SDL_FALSE; + + WAYLAND_wl_display_flush(d->display); + +#ifdef SDL_USE_IME + if (d->text_input_manager == NULL && SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE) { + SDL_IME_PumpEvents(); + } +#endif + + /* If key repeat is active, we'll need to cap our maximum wait time to handle repeats */ + if (input && input->keyboard_repeat.is_initialized && input->keyboard_repeat.is_key_down) { + uint32_t now = SDL_GetTicks(); + if (keyboard_repeat_handle(&input->keyboard_repeat, now)) { + /* A repeat key event was already due */ + return 1; + } else { + uint32_t next_repeat_wait_time = (input->keyboard_repeat.next_repeat_ms - now) + 1; + if (timeout >= 0) { + timeout = SDL_min(timeout, next_repeat_wait_time); + } else { + timeout = next_repeat_wait_time; + } + key_repeat_active = SDL_TRUE; + } + } + + /* wl_display_prepare_read() will return -1 if the default queue is not empty. + * If the default queue is empty, it will prepare us for our SDL_IOReady() call. */ + if (WAYLAND_wl_display_prepare_read(d->display) == 0) { + if (SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_FALSE, timeout) > 0) { + /* There are new events available to read */ + WAYLAND_wl_display_read_events(d->display); + WAYLAND_wl_display_dispatch_pending(d->display); + return 1; + } else { + /* No events available within the timeout */ + WAYLAND_wl_display_cancel_read(d->display); + + /* If key repeat is active, we might have woken up to generate a key event */ + if (key_repeat_active) { + uint32_t now = SDL_GetTicks(); + if (keyboard_repeat_handle(&input->keyboard_repeat, now)) { + return 1; + } + } + + return 0; + } + } else { + /* We already had pending events */ + WAYLAND_wl_display_dispatch_pending(d->display); + return 1; + } +} + void Wayland_PumpEvents(_THIS) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index e4b76d830..699d2b377 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -83,6 +83,8 @@ struct SDL_WaylandInput { }; extern void Wayland_PumpEvents(_THIS); +extern void Wayland_SendWakeupEvent(_THIS, SDL_Window *window); +extern int Wayland_WaitEventTimeout(_THIS, int timeout); extern void Wayland_add_data_device_manager(SDL_VideoData *d, uint32_t id, uint32_t version); extern void Wayland_add_text_input_manager(SDL_VideoData *d, uint32_t id, uint32_t version); diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 03464d193..71f6fa886 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -169,6 +169,9 @@ Wayland_DeleteDevice(SDL_VideoDevice *device) WAYLAND_wl_display_flush(data->display); WAYLAND_wl_display_disconnect(data->display); } + if (device->wakeup_lock) { + SDL_DestroyMutex(device->wakeup_lock); + } SDL_free(data); SDL_free(device); SDL_WAYLAND_UnloadSymbols(); @@ -212,6 +215,7 @@ Wayland_CreateDevice(int devindex) } device->driverdata = data; + device->wakeup_lock = SDL_CreateMutex(); /* Set the function pointers */ device->VideoInit = Wayland_VideoInit; @@ -222,6 +226,8 @@ Wayland_CreateDevice(int devindex) device->SuspendScreenSaver = Wayland_SuspendScreenSaver; device->PumpEvents = Wayland_PumpEvents; + device->WaitEventTimeout = Wayland_WaitEventTimeout; + device->SendWakeupEvent = Wayland_SendWakeupEvent; device->GL_SwapWindow = Wayland_GLES_SwapWindow; device->GL_GetSwapInterval = Wayland_GLES_GetSwapInterval;