wl_unmap: recycle WINGs client wl_surface on popup close to eliminate stale pixels

The stale popup content visible under the main window after close comes from
the wl_redisplay composite of the popup into the parent backing (rootView).
Even though rootView is not committed to any wl_surface, the popup surface
itself retains its last committed buffer. Disabling the scene node is
insufficient for completely eliminating visual artifacts.

Fix: add wl_client_toplevel_recycle which destroys the xdg_toplevel,
xdg_surface, wl_surface, and SHM buffer but preserves the WLClientToplevel
struct and its WINGs window ID mapping. On unmap (wl_unmap), call recycle
for topLevel popups. On remap (wl_map), call wl_client_toplevel_remap which
recreates all Wayland protocol objects from scratch, triggering a fresh
configure cycle. This ensures no stale buffer or state persists between
open/close cycles.
This commit is contained in:
lkn 2026-06-07 15:29:33 +02:00
parent 60efe4db78
commit 118a7c30a9
3 changed files with 105 additions and 1 deletions

View File

@ -560,6 +560,91 @@ wl_client_toplevel_destroy(struct W_View *view)
wl_client_view_destroy_backing((unsigned long)view->window);
}
void
wl_client_toplevel_recycle(WNativeWindow id)
{
WLClientToplevel *top = wlc_top_find(id);
if (!top) return;
/* Destroy the Wayland protocol objects but keep the
* WLClientToplevel struct and its view->window mapping,
* so the surface can be recreated cleanly on remap. */
if (top->wmaker_popup) {
wmaker_popup_v1_destroy(top->wmaker_popup);
top->wmaker_popup = NULL;
}
if (top->xdg_toplevel) {
xdg_toplevel_destroy(top->xdg_toplevel);
top->xdg_toplevel = NULL;
}
if (top->xdg_surface) {
xdg_surface_destroy(top->xdg_surface);
top->xdg_surface = NULL;
}
if (top->surface) {
wl_surface_destroy(top->surface);
top->surface = NULL;
}
if (top->buf) {
wlclient_destroy_shm_buffer(top->buf);
top->buf = NULL;
}
top->configured = 0;
top->mapped = 0;
top->want_close = 0;
}
static void
wl_client_toplevel_remap_locked(WLClientToplevel *top)
{
if (!top || top->surface) return; /* already mapped */
WLClientCtx *ctx = top->ctx;
if (!ctx || !ctx->compositor || !ctx->wm_base) return;
top->buf = wlclient_create_shm_buffer(ctx,
top->pending_w, top->pending_h);
if (!top->buf) return;
top->surface = wl_compositor_create_surface(ctx->compositor);
if (!top->surface) return;
top->xdg_surface = xdg_wm_base_get_xdg_surface(
ctx->wm_base, top->surface);
if (!top->xdg_surface) return;
xdg_surface_add_listener(top->xdg_surface,
&xdg_surface_listener, top);
top->xdg_toplevel = xdg_surface_get_toplevel(
top->xdg_surface);
if (!top->xdg_toplevel) return;
xdg_toplevel_add_listener(top->xdg_toplevel,
&xdg_toplevel_listener, top);
if (top->view->flags.popup) {
xdg_toplevel_set_app_id(top->xdg_toplevel, "wings-popup");
xdg_toplevel_set_title(top->xdg_toplevel, "");
if (ctx->wmaker_popup_mgr) {
top->wmaker_popup =
wmaker_popup_manager_v1_get_popup(
ctx->wmaker_popup_mgr,
top->xdg_toplevel);
if (top->wmaker_popup && ctx->main_toplevel)
wmaker_popup_v1_set_parent(
top->wmaker_popup,
ctx->main_toplevel);
}
}
wl_surface_commit(top->surface);
W_SetViewBacking(top->view->window, top->buf->image);
}
void
wl_client_toplevel_remap(WNativeWindow id)
{
WLClientToplevel *top = wlc_top_find(id);
wl_client_toplevel_remap_locked(top);
}
void
W_WLClientSetMinMaxSize(W_View *view)
{

View File

@ -16,6 +16,10 @@ static unsigned int wl_view_next_id = WL_VIEW_ID_BASE;
/* Callback set by WM backend to commit frame_buf after paint */
void (*W_ViewCommitBackingFn)(unsigned long id) = NULL;
/* Optional hook: destroy wl_surface + xdg_toplevel on popup unmap
* so the compositor has no stale state. Set by the WM backend. */
void (*W_WLClientToplevelRecycleFn)(unsigned long id) = NULL;
void (*W_WLClientToplevelRemapFn)(unsigned long id) = NULL;
void (*W_ViewDestroyBackingFn)(unsigned long id) = NULL;
void (*W_ViewSceneNodeSetEnabledFn)(unsigned long id, int enabled) = NULL;
@ -211,6 +215,12 @@ static void wl_unmap(void *vctx, W_View *view)
/* Disable the scene buffer so the view disappears from screen. */
if (W_ViewSceneNodeSetEnabledFn)
W_ViewSceneNodeSetEnabledFn((unsigned long)view->window, 0);
/* Destroy the WINGs client wl_surface + xdg_toplevel on unmap so
* the compositor has no stale state for this surface. The recycle
* function preserves the WLClientToplevel struct and ID mapping
* so the surface can be recreated on the next map. */
if (view->flags.topLevel && view->flags.popup && W_WLClientToplevelRecycleFn)
W_WLClientToplevelRecycleFn((unsigned long)view->window);
if (view->parent && view->parent->flags.mapped)
wl_redisplay(vctx, view->parent);
}
@ -305,7 +315,10 @@ static void wl_map(void *vctx, W_View *view)
child = child->nextSister;
}
}
/* Re-enable scene node after unmap so the surface becomes visible again. */
/* Re-enable scene node after unmap so the surface becomes visible again.
* For recycled popups, recreate the wl_surface + xdg_toplevel first. */
if (view->flags.topLevel && view->flags.popup && W_WLClientToplevelRemapFn)
W_WLClientToplevelRemapFn((unsigned long)view->window);
if (W_ViewSceneNodeSetEnabledFn)
W_ViewSceneNodeSetEnabledFn((unsigned long)view->window, 1);

View File

@ -470,6 +470,12 @@ wl_display_open(const char *name)
unsigned int, int, int, unsigned long);
extern void wl_pointer_ungrab(unsigned long);
W_WLPointerGrabFn = wl_pointer_grab;
extern void (*W_WLClientToplevelRecycleFn)(unsigned long);
extern void wl_client_toplevel_recycle(WNativeWindow);
W_WLClientToplevelRecycleFn = wl_client_toplevel_recycle;
extern void (*W_WLClientToplevelRemapFn)(unsigned long);
extern void wl_client_toplevel_remap(WNativeWindow);
W_WLClientToplevelRemapFn = wl_client_toplevel_remap;
W_WLPointerUngrabFn = wl_pointer_ungrab;
extern void (*W_ClipboardStoreFn)(const char *text, int len);