SDL: x11: Support sorting displays via the priority hint

From a7da3ad59bd8ec70f0cd813d15875fa2731a83eb Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 18 Oct 2024 11:26:41 -0400
Subject: [PATCH] x11: Support sorting displays via the priority hint

Store the connector name for displays and use it for sorting them according to priority, if the hint is set.
---
 include/SDL3/SDL_hints.h     |  1 +
 src/video/x11/SDL_x11modes.c | 49 ++++++++++++++++++++++++++++++++++++
 src/video/x11/SDL_x11modes.h |  1 +
 3 files changed, 51 insertions(+)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 3afd1a5676344..e097ce072f233 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -3158,6 +3158,7 @@ extern "C" {
  *
  * - KMSDRM (kmsdrm)
  * - Wayland (wayland)
+ * - X11 (x11)
  *
  * This hint should be set before SDL is initialized.
  *
diff --git a/src/video/x11/SDL_x11modes.c b/src/video/x11/SDL_x11modes.c
index 1194af2c0e9de..f2170c4ba6f5c 100644
--- a/src/video/x11/SDL_x11modes.c
+++ b/src/video/x11/SDL_x11modes.c
@@ -623,6 +623,7 @@ static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int
     displaydata->y = display_y;
     displaydata->use_xrandr = true;
     displaydata->xrandr_output = outputid;
+    SDL_strlcpy(displaydata->connector_name, display_name, sizeof(displaydata->connector_name));
 
     SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode);
     SetXRandRDisplayName(dpy, EDID, display_name, display_name_size, outputid, display_mm_width, display_mm_height);
@@ -751,6 +752,52 @@ void X11_HandleXRandREvent(SDL_VideoDevice *_this, const XEvent *xevent)
     }
 }
 
+static void X11_SortOutputsByPriorityHint(SDL_VideoDevice *_this)
+{
+    const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
+
+    if (name_hint) {
+        char *saveptr;
+        char *str = SDL_strdup(name_hint);
+        SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays);
+
+        if (str && sorted_list) {
+            int sorted_index = 0;
+
+            // Sort the requested displays to the front of the list.
+            const char *token = SDL_strtok_r(str, ",", &saveptr);
+            while (token) {
+                for (int i = 0; i < _this->num_displays; ++i) {
+                    SDL_VideoDisplay *d = _this->displays[i];
+                    if (d) {
+                        SDL_DisplayData *data = d->internal;
+                        if (SDL_strcmp(token, data->connector_name) == 0) {
+                            sorted_list[sorted_index++] = d;
+                            _this->displays[i] = NULL;
+                            break;
+                        }
+                    }
+                }
+
+                token = SDL_strtok_r(NULL, ",", &saveptr);
+            }
+
+            // Append the remaining displays to the end of the list.
+            for (int i = 0; i < _this->num_displays; ++i) {
+                if (_this->displays[i]) {
+                    sorted_list[sorted_index++] = _this->displays[i];
+                }
+            }
+
+            // Copy the sorted list back to the display list.
+            SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays);
+        }
+
+        SDL_free(str);
+        SDL_free(sorted_list);
+    }
+}
+
 static bool X11_InitModes_XRandR(SDL_VideoDevice *_this)
 {
     SDL_VideoData *data = _this->internal;
@@ -810,6 +857,8 @@ static bool X11_InitModes_XRandR(SDL_VideoDevice *_this)
         return SDL_SetError("No available displays");
     }
 
+    X11_SortOutputsByPriorityHint(_this);
+
     return true;
 }
 #endif // SDL_VIDEO_DRIVER_X11_XRANDR
diff --git a/src/video/x11/SDL_x11modes.h b/src/video/x11/SDL_x11modes.h
index 4c250b6a72cb0..d8a92032bd1d5 100644
--- a/src/video/x11/SDL_x11modes.h
+++ b/src/video/x11/SDL_x11modes.h
@@ -38,6 +38,7 @@ struct SDL_DisplayData
 
 #ifdef SDL_VIDEO_DRIVER_X11_XRANDR
     RROutput xrandr_output;
+    char connector_name[16];
 #endif
 };