Skip to content

Commit

Permalink
Fix Wayland event loop order to avoid missed renders
Browse files Browse the repository at this point in the history
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
  • Loading branch information
joanbm authored and Cloudef committed Jul 5, 2022
1 parent 7d2c189 commit 04b0d83
Showing 1 changed file with 34 additions and 13 deletions.
47 changes: 34 additions & 13 deletions lib/renderers/wayland/wayland.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@
static int efd;

static void
render(struct bm_menu *menu)
{
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;
}

render_windows_if_pending(const struct bm_menu *menu, struct wayland *wayland) {
struct window *window;
wl_list_for_each(window, &wayland->windows, link) {
if (window->render_pending)
bm_wl_window_render(window, wayland->display, menu);
}
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];
uint32_t num = epoll_wait(efd, ep, 16, -1);
Expand All @@ -42,13 +43,33 @@ render(struct bm_menu *menu)
bm_wl_repeat(wayland);
}
}
}

if (menu->dirty) {
menu->dirty = false;
wl_list_for_each(window, &wayland->windows, link) {
static void
schedule_windows_render_if_dirty(struct bm_menu *menu, struct wayland *wayland) {
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);
}
}

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
Expand Down

0 comments on commit 04b0d83

Please sign in to comment.