diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 695af204a8..5d4bf4b04b 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -495,117 +495,102 @@ void Wayland_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window) WAYLAND_wl_display_flush(d->display); } -static int dispatch_queued_events(SDL_VideoData *viddata) -{ - int rc; - - /* - * NOTE: When reconnection is implemented, check if libdecor needs to be - * involved in the reconnection process. - */ -#ifdef HAVE_LIBDECOR_H - if (viddata->shell.libdecor) { - libdecor_dispatch(viddata->shell.libdecor, 0); - } -#endif - - rc = WAYLAND_wl_display_dispatch_pending(viddata->display); - return rc >= 0 ? 1 : rc; -} - int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) { SDL_VideoData *d = _this->internal; SDL_WaylandSeat *seat; - bool key_repeat_active = false; - - WAYLAND_wl_display_flush(d->display); + Uint64 start = SDL_GetTicksNS(); + const int display_fd = WAYLAND_wl_display_get_fd(d->display); + int ret; + bool poll_alarm_set = false; #ifdef SDL_USE_IME SDL_Window *keyboard_focus = SDL_GetKeyboardFocus(); if (!d->text_input_manager && keyboard_focus && SDL_TextInputActive(keyboard_focus)) { - SDL_IME_PumpEvents(); + // If a DBus IME is active with no text input protocol, periodically wake to poll it. + if (timeoutNS < 0 || SDL_MS_TO_NS(200) <= timeoutNS) { + timeoutNS = SDL_MS_TO_NS(200); + poll_alarm_set = true; + } } #endif -#ifdef SDL_USE_LIBDBUS - SDL_DBus_PumpEvents(); -#endif - // If key repeat is active, we'll need to cap our maximum wait time to handle repeats wl_list_for_each (seat, &d->seat_list, link) { if (keyboard_repeat_is_set(&seat->keyboard.repeat)) { - Wayland_SeatSetKeymap(seat); - - const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns; - if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) { - // A repeat key event was already due - return 1; - } else { - const Uint64 next_repeat_wait_time = (seat->keyboard.repeat.next_repeat_ns - elapsed) + 1; - if (timeoutNS >= 0) { - timeoutNS = SDL_min(timeoutNS, next_repeat_wait_time); - } else { - timeoutNS = next_repeat_wait_time; - } - key_repeat_active = true; + const Uint64 elapsed = start - seat->keyboard.repeat.sdl_press_time_ns; + const Uint64 next_repeat_wait_time = (seat->keyboard.repeat.next_repeat_ns - elapsed) + 1; + if (timeoutNS < 0 || next_repeat_wait_time <= timeoutNS) { + timeoutNS = next_repeat_wait_time; + poll_alarm_set = 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) { - // Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait - int err = SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS); - if (err > 0) { - // There are new events available to read - WAYLAND_wl_display_read_events(d->display); - return dispatch_queued_events(d); - } else if (err == 0) { - int ret = 0; + if (timeoutNS > 0) { + const Uint64 now = SDL_GetTicksNS(); + const Uint64 elapsed = now - start; + start = now; + timeoutNS = elapsed <= timeoutNS ? timeoutNS - elapsed : 0; + } - // No events available within the timeout - WAYLAND_wl_display_cancel_read(d->display); + ret = WAYLAND_wl_display_flush(d->display); - // If key repeat is active, we might have woken up to generate a key event - if (key_repeat_active) { - wl_list_for_each (seat, &d->seat_list, link) { - if (keyboard_repeat_is_set(&seat->keyboard.repeat)) { - Wayland_SeatSetKeymap(seat); + if (ret == -1 && errno == EAGAIN) { + // Unable to write to the socket; poll until the socket can be written to, it times out, or is interrupted. + ret = SDL_IOReady(display_fd, SDL_IOR_WRITE | SDL_IOR_NO_RETRY, timeoutNS); - const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns; - if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) { - ++ret; - } - } - } - } - - return ret; - } else { - // Error returned from poll()/select() - WAYLAND_wl_display_cancel_read(d->display); - - if (errno == EINTR) { - /* If the wait was interrupted by a signal, we may have generated a - * SDL_EVENT_QUIT event. Let the caller know to call SDL_PumpEvents(). */ - return 1; - } else { - return err; + if (ret <= 0) { + // The poll operation timed out or experienced an error, so see if there are any events to read without waiting. + timeoutNS = 0; } } - } else { - // We already had pending events - return dispatch_queued_events(d); + + if (ret < 0) { + // Pump events on an interrupt or broken pipe to handle the error. + WAYLAND_wl_display_cancel_read(d->display); + return errno == EINTR || errno == EPIPE ? 1 : ret; + } + + if (timeoutNS > 0) { + const Uint64 now = SDL_GetTicksNS(); + const Uint64 elapsed = now - start; + start = now; + timeoutNS = elapsed <= timeoutNS ? timeoutNS - elapsed : 0; + } + + // Use SDL_IOR_NO_RETRY to catch EINTR. + ret = SDL_IOReady(display_fd, SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS); + if (ret <= 0) { + // Timeout or error, cancel the read. + WAYLAND_wl_display_cancel_read(d->display); + + // The poll timed out with no data to read, but signal the caller to pump events if polling is required. + if (ret == 0) { + return poll_alarm_set ? 1 : 0; + } else { + // Pump events on an interrupt or broken pipe to handle the error. + return errno == EINTR || errno == EPIPE ? 1 : ret; + } + } + + ret = WAYLAND_wl_display_read_events(d->display); + if (ret == -1) { + return ret; + } } + + // Signal to the caller that there might be an event available. + return 1; } void Wayland_PumpEvents(SDL_VideoDevice *_this) { SDL_VideoData *d = _this->internal; SDL_WaylandSeat *seat; - int err; + const int display_fd = WAYLAND_wl_display_get_fd(d->display); + int ret = 0; #ifdef SDL_USE_IME SDL_Window *keyboard_focus = SDL_GetKeyboardFocus(); @@ -618,27 +603,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) SDL_DBus_PumpEvents(); #endif -#ifdef HAVE_LIBDECOR_H - if (d->shell.libdecor) { - libdecor_dispatch(d->shell.libdecor, 0); - } -#endif - - WAYLAND_wl_display_flush(d->display); - - /* 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_IOR_READ, 0) > 0) { - WAYLAND_wl_display_read_events(d->display); - } else { - WAYLAND_wl_display_cancel_read(d->display); - } - } - - // Dispatch any pre-existing pending events or new events we may have read - err = WAYLAND_wl_display_dispatch_pending(d->display); - + // Synthesize key repeat events. wl_list_for_each (seat, &d->seat_list, link) { if (keyboard_repeat_is_set(&seat->keyboard.repeat)) { Wayland_SeatSetKeymap(seat); @@ -648,7 +613,60 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) } } - if (err < 0 && !d->display_disconnected) { +#ifdef HAVE_LIBDECOR_H + if (d->shell.libdecor) { + libdecor_dispatch(d->shell.libdecor, 0); + } +#endif + + /* If the queue isn't empty, dispatch any old events, and try to prepare for reading again. + * If preparing to read returns -1 on the second try, wl_display_read_events() enqueued new + * events at some point between dispatching the old events and preparing for the read, + * probably from another thread, which means that the events in the queue are current. + */ + ret = WAYLAND_wl_display_prepare_read(d->display); + if (ret == -1) { + ret = WAYLAND_wl_display_dispatch_pending(d->display); + if (ret < 0) { + goto connection_error; + } + + ret = WAYLAND_wl_display_prepare_read(d->display); + } + + if (ret == 0) { + ret = WAYLAND_wl_display_flush(d->display); + + if (ret == -1 && errno == EAGAIN) { + // Unable to write to the socket; wait a brief time to see if it becomes writable. + ret = SDL_IOReady(display_fd, SDL_IOR_WRITE, SDL_MS_TO_NS(4)); + if (ret > 0) { + ret = WAYLAND_wl_display_flush(d->display); + } + } + + // If the compositor closed the socket, just jump to the error handler. + if (ret < 0 && errno == EPIPE) { + WAYLAND_wl_display_cancel_read(d->display); + goto connection_error; + } + + ret = SDL_IOReady(display_fd, SDL_IOR_READ, 0); + if (ret > 0) { + ret = WAYLAND_wl_display_read_events(d->display); + if (ret == 0) { + ret = WAYLAND_wl_display_dispatch_pending(d->display); + } + } else { + WAYLAND_wl_display_cancel_read(d->display); + } + + } else { + ret = WAYLAND_wl_display_dispatch_pending(d->display); + } + +connection_error: + if (ret < 0 && !d->display_disconnected) { /* Something has failed with the Wayland connection -- for example, * the compositor may have shut down and closed its end of the socket, * or there is a library-specific error. @@ -659,9 +677,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this) d->display_disconnected = 1; SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Wayland display connection closed by server (fatal)"); - /* Only send a single quit message, as application shutdown might call - * SDL_PumpEvents - */ + // Only send a single quit message, as application shutdown might call SDL_PumpEvents(). SDL_SendQuit(); } }