wl_client_view_unmap: use 1x1 transparent buffer instead of NULL to avoid xdg unmap

NULL-buffer unmap triggers a wlroots xdg_surface unmap, which requires a
configure/ack cycle before the next buffer attach. This creates a race
window where the popup reopen cannot commit its real buffer yet, causing
the click to hit the main window instead of the popup.

Fix: replace the popup buffer with a 1x1 transparent ARGB buffer instead
of attaching NULL. This hides the popup visually (the tiny buffer is
invisible) without unmapping the xdg_surface. The surface stays configured
and alive, so the next popup open can commit a real buffer immediately
without waiting for a configure event.

Also: always call resizeMenu() in popUpMenu to ensure the backing buffer
is recreated at the correct dimensions after the 1x1 replacement.
This commit is contained in:
lkn 2026-06-07 16:39:15 +02:00
parent 2a335646ea
commit 230eefd8d3
2 changed files with 13 additions and 12 deletions

View File

@ -568,17 +568,16 @@ wl_client_view_unmap(WNativeWindow id)
{
WLClientToplevel *top = wlc_top_find(id);
if (!top || !top->surface) return;
/* Attach NULL + commit: the standard Wayland way
* for a client to hide a surface from the compositor.
* The xdg_toplevel object stays alive for remap. */
/* Attach NULL + commit: the standard Wayland way for a client to
* hide a surface. This triggers a wlroots unmap and the compositor
* will send a new xdg_surface.configure before we can re-attach a
* real buffer. We set configured=0 so wl_client_toplevel_commit_locked
* blocks all buffer attachments until the configure arrives. */
wl_surface_attach(top->surface, NULL, 0, 0);
/* Replace the popup buffer with a 1x1 transparent pixel instead of
* attaching NULL (which triggers a wlroots xdg unmap + configure
* cycle). A 1x1 invisible buffer keeps the surface alive and
* configured, so the next popup open can commit immediately. */
if (top->buf) wlclient_destroy_shm_buffer(top->buf);
top->buf = wlclient_create_shm_buffer(top->ctx, 1, 1);
if (!top->buf) return;
wl_surface_attach(top->surface, top->buf->wl_buf, 0, 0);
wl_surface_damage_buffer(top->surface, 0, 0, 1, 1);
wl_surface_commit(top->surface);
top->configured = 0;
if (top->ctx && top->ctx->display)
wl_display_flush(top->ctx->display);
}

View File

@ -481,8 +481,10 @@ static void popUpMenu(PopUpButton * bPtr)
/* Resize before map so the backing has the correct dimensions
* before any rendering or commit happens. */
if (W_VIEW_WIDTH(bPtr->menuView) != W_VIEW_WIDTH(bPtr->view))
resizeMenu(bPtr);
/* Always resize to ensure the backing matches the current item count
* and pointer position. After a 1x1-buffer unmap, the buffer must be
* recreated at the correct dimensions before wl_redisplay paints. */
resizeMenu(bPtr);
/* Map first — wl_map calls wl_redisplay internally, which fills
* the background and dispatches WME_EXPOSE to paint all entries.