From 8d3ef700c7a5ad99aec1187871bb81e1e4dfb220 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 17 Dec 2025 10:12:33 +0200 Subject: [PATCH] xwayland: Add _XWAYLAND_SUSPENDED If a compositor stops sending frame callbacks to a mapped window (this can happen with some compositors if they don't unmap minimized windows or windows on other virtual desktops), the app may break. For example, it is a pretty common thing with video games. They may freeze and remain frozen even after getting brought back to foreground. This change adds the _XWAYLAND_SUSPENDED property so the compositor can provide a hint to Xwayland that the window won't receive frame callbacks any time soon and that it is better to send present complete notify events at approximately 60Hz instead of 1Hz. Signed-off-by: Vlad Zahorodnii --- hw/xwayland/xwayland-present.c | 3 +- hw/xwayland/xwayland-screen.c | 16 ++++++---- hw/xwayland/xwayland-screen.h | 1 + hw/xwayland/xwayland-window.c | 54 ++++++++++++++++++++++++++++++++-- hw/xwayland/xwayland-window.h | 1 + 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/hw/xwayland/xwayland-present.c b/hw/xwayland/xwayland-present.c index dcacb8ee8..546b38ffc 100644 --- a/hw/xwayland/xwayland-present.c +++ b/hw/xwayland/xwayland-present.c @@ -189,7 +189,8 @@ xwl_present_reset_timer(struct xwl_present_window *xwl_present_window) CARD32 timeout; if (xwl_window && xwl_window->frame_callback && - !xorg_list_is_empty(&xwl_present_window->frame_callback_list)) + !xorg_list_is_empty(&xwl_present_window->frame_callback_list) && + !xwl_window->suspended) timeout = TIMER_LEN_FLIP; else timeout = TIMER_LEN_COPY; diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c index 22c500801..9a7705e4a 100644 --- a/hw/xwayland/xwayland-screen.c +++ b/hw/xwayland/xwayland-screen.c @@ -171,7 +171,6 @@ xwl_property_callback(CallbackListPtr *pcbl, void *closure, { ScreenPtr screen = closure; PropertyStateRec *rec = calldata; - struct xwl_screen *xwl_screen; struct xwl_window *xwl_window; if (rec->win->drawable.pScreen != screen) @@ -181,10 +180,7 @@ xwl_property_callback(CallbackListPtr *pcbl, void *closure, if (!xwl_window) return; - xwl_screen = xwl_screen_get(screen); - - if (rec->prop->propertyName == xwl_screen->allow_commits_prop) - xwl_window_update_property(xwl_window, rec); + xwl_window_update_property(xwl_window, rec); } #ifdef XACE @@ -204,7 +200,8 @@ xwl_access_property_callback(CallbackListPtr *pcbl, void *closure, ScreenPtr pScreen = closure; struct xwl_screen *xwl_screen = xwl_screen_get(pScreen); - if (prop->propertyName == xwl_screen->allow_commits_prop) { + if (prop->propertyName == xwl_screen->allow_commits_prop || + prop->propertyName == xwl_screen->suspended_prop) { /* Only the WM and the Xserver itself */ if (client != serverClient && client->index != xwl_screen->wm_client_id && @@ -865,6 +862,7 @@ Bool xwl_screen_init(ScreenPtr pScreen, int argc, char **argv) { static const char allow_commits[] = "_XWAYLAND_ALLOW_COMMITS"; + static const char suspended[] = "_XWAYLAND_SUSPENDED"; struct xwl_screen *xwl_screen; Pixel red_mask, blue_mask, green_mask; int ret, bpc, green_bpc, i; @@ -1176,6 +1174,12 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv) if (xwl_screen->allow_commits_prop == BAD_RESOURCE) return FALSE; + xwl_screen->suspended_prop = MakeAtom(suspended, + strlen(suspended), + TRUE); + if (xwl_screen->suspended_prop == BAD_RESOURCE) + return FALSE; + AddCallback(&PropertyStateCallback, xwl_property_callback, pScreen); AddCallback(&RootWindowFinalizeCallback, xwl_root_window_finalized_callback, pScreen); #ifdef XACE diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h index ffbaa09e7..538e8db8d 100644 --- a/hw/xwayland/xwayland-screen.h +++ b/hw/xwayland/xwayland-screen.h @@ -141,6 +141,7 @@ struct xwl_screen { struct glamor_context *glamor_ctx; Atom allow_commits_prop; + Atom suspended_prop; /* The preferred GLVND vendor. If NULL, "mesa" is assumed. */ const char *glvnd_vendor; diff --git a/hw/xwayland/xwayland-window.c b/hw/xwayland/xwayland-window.c index dac4b4ed6..6a0e3f3bc 100644 --- a/hw/xwayland/xwayland-window.c +++ b/hw/xwayland/xwayland-window.c @@ -172,17 +172,67 @@ xwl_window_set_allow_commits_from_property(struct xwl_window *xwl_window, xwl_window_set_allow_commits(xwl_window, !!propdata[0], "from property"); } +static void +xwl_window_set_suspended(struct xwl_window *xwl_window, Bool suspended, + const char *debug_msg) +{ + xwl_window->suspended = suspended; + DebugF("XWAYLAND: win %d suspended = %u (%s)\n", + xwl_window->toplevel->drawable.id, suspended, debug_msg); +} + +static void +xwl_window_reset_suspended(struct xwl_window *xwl_window, const char *debug_msg) +{ + xwl_window_set_suspended(xwl_window, FALSE, debug_msg); +} + +static void +xwl_window_set_suspended_from_property(struct xwl_window *xwl_window, PropertyPtr prop) +{ + static Bool warned = FALSE; + CARD32 *propdata; + + if (prop->propertyName != xwl_window->xwl_screen->suspended_prop) + FatalError("Xwayland internal error: prop mismatch in %s.\n", __func__); + + if (prop->type != XA_CARDINAL || prop->format != 32 || prop->size != 1) { + /* Not properly set, so fall back to the default value */ + xwl_window_reset_suspended(xwl_window, "WM fault"); + + if (!warned) { + LogMessageVerb(X_WARNING, 0, "Window manager is misusing property %s.\n", + NameForAtom(prop->propertyName)); + warned = TRUE; + } + return; + } + + propdata = prop->data; + xwl_window_set_suspended(xwl_window, !!propdata[0], "from property"); +} + void xwl_window_update_property(struct xwl_window *xwl_window, PropertyStateRec *propstate) { + struct xwl_screen *xwl_screen = xwl_window->xwl_screen; + switch (propstate->state) { case PropertyNewValue: - xwl_window_set_allow_commits_from_property(xwl_window, propstate->prop); + if (propstate->prop->propertyName == xwl_screen->allow_commits_prop) { + xwl_window_set_allow_commits_from_property(xwl_window, propstate->prop); + } else if (propstate->prop->propertyName == xwl_screen->suspended_prop) { + xwl_window_set_suspended_from_property(xwl_window, propstate->prop); + } break; case PropertyDelete: - xwl_window_set_allow_commits(xwl_window, TRUE, "property deleted"); + if (propstate->prop->propertyName == xwl_screen->allow_commits_prop) { + xwl_window_set_allow_commits(xwl_window, TRUE, "property deleted"); + } else if (propstate->prop->propertyName == xwl_screen->suspended_prop) { + xwl_window_reset_suspended(xwl_window, "property deleted"); + } break; default: diff --git a/hw/xwayland/xwayland-window.h b/hw/xwayland/xwayland-window.h index ae3e531c6..6ea53d1fd 100644 --- a/hw/xwayland/xwayland-window.h +++ b/hw/xwayland/xwayland-window.h @@ -90,6 +90,7 @@ struct xwl_window { struct xorg_list link_damage; struct xorg_list link_window; struct wl_callback *frame_callback; + Bool suspended; Bool allow_commits; struct xorg_list window_buffers_available; struct xorg_list window_buffers_unavailable;