wayland: latch xdg_toplevel configure state until xdg_surface.configure

Per xdg-shell, xdg_toplevel.configure events describe pending state
that only becomes current when the compositor sends
xdg_surface.configure, which the client must acknowledge.  RetroArch
applied toplevel state (size, fullscreen/maximized flags, the EGL
window resize via the driver hook) immediately at toplevel.configure
and blind-acked xdg_surface.configure from the shared input-common
listener, i.e. state was applied before the compositor's atomic
point and the ack carried no meaning.

This is the suspected root of the long-standing fullscreen state
cluster on stricter compositors: fullscreen reverting to a window on
core launch with core-dependent (timing-dependent) reproduction
(#14481), fullscreen re-entry wedging on KWin until restart including
via internal-resolution switches (forum 48446), wrong-size/offset
fullscreen repaired by alt-tab - i.e. by the next configure cycle -
(#16779, #17372), and the pre-fullscreen splash artifact (#14408)
that the in-tree ignore_configuration / legacy-fullscreen hacks were
added to paper over.

xdg_toplevel.configure now only records the advertised size and
states into an embedded cfg_pending struct (last-wins on repeated
events, no allocation); a gfx-local xdg_surface listener applies the
latched state, invokes the per-backend hook, clears 'configured',
and acknowledges with the serial.  ignore_configuration is evaluated
at the apply point and drops the latched configuration exactly as
the old code dropped the toplevel event; the legacy-fullscreen
fallback is preserved unchanged inside the apply.  No commits are
added to the configure path: as before, acknowledged state takes
effect on the frame loop's next commit.  The libdecor path is
untouched - libdecor's configure callback is already the atomic
point and commits via libdecor_frame_commit.

Validated under headless Weston 13 (Vulkan/lavapipe, imageviewer
content), A/B against master:
- windowed startup: structurally identical protocol traces;
- fullscreen startup (the bug-cluster path): identical request
  sequences (set_fullscreen, fullscreen configure 1024x640 with
  4 states, ack), identical applied swapchain resolution, zero
  protocol errors.
So: no regression on the one compositor testable here, with the
intended behavioral difference confined to when state is applied.

NOT verified: Mutter and KWin, interactive resize, and runtime
libdecor (compile-covered only; Weston runs took the plain xdg
path).  The issues above double as the test plan - this should be
confirmed against KWin (forum 48446 repro: fullscreen -> windowed ->
fullscreen, and internal-resolution switching) and a multi-monitor
Mutter/KWin setup (#16779/#17372 repro) before or promptly after
merging, and backed out if the cluster does not improve.
This commit is contained in:
libretroadmin authored and libretroadmin committed 2026-07-20 04:03:22 +02:00
1 parent 35dcafb253
commit 7eefa407e0
2 files changed
+97 -40

No files matched your search

+84 -40
View File
@@ -102,41 +102,22 @@ static bool gfx_ctx_wl_should_use_legacy_fullscreen_configure(
return false;
}
static void xdg_toplevel_handle_configure_common(gfx_ctx_wayland_data_t *wl,
void *toplevel,
int32_t width, int32_t height, struct wl_array *states)
/* Apply a latched xdg_toplevel configuration. Runs from the
* xdg_surface.configure handler: per xdg-shell, toplevel configure
* events describe pending state that only becomes current when the
* compositor sends xdg_surface.configure. */
static void xdg_configure_apply(gfx_ctx_wayland_data_t *wl)
{
const uint32_t *state;
bool floating = true;
int32_t width = wl->cfg_pending.width;
int32_t height = wl->cfg_pending.height;
bool floating = wl->cfg_pending.floating;
wl->fullscreen = false;
wl->maximized = false;
WL_ARRAY_FOR_EACH(state, states, const uint32_t*)
{
switch (*state)
{
case XDG_TOPLEVEL_STATE_FULLSCREEN:
wl->fullscreen = true;
floating = false;
break;
case XDG_TOPLEVEL_STATE_MAXIMIZED:
wl->maximized = true;
/* fall-through */
case XDG_TOPLEVEL_STATE_TILED_LEFT:
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
case XDG_TOPLEVEL_STATE_TILED_TOP:
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
floating = false;
break;
case XDG_TOPLEVEL_STATE_RESIZING:
wl->resize = true;
break;
case XDG_TOPLEVEL_STATE_ACTIVATED:
wl->activated = true;
break;
}
}
wl->fullscreen = wl->cfg_pending.fullscreen;
wl->maximized = wl->cfg_pending.maximized;
if (wl->cfg_pending.resizing)
wl->resize = true;
if (wl->cfg_pending.activated)
wl->activated = true;
if (width == 0 || height == 0)
{
@@ -307,14 +288,77 @@ static void xdg_toplevel_handle_configure(void *data,
int32_t width, int32_t height, struct wl_array *states)
{
gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data;
if (wl->ignore_configuration)
return;
xdg_toplevel_handle_configure_common(wl, toplevel, width, height, states);
if (wl->driver_configure_handler)
wl->driver_configure_handler(wl);
wl->configured = false;
const uint32_t *state;
/* Record only; the state is pending until xdg_surface.configure.
* A later toplevel.configure before the surface.configure
* supersedes this one (last-wins), matching the protocol. */
wl->cfg_pending.width = width;
wl->cfg_pending.height = height;
wl->cfg_pending.fullscreen = false;
wl->cfg_pending.maximized = false;
wl->cfg_pending.resizing = false;
wl->cfg_pending.activated = false;
wl->cfg_pending.floating = true;
WL_ARRAY_FOR_EACH(state, states, const uint32_t*)
{
switch (*state)
{
case XDG_TOPLEVEL_STATE_FULLSCREEN:
wl->cfg_pending.fullscreen = true;
wl->cfg_pending.floating = false;
break;
case XDG_TOPLEVEL_STATE_MAXIMIZED:
wl->cfg_pending.maximized = true;
/* fall-through */
case XDG_TOPLEVEL_STATE_TILED_LEFT:
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
case XDG_TOPLEVEL_STATE_TILED_TOP:
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
wl->cfg_pending.floating = false;
break;
case XDG_TOPLEVEL_STATE_RESIZING:
wl->cfg_pending.resizing = true;
break;
case XDG_TOPLEVEL_STATE_ACTIVATED:
wl->cfg_pending.activated = true;
break;
}
}
wl->cfg_pending.pending = true;
}
static void xdg_surface_handle_configure_latch(void *data,
struct xdg_surface *xdg_surface, uint32_t serial)
{
gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data;
if (wl->cfg_pending.pending)
{
wl->cfg_pending.pending = false;
/* ignore_configuration drops the configuration exactly as the
* pre-latching code dropped it at toplevel.configure time;
* evaluated here, at the atomic point. */
if (!wl->ignore_configuration)
{
xdg_configure_apply(wl);
if (wl->driver_configure_handler)
wl->driver_configure_handler(wl);
wl->configured = false;
}
}
/* Always acknowledge; the new state takes effect on the next
* wl_surface.commit, which the frame loop performs. */
xdg_surface_ack_configure(xdg_surface, serial);
}
static const struct xdg_surface_listener wl_xdg_surface_listener = {
xdg_surface_handle_configure_latch,
};
static const struct xdg_toplevel_listener wl_xdg_toplevel_listener = {
xdg_toplevel_handle_configure,
xdg_toplevel_handle_close,
@@ -988,7 +1032,7 @@ bool gfx_ctx_wl_init_common(
#endif
{
wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_shell, wl->surface);
xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl);
xdg_surface_add_listener(wl->xdg_surface, &wl_xdg_surface_listener, wl);
wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface);
xdg_toplevel_add_listener(wl->xdg_toplevel, &wl_xdg_toplevel_listener, wl);
+13
View File
@@ -268,6 +268,19 @@ typedef struct gfx_ctx_wayland_data
bool configured;
bool ignore_configuration;
driver_configure_handler_t driver_configure_handler;
/* State from xdg_toplevel.configure, held until the compositor's
* xdg_surface.configure marks it current (xdg-shell latching). */
struct
{
int32_t width;
int32_t height;
bool fullscreen;
bool maximized;
bool resizing;
bool activated;
bool floating;
bool pending;
} cfg_pending;
bool activated;
bool reported_display_size;
bool swap_complete;