SDL: Fixed display detection after the monitor resolution changed on X11

From 9e6d5babcea042e049b41991063e3d34a6fb028c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 24 Jul 2024 17:46:25 -0700
Subject: [PATCH] Fixed display detection after the monitor resolution changed
 on X11

---
 src/video/x11/SDL_x11modes.c | 107 +++++++++++++++++++++++++----------
 1 file changed, 78 insertions(+), 29 deletions(-)

diff --git a/src/video/x11/SDL_x11modes.c b/src/video/x11/SDL_x11modes.c
index b6dc61fcc8f7a..9419f77305156 100644
--- a/src/video/x11/SDL_x11modes.c
+++ b/src/video/x11/SDL_x11modes.c
@@ -25,6 +25,7 @@
 #include "SDL_x11video.h"
 #include "SDL_x11settings.h"
 #include "edid.h"
+#include "../../events/SDL_displayevents_c.h"
 
 /* #define X11MODES_DEBUG */
 
@@ -529,17 +530,15 @@ static void SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size
 #endif
 }
 
-static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_bool send_event)
+static int X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *display, char *display_name, size_t display_name_size)
 {
     Atom EDID = X11_XInternAtom(dpy, "EDID", False);
     XRROutputInfo *output_info;
     int display_x, display_y;
     unsigned long display_mm_width, display_mm_height;
     SDL_DisplayData *displaydata;
-    char display_name[128];
     SDL_DisplayMode mode;
     SDL_DisplayModeData *modedata;
-    SDL_VideoDisplay display;
     RRMode modeID;
     RRCrtc output_crtc;
     XRRCrtcInfo *crtc;
@@ -549,13 +548,17 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen
     int scanline_pad;
     int i, n;
 
+    if (!display || !display_name) {
+        return -1; /* invalid parameters */
+    }
+
     if (get_visualinfo(dpy, screen, &vinfo) < 0) {
-        return 0; /* uh, skip this screen? */
+        return -1; /* uh, skip this screen? */
     }
 
     pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
     if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
-        return 0; /* Palettized video modes are no longer supported, ignore this one. */
+        return -1; /* Palettized video modes are no longer supported, ignore this one. */
     }
 
     scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
@@ -573,10 +576,10 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen
     output_info = X11_XRRGetOutputInfo(dpy, res, outputid);
     if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) {
         X11_XRRFreeOutputInfo(output_info);
-        return 0; /* ignore this one. */
+        return -1; /* ignore this one. */
     }
 
-    SDL_strlcpy(display_name, output_info->name, sizeof(display_name));
+    SDL_strlcpy(display_name, output_info->name, display_name_size);
     display_mm_width = output_info->mm_width;
     display_mm_height = output_info->mm_height;
     output_crtc = output_info->crtc;
@@ -584,7 +587,7 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen
 
     crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc);
     if (!crtc) {
-        return 0; /* oh well, ignore it. */
+        return -1; /* oh well, ignore it. */
     }
 
     SDL_zero(mode);
@@ -622,18 +625,62 @@ static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen
     displaydata->xrandr_output = outputid;
 
     SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode);
-    SetXRandRDisplayName(dpy, EDID, display_name, sizeof(display_name), outputid, display_mm_width, display_mm_height);
+    SetXRandRDisplayName(dpy, EDID, display_name, display_name_size, outputid, display_mm_width, display_mm_height);
 
-    SDL_zero(display);
+    SDL_zero(*display);
     if (*display_name) {
-        display.name = display_name;
+        display->name = display_name;
     }
-    display.desktop_mode = mode;
-    display.content_scale = GetGlobalContentScale(_this);
-    display.internal = displaydata;
+    display->desktop_mode = mode;
+    display->content_scale = GetGlobalContentScale(_this);
+    display->internal = displaydata;
+
+    return 0;
+}
+
+static int X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_bool send_event)
+{
+    SDL_VideoDisplay display;
+    char display_name[128];
+
+    if (X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name)) == -1) {
+        return 0; /* failed to query data, skip this display */
+    }
+
     if (SDL_AddVideoDisplay(&display, send_event) == 0) {
         return -1;
     }
+
+    return 0;
+}
+
+
+static int X11_UpdateXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *existing_display)
+{
+    SDL_VideoDisplay display;
+    char display_name[128];
+
+    if (X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name)) == -1) {
+        return -1; /* failed to query current display state */
+    }
+
+    /* update mode - this call takes ownership of display.desktop_mode.internal */
+    SDL_SetDesktopDisplayMode(existing_display, &display.desktop_mode);
+
+    /* update bounds */
+    if (existing_display->internal->x != display.internal->x ||
+        existing_display->internal->y != display.internal->y) {
+        existing_display->internal->x = display.internal->x;
+        existing_display->internal->y = display.internal->y;
+        SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
+    }
+
+    /* update scale */
+    SDL_SetDisplayContentScale(existing_display, display.content_scale);
+
+    /* SDL_DisplayData is updated piece-meal above, free our local copy of this data */
+    SDL_free( display.internal );
+
     return 0;
 }
 
@@ -664,25 +711,27 @@ static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutput
             SDL_DelVideoDisplay(display->id, SDL_TRUE);
         }
     } else if (ev->connection == RR_Connected) { /* output is coming online */
-        if (display) {
-            /* !!! FIXME: update rotation or current mode of existing display? */
-        } else {
-            Display *dpy = ev->display;
-            const int screen = DefaultScreen(dpy);
-            XVisualInfo vinfo;
-            if (get_visualinfo(dpy, screen, &vinfo) == 0) {
-                XRRScreenResources *res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen));
-                if (!res || res->noutput == 0) {
-                    if (res) {
-                        X11_XRRFreeScreenResources(res);
-                    }
-                    res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen));
+        Display *dpy = ev->display;
+        const int screen = DefaultScreen(dpy);
+        XVisualInfo vinfo;
+        if (get_visualinfo(dpy, screen, &vinfo) == 0) {
+            XRRScreenResources *res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen));
+            if (!res || res->noutput == 0) {
+                if (res) {
+                    X11_XRRFreeScreenResources(res);
                 }
+                res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen));
+            }
 
-                if (res) {
+            if (res) {
+                if (display) {
+                    X11_UpdateXRandRDisplay(_this, dpy, screen, ev->output, res, display);
+                }
+                else {
                     X11_AddXRandRDisplay(_this, dpy, screen, ev->output, res, SDL_TRUE);
-                    X11_XRRFreeScreenResources(res);
                 }
+
+                X11_XRRFreeScreenResources(res);
             }
         }
     }