SDL: kmsdrm: Support sorting displays via the priority hint

From 0faf9dc4a41a8babec565ea0b3df6e7b4cd01ea1 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 18 Oct 2024 11:25:10 -0400
Subject: [PATCH] kmsdrm: Support sorting displays via the priority hint

Use the connector name for displays and sort them according to priority, if the hint is set.
---
 include/SDL3/SDL_hints.h           |  5 ++-
 src/video/kmsdrm/SDL_kmsdrmsym.h   |  2 +
 src/video/kmsdrm/SDL_kmsdrmvideo.c | 60 ++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 33baa75c73f89..3afd1a5676344 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -3149,13 +3149,14 @@ extern "C" {
  * prioritized in the list of displays, as exposed by calling
  * SDL_GetDisplays(), with the first listed becoming the primary display. The
  * naming convention can vary depending on the environment, but it is usually
- * a connector name (e.g. 'DP-1', 'DP-2', 'HDMI-1', etc...).
+ * a connector name (e.g. 'DP-1', 'DP-2', 'HDMI-A-1',etc...).
  *
- * On X11 and Wayland desktops, the connector names associated with displays
+ * On Wayland and X11 desktops, the connector names associated with displays
  * can typically be found by using the `xrandr` utility.
  *
  * This hint is currently supported on the following drivers:
  *
+ * - KMSDRM (kmsdrm)
  * - Wayland (wayland)
  *
  * This hint should be set before SDL is initialized.
diff --git a/src/video/kmsdrm/SDL_kmsdrmsym.h b/src/video/kmsdrm/SDL_kmsdrmsym.h
index 1f0646636ebcd..5f389d854ab2f 100644
--- a/src/video/kmsdrm/SDL_kmsdrmsym.h
+++ b/src/video/kmsdrm/SDL_kmsdrmsym.h
@@ -63,6 +63,8 @@ SDL_KMSDRM_SYM_OPT(int,drmModeAddFB2WithModifiers,(int fd, uint32_t width,
                          const uint32_t pitches[4], const uint32_t offsets[4],
                          const uint64_t modifier[4], uint32_t *buf_id, uint32_t flags))
 
+SDL_KMSDRM_SYM_OPT(const char *,drmModeGetConnectorTypeName,(uint32_t connector_type))
+
 SDL_KMSDRM_SYM(int,drmModeRmFB,(int fd, uint32_t bufferId))
 SDL_KMSDRM_SYM(drmModeFBPtr,drmModeGetFB,(int fd, uint32_t buf))
 SDL_KMSDRM_SYM(drmModeCrtcPtr,drmModeGetCrtc,(int fd, uint32_t crtcId))
diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c
index 9097254ad7657..ae9b623583ccd 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvideo.c
+++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c
@@ -799,8 +799,10 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     SDL_DisplayModeData *modedata = NULL;
     drmModeEncoder *encoder = NULL;
     drmModeCrtc *crtc = NULL;
+    const char *connector_type = NULL;
     SDL_DisplayID display_id;
     SDL_PropertiesID display_properties;
+    char name_fmt[64];
     int orientation;
     int mode_index;
     int i, j;
@@ -963,6 +965,15 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
         KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
     }
 
+    // Set the name by the connector type, if possible
+    if (KMSDRM_drmModeGetConnectorTypeName) {
+        connector_type = KMSDRM_drmModeGetConnectorTypeName(connector->connector_type);
+        if (connector_type == NULL) {
+            connector_type = "Unknown";
+        }
+        SDL_snprintf(name_fmt, sizeof(name_fmt), "%s-%u", connector_type, connector->connector_type_id);
+    }
+
     /*****************************************/
     // Part 2: setup the SDL_Display itself.
     /*****************************************/
@@ -984,6 +995,9 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     CalculateRefreshRate(&dispdata->mode, &display.desktop_mode.refresh_rate_numerator, &display.desktop_mode.refresh_rate_denominator);
     display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
     display.desktop_mode.internal = modedata;
+    if (connector_type) {
+        display.name = name_fmt;
+    }
 
     // Add the display to the list of SDL displays.
     display_id = SDL_AddVideoDisplay(&display, false);
@@ -1016,6 +1030,49 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     }
 } // NOLINT(clang-analyzer-unix.Malloc): If no error `dispdata` is saved in the display
 
+static void KMSDRM_SortDisplays(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_strcmp(token, d->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);
+    }
+}
+
 /* Initializes the list of SDL displays: we build a new display for each
    connecter connector we find.
    This is to be called early, in VideoInit(), because it gets us
@@ -1078,6 +1135,9 @@ static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
         goto cleanup;
     }
 
+    // Sort the displays, if necessary
+    KMSDRM_SortDisplays(_this);
+
     // Determine if video hardware supports async pageflips.
     if (KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_ASYNC_PAGE_FLIP, &async_pageflip) != 0) {
         SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not determine async page flip capability.");