Skip to content

Commit bfa89b3

Browse files
committed
wayland: Refactor event processing
Clean up and implement some best practices for event polling and handling: - Ensure that events are still pumped, even if existing events are in the queue, to ensure that the most recent data is always processed. - Handle wl_display_flush returning EAGAIN by trying to poll the socket until it can be written to. - Ensure that events are always pumped on interrupts and broken pipe errors to handle quit events. - Minimize double-pumping of events, since returning a success code from a wait will implicitly pump events again. - Wake to poll when using a DBus IME, but the text input protocol is not enabled, to ensure that text events are delivered.
1 parent 9479ac0 commit bfa89b3

File tree

1 file changed

+112
-96
lines changed

1 file changed

+112
-96
lines changed

src/video/wayland/SDL_waylandevents.c

Lines changed: 112 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -495,117 +495,102 @@ void Wayland_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window)
495495
WAYLAND_wl_display_flush(d->display);
496496
}
497497

498-
static int dispatch_queued_events(SDL_VideoData *viddata)
499-
{
500-
int rc;
501-
502-
/*
503-
* NOTE: When reconnection is implemented, check if libdecor needs to be
504-
* involved in the reconnection process.
505-
*/
506-
#ifdef HAVE_LIBDECOR_H
507-
if (viddata->shell.libdecor) {
508-
libdecor_dispatch(viddata->shell.libdecor, 0);
509-
}
510-
#endif
511-
512-
rc = WAYLAND_wl_display_dispatch_pending(viddata->display);
513-
return rc >= 0 ? 1 : rc;
514-
}
515-
516498
int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
517499
{
518500
SDL_VideoData *d = _this->internal;
519501
SDL_WaylandSeat *seat;
520-
bool key_repeat_active = false;
521-
522-
WAYLAND_wl_display_flush(d->display);
502+
Uint64 start = SDL_GetTicksNS();
503+
const int display_fd = WAYLAND_wl_display_get_fd(d->display);
504+
int ret;
505+
bool poll_alarm_set = false;
523506

524507
#ifdef SDL_USE_IME
525508
SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
526509
if (!d->text_input_manager && keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
527-
SDL_IME_PumpEvents();
510+
// If a DBus IME is active with no text input protocol, periodically wake to poll it.
511+
if (timeoutNS < 0 || SDL_MS_TO_NS(200) <= timeoutNS) {
512+
timeoutNS = SDL_MS_TO_NS(200);
513+
poll_alarm_set = true;
514+
}
528515
}
529516
#endif
530517

531-
#ifdef SDL_USE_LIBDBUS
532-
SDL_DBus_PumpEvents();
533-
#endif
534-
535518
// If key repeat is active, we'll need to cap our maximum wait time to handle repeats
536519
wl_list_for_each (seat, &d->seat_list, link) {
537520
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
538-
Wayland_SeatSetKeymap(seat);
539-
540-
const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
541-
if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
542-
// A repeat key event was already due
543-
return 1;
544-
} else {
545-
const Uint64 next_repeat_wait_time = (seat->keyboard.repeat.next_repeat_ns - elapsed) + 1;
546-
if (timeoutNS >= 0) {
547-
timeoutNS = SDL_min(timeoutNS, next_repeat_wait_time);
548-
} else {
549-
timeoutNS = next_repeat_wait_time;
550-
}
551-
key_repeat_active = true;
521+
const Uint64 elapsed = start - seat->keyboard.repeat.sdl_press_time_ns;
522+
const Uint64 next_repeat_wait_time = (seat->keyboard.repeat.next_repeat_ns - elapsed) + 1;
523+
if (timeoutNS < 0 || next_repeat_wait_time <= timeoutNS) {
524+
timeoutNS = next_repeat_wait_time;
525+
poll_alarm_set = true;
552526
}
553527
}
554528
}
555529

556-
/* wl_display_prepare_read() will return -1 if the default queue is not empty.
557-
* If the default queue is empty, it will prepare us for our SDL_IOReady() call. */
558530
if (WAYLAND_wl_display_prepare_read(d->display) == 0) {
559-
// Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait
560-
int err = SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS);
561-
if (err > 0) {
562-
// There are new events available to read
563-
WAYLAND_wl_display_read_events(d->display);
564-
return dispatch_queued_events(d);
565-
} else if (err == 0) {
566-
int ret = 0;
567-
568-
// No events available within the timeout
569-
WAYLAND_wl_display_cancel_read(d->display);
531+
if (timeoutNS > 0) {
532+
const Uint64 now = SDL_GetTicksNS();
533+
const Uint64 elapsed = now - start;
534+
start = now;
535+
timeoutNS = elapsed <= timeoutNS ? timeoutNS - elapsed : 0;
536+
}
570537

571-
// If key repeat is active, we might have woken up to generate a key event
572-
if (key_repeat_active) {
573-
wl_list_for_each (seat, &d->seat_list, link) {
574-
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
575-
Wayland_SeatSetKeymap(seat);
538+
ret = WAYLAND_wl_display_flush(d->display);
576539

577-
const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
578-
if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
579-
++ret;
580-
}
581-
}
582-
}
540+
if (ret == -1 && errno == EAGAIN) {
541+
// Unable to write to the socket; poll until the socket can be written to, it times out, or is interrupted.
542+
ret = SDL_IOReady(display_fd, SDL_IOR_WRITE | SDL_IOR_NO_RETRY, timeoutNS);
543+
544+
if (ret <= 0) {
545+
// The poll operation timed out or experienced an error, so see if there are any events to read without waiting.
546+
timeoutNS = 0;
583547
}
548+
}
584549

585-
return ret;
586-
} else {
587-
// Error returned from poll()/select()
550+
if (ret < 0) {
551+
// Pump events on an interrupt or broken pipe to handle the error.
552+
WAYLAND_wl_display_cancel_read(d->display);
553+
return errno == EINTR || errno == EPIPE ? 1 : ret;
554+
}
555+
556+
if (timeoutNS > 0) {
557+
const Uint64 now = SDL_GetTicksNS();
558+
const Uint64 elapsed = now - start;
559+
start = now;
560+
timeoutNS = elapsed <= timeoutNS ? timeoutNS - elapsed : 0;
561+
}
562+
563+
// Use SDL_IOR_NO_RETRY to catch EINTR.
564+
ret = SDL_IOReady(display_fd, SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS);
565+
if (ret <= 0) {
566+
// Timeout or error, cancel the read.
588567
WAYLAND_wl_display_cancel_read(d->display);
589568

590-
if (errno == EINTR) {
591-
/* If the wait was interrupted by a signal, we may have generated a
592-
* SDL_EVENT_QUIT event. Let the caller know to call SDL_PumpEvents(). */
593-
return 1;
569+
// The poll timed out with no data to read, but signal the caller to pump events if polling is required.
570+
if (ret == 0) {
571+
return poll_alarm_set ? 1 : 0;
594572
} else {
595-
return err;
573+
// Pump events on an interrupt or broken pipe to handle the error.
574+
return errno == EINTR || errno == EPIPE ? 1 : ret;
596575
}
597576
}
598-
} else {
599-
// We already had pending events
600-
return dispatch_queued_events(d);
577+
578+
ret = WAYLAND_wl_display_read_events(d->display);
579+
if (ret == -1) {
580+
return ret;
581+
}
601582
}
583+
584+
// Signal to the caller that there might be an event available.
585+
return 1;
602586
}
603587

604588
void Wayland_PumpEvents(SDL_VideoDevice *_this)
605589
{
606590
SDL_VideoData *d = _this->internal;
607591
SDL_WaylandSeat *seat;
608-
int err;
592+
const int display_fd = WAYLAND_wl_display_get_fd(d->display);
593+
int ret = 0;
609594

610595
#ifdef SDL_USE_IME
611596
SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
@@ -618,37 +603,70 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
618603
SDL_DBus_PumpEvents();
619604
#endif
620605

606+
// Synthesize key repeat events.
607+
wl_list_for_each (seat, &d->seat_list, link) {
608+
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
609+
Wayland_SeatSetKeymap(seat);
610+
611+
const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
612+
keyboard_repeat_handle(&seat->keyboard.repeat, elapsed);
613+
}
614+
}
615+
621616
#ifdef HAVE_LIBDECOR_H
622617
if (d->shell.libdecor) {
623618
libdecor_dispatch(d->shell.libdecor, 0);
624619
}
625620
#endif
626621

627-
WAYLAND_wl_display_flush(d->display);
628-
629-
/* wl_display_prepare_read() will return -1 if the default queue is not empty.
630-
* If the default queue is empty, it will prepare us for our SDL_IOReady() call. */
631-
if (WAYLAND_wl_display_prepare_read(d->display) == 0) {
632-
if (SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ, 0) > 0) {
633-
WAYLAND_wl_display_read_events(d->display);
634-
} else {
635-
WAYLAND_wl_display_cancel_read(d->display);
622+
/* If the queue isn't empty, dispatch any old events, and try to prepare for reading again.
623+
* If preparing to read returns -1 on the second try, wl_display_read_events() enqueued new
624+
* events at some point between dispatching the old events and preparing for the read,
625+
* probably from another thread, which means that the events in the queue are current.
626+
*/
627+
ret = WAYLAND_wl_display_prepare_read(d->display);
628+
if (ret == -1) {
629+
ret = WAYLAND_wl_display_dispatch_pending(d->display);
630+
if (ret < 0) {
631+
goto connection_error;
636632
}
633+
634+
ret = WAYLAND_wl_display_prepare_read(d->display);
637635
}
638636

639-
// Dispatch any pre-existing pending events or new events we may have read
640-
err = WAYLAND_wl_display_dispatch_pending(d->display);
637+
if (ret == 0) {
638+
ret = WAYLAND_wl_display_flush(d->display);
641639

642-
wl_list_for_each (seat, &d->seat_list, link) {
643-
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
644-
Wayland_SeatSetKeymap(seat);
640+
if (ret == -1 && errno == EAGAIN) {
641+
// Unable to write to the socket; wait a brief time to see if it becomes writable.
642+
ret = SDL_IOReady(display_fd, SDL_IOR_WRITE, SDL_MS_TO_NS(4));
643+
if (ret > 0) {
644+
ret = WAYLAND_wl_display_flush(d->display);
645+
}
646+
}
645647

646-
const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
647-
keyboard_repeat_handle(&seat->keyboard.repeat, elapsed);
648+
// If the compositor closed the socket, just jump to the error handler.
649+
if (ret < 0 && errno == EPIPE) {
650+
WAYLAND_wl_display_cancel_read(d->display);
651+
goto connection_error;
648652
}
653+
654+
ret = SDL_IOReady(display_fd, SDL_IOR_READ, 0);
655+
if (ret > 0) {
656+
ret = WAYLAND_wl_display_read_events(d->display);
657+
if (ret == 0) {
658+
ret = WAYLAND_wl_display_dispatch_pending(d->display);
659+
}
660+
} else {
661+
WAYLAND_wl_display_cancel_read(d->display);
662+
}
663+
664+
} else {
665+
ret = WAYLAND_wl_display_dispatch_pending(d->display);
649666
}
650667

651-
if (err < 0 && !d->display_disconnected) {
668+
connection_error:
669+
if (ret < 0 && !d->display_disconnected) {
652670
/* Something has failed with the Wayland connection -- for example,
653671
* the compositor may have shut down and closed its end of the socket,
654672
* or there is a library-specific error.
@@ -659,9 +677,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
659677
d->display_disconnected = 1;
660678
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Wayland display connection closed by server (fatal)");
661679

662-
/* Only send a single quit message, as application shutdown might call
663-
* SDL_PumpEvents
664-
*/
680+
// Only send a single quit message, as application shutdown might call SDL_PumpEvents().
665681
SDL_SendQuit();
666682
}
667683
}

0 commit comments

Comments
 (0)