SDL: Handle XWayland not sending display disconnected events

From e9632c83c70f35f581a8a1e89936bdad06b1e6b8 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 4 Mar 2025 17:23:23 -0800
Subject: [PATCH] Handle XWayland not sending display disconnected events

Also updated X11_CheckDisplaysMoved() to handle multiple X11 screens

Fixes https://github.com/libsdl-org/SDL/issues/12462
---
 src/video/x11/SDL_x11modes.c | 72 +++++++++++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 9 deletions(-)

diff --git a/src/video/x11/SDL_x11modes.c b/src/video/x11/SDL_x11modes.c
index e17a2d105c5ef..37198a72eb190 100644
--- a/src/video/x11/SDL_x11modes.c
+++ b/src/video/x11/SDL_x11modes.c
@@ -555,22 +555,73 @@ static XRRScreenResources *X11_GetScreenResources(Display *dpy, int screen)
 
 static void X11_CheckDisplaysMoved(SDL_VideoDevice *_this, Display *dpy)
 {
-    const int screen = DefaultScreen(dpy);
-    XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
-    if (!res) {
+    const int screencount = ScreenCount(dpy);
+
+    SDL_DisplayID *displays = SDL_GetDisplays(NULL);
+    if (!displays) {
         return;
     }
 
-    SDL_DisplayID *displays = SDL_GetDisplays(NULL);
-    if (displays) {
+    for (int screen = 0; screen < screencount; ++screen) {
+        XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
+        if (!res) {
+            continue;
+        }
+
         for (int i = 0; displays[i]; ++i) {
             SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
             const SDL_DisplayData *displaydata = display->internal;
-            X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
+            if (displaydata->screen == screen) {
+                X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
+            }
         }
-        SDL_free(displays);
+        X11_XRRFreeScreenResources(res);
     }
-    X11_XRRFreeScreenResources(res);
+    SDL_free(displays);
+}
+
+static void X11_CheckDisplaysRemoved(SDL_VideoDevice *_this, Display *dpy)
+{
+    const int screencount = ScreenCount(dpy);
+    int num_displays = 0;
+
+    SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
+    if (!displays) {
+        return;
+    }
+
+    for (int screen = 0; screen < screencount; ++screen) {
+        XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
+        if (!res) {
+            continue;
+        }
+
+        for (int output = 0; output < res->noutput; output++) {
+            for (int i = 0; i < num_displays; ++i) {
+                if (!displays[i]) {
+                    // We already removed this display from the list
+                    continue;
+                }
+
+                SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
+                const SDL_DisplayData *displaydata = display->internal;
+                if (displaydata->xrandr_output == res->outputs[output]) {
+                    // This display is active, remove it from the list
+                    displays[i] = 0;
+                    break;
+                }
+            }
+        }
+        X11_XRRFreeScreenResources(res);
+    }
+
+    for (int i = 0; i < num_displays; ++i) {
+        if (displays[i]) {
+            // This display wasn't in the XRandR list
+            SDL_DelVideoDisplay(displays[i], true);
+        }
+    }
+    SDL_free(displays);
 }
 
 static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev)
@@ -580,9 +631,12 @@ static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutput
     int i;
 
 #if 0
-    printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection);
+    printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]\n", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection);
 #endif
 
+    // XWayland doesn't always send output disconnected events
+    X11_CheckDisplaysRemoved(_this, ev->display);
+
     displays = SDL_GetDisplays(NULL);
     if (displays) {
         for (i = 0; displays[i]; ++i) {