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 <vlad.zahorodnii@kde.org>
This commit is contained in:
Vlad Zahorodnii 2025-12-17 10:12:33 +02:00
parent ac42c39145
commit 8d3ef700c7
5 changed files with 66 additions and 9 deletions

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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:

View file

@ -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;