Fix Wayland event loop order to avoid missed renders

After the changes of the previous commit, the event loop flow on Wayland is:
    1. Render windows if window->render_pending is set
    2. Wait for events [NOTE: this includes the frame callback]
    3. Schedule render if menu->dirty is set
    4. Handle events (return from render) and repeat

This can still miss renders since the menu->dirty flag is set in step (4),
but the menu->dirty flag is checked in step (3). So if the event loop only
does a single iteration (quite unusual as most user actions cause multiple
events), we can get stuck on step (2) for a while.

In order to avoid this problem, this changes the event loop order to:
    1. Schedule render if menu->dirty is set
    2. Wait for events [NOTE: this includes the frame callback]
    3. Render windows if window->render_pending is set
    4. Handle events (return from render) and repeat

Script (for Sway) to reproduce the issue / verify the fix:
    #!/usr/bin/env sh
    mousesety() { swaymsg seat - cursor set 200 "$1" >/dev/null; sleep 0.2; }
    { while true; do mousesety 200; mousesety 300; mousesety 400; done } &
    trap 'kill $!' EXIT
    export BEMENU_BACKEND=wayland BEMENU_OPTS='--list 40 --hb #0000FF'
    yes | head -30 | bemenu

Fixes: #274
Fixes: #275
This commit is contained in:
Joan Bruguera 2022-07-02 20:58:13 +02:00 committed by Jari Vetoniemi
parent 7d2c189865
commit 04b0d83d56

View File

@ -14,22 +14,23 @@
static int efd; static int efd;
static void static void
render(struct bm_menu *menu) render_windows_if_pending(const struct bm_menu *menu, struct wayland *wayland) {
{
struct wayland *wayland = menu->renderer->internal;
wl_display_dispatch_pending(wayland->display);
if (wl_display_flush(wayland->display) < 0 && errno != EAGAIN) {
wayland->input.sym = XKB_KEY_Escape;
return;
}
struct window *window; struct window *window;
wl_list_for_each(window, &wayland->windows, link) { wl_list_for_each(window, &wayland->windows, link) {
if (window->render_pending) if (window->render_pending)
bm_wl_window_render(window, wayland->display, menu); bm_wl_window_render(window, wayland->display, menu);
} }
wl_display_flush(wayland->display); wl_display_flush(wayland->display);
}
static void
wait_for_events(struct wayland *wayland) {
wl_display_dispatch_pending(wayland->display);
if (wl_display_flush(wayland->display) < 0 && errno != EAGAIN) {
wayland->input.sym = XKB_KEY_Escape;
return;
}
struct epoll_event ep[16]; struct epoll_event ep[16];
uint32_t num = epoll_wait(efd, ep, 16, -1); uint32_t num = epoll_wait(efd, ep, 16, -1);
@ -42,13 +43,33 @@ render(struct bm_menu *menu)
bm_wl_repeat(wayland); bm_wl_repeat(wayland);
} }
} }
}
if (menu->dirty) { static void
menu->dirty = false; schedule_windows_render_if_dirty(struct bm_menu *menu, struct wayland *wayland) {
wl_list_for_each(window, &wayland->windows, link) { struct window *window;
wl_list_for_each(window, &wayland->windows, link) {
if (window->render_pending) {
// This does not happen during normal execution, but only when the windows need to
// be(re)created. We need to do the render ASAP (not schedule it) because otherwise,
// since we lack a window, we may not receive further events and will get deadlocked
render_windows_if_pending(menu, wayland);
} else if (menu->dirty) {
bm_wl_window_schedule_render(window); bm_wl_window_schedule_render(window);
} }
} }
menu->dirty = false;
}
static void
render(struct bm_menu *menu)
{
struct wayland *wayland = menu->renderer->internal;
schedule_windows_render_if_dirty(menu, wayland);
wait_for_events(wayland);
render_windows_if_pending(menu, wayland);
} }
static enum bm_key static enum bm_key