From 7931321cffa27d4168738ddbe2251966f243afc9 Mon Sep 17 00:00:00 2001 From: Jipok Date: Thu, 8 Jan 2026 00:46:41 +0500 Subject: [PATCH] X11: Handle WM_STATE transitions to detect Withdrawn/Iconic states (#14770) When running SDL3 applications on tiling window managers like i3, moving a window to an invisible workspace does not trigger SDL_WINDOW_MINIMIZED or SDL_WINDOW_HIDDEN. Consequently, the application continues rendering at full speed (VSync dependent), consuming unnecessary GPU/CPU resources even when not visible. When a workspace is hidden, i3(and possible other tiling WMs) unmaps the container and sets the client window state to WithdrawnState (via the WM_STATE atom). Previously, the SDL3 X11 backend ignored changes to WM_STATE during PropertyNotify events, failing to detect this transition. --- src/video/x11/SDL_x11events.c | 30 ++++++++++++++++++++++++++++++ src/video/x11/SDL_x11video.c | 1 + src/video/x11/SDL_x11video.h | 1 + 3 files changed, 32 insertions(+) diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index ebd24ad617..cea241edde 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -2085,6 +2085,36 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) if (changed & SDL_WINDOW_OCCLUDED) { SDL_SendWindowEvent(data->window, (flags & SDL_WINDOW_OCCLUDED) ? SDL_EVENT_WINDOW_OCCLUDED : SDL_EVENT_WINDOW_EXPOSED, 0, 0); } + } else if (xevent->xproperty.atom == videodata->atoms.WM_STATE) { + /* Support for ICCCM-compliant window managers (like i3) that change + WM_STATE to WithdrawnState without sending UnmapNotify or updating + _NET_WM_STATE when moving windows to invisible workspaces. */ + Atom type; + int format; + unsigned long nitems, bytes_after; + unsigned char *prop_data = NULL; + + if (X11_XGetWindowProperty(display, data->xwindow, videodata->atoms.WM_STATE, + 0L, 2L, False, videodata->atoms.WM_STATE, + &type, &format, &nitems, &bytes_after, &prop_data) == Success) { + if (nitems > 0) { + // WM_STATE: 0=Withdrawn, 1=Normal, 3=Iconic + Uint32 state = *(Uint32 *)prop_data; + + if (state == 0 || state == 3) { // Withdrawn or Iconic + if (!(data->window->flags & SDL_WINDOW_MINIMIZED)) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); + } + } else if (state == 1) { // NormalState + if (data->window->flags & SDL_WINDOW_MINIMIZED) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); + } + } + } + X11_XFree(prop_data); + } } else if (xevent->xproperty.atom == videodata->atoms.XKLAVIER_STATE) { /* Hack for Ubuntu 12.04 (etc) that doesn't send MappingNotify events when the keyboard layout changes (for example, diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index c4f00021f5..90cb140e53 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -362,6 +362,7 @@ static bool X11_VideoInit(SDL_VideoDevice *_this) GET_ATOM(WM_TAKE_FOCUS); GET_ATOM(WM_NAME); GET_ATOM(WM_TRANSIENT_FOR); + GET_ATOM(WM_STATE); GET_ATOM(_NET_WM_STATE); GET_ATOM(_NET_WM_STATE_HIDDEN); GET_ATOM(_NET_WM_STATE_FOCUSED); diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 2dc4d3ab60..32c3722784 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -73,6 +73,7 @@ struct SDL_VideoData Atom WM_TAKE_FOCUS; Atom WM_NAME; Atom WM_TRANSIENT_FOR; + Atom WM_STATE; Atom _NET_WM_STATE; Atom _NET_WM_STATE_HIDDEN; Atom _NET_WM_STATE_FOCUSED;