SDL: wayland: Use D-Bus to retrieve the cursor size and theme on GNOME

From 0b9868b02686f8256312821e4c6f0c7afd34270b Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 2 Aug 2022 12:56:56 -0400
Subject: [PATCH] wayland: Use D-Bus to retrieve the cursor size and theme on
 GNOME

GNOME exposes the cursor size and theme via the org.freedesktop.portal.Settings interface of the xdg-desktop portal, so query these values via D-Bus, if available.

The XCURSOR_SIZE/XCURSOR_THEME envvars will be tried first, so as not to override any user specified sizes or themes, then D-Bus, then, failing that, it will fall back to default values.
---
 src/video/wayland/SDL_waylandmouse.c | 137 +++++++++++++++++++++++++--
 1 file changed, 128 insertions(+), 9 deletions(-)

diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index 4545c663d7f..4b3d6f734ed 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -55,6 +55,106 @@ typedef struct {
     void               *shm_data;
 } Wayland_CursorData;
 
+#ifdef SDL_USE_LIBDBUS
+
+#include "../../core/linux/SDL_dbus.h"
+
+static DBusMessage*
+wayland_read_dbus_setting(SDL_DBusContext *dbus, const char *key)
+{
+    static const char *iface = "org.gnome.desktop.interface";
+
+    DBusMessage *reply = NULL;
+    DBusMessage *msg = dbus->message_new_method_call("org.freedesktop.portal.Desktop",  /* Node */
+                                                     "/org/freedesktop/portal/desktop", /* Path */
+                                                     "org.freedesktop.portal.Settings", /* Interface */
+                                                     "Read"); /* Method */
+
+    if (msg) {
+        if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
+            reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL);
+        }
+        dbus->message_unref(msg);
+    }
+
+    return reply;
+}
+
+static SDL_bool
+wayland_parse_dbus_reply(SDL_DBusContext *dbus, DBusMessage *reply, int type, void *value)
+{
+    DBusMessageIter iter[3];
+
+    dbus->message_iter_init(reply, &iter[0]);
+    if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) {
+        return SDL_FALSE;
+    }
+
+    dbus->message_iter_recurse(&iter[0], &iter[1]);
+    if (dbus->message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) {
+        return SDL_FALSE;
+    }
+
+    dbus->message_iter_recurse(&iter[1], &iter[2]);
+    if (dbus->message_iter_get_arg_type(&iter[2]) != type) {
+        return SDL_FALSE;
+    }
+
+    dbus->message_iter_get_basic(&iter[2], value);
+
+    return SDL_TRUE;
+}
+
+static SDL_bool
+wayland_dbus_read_cursor_size(int *size)
+{
+    static const char *cursor_size_value = "cursor-size";
+
+    DBusMessage *reply;
+    SDL_DBusContext *dbus = SDL_DBus_GetContext();
+
+    if (!dbus || !size) {
+        return SDL_FALSE;
+    }
+
+    if ((reply = wayland_read_dbus_setting(dbus, cursor_size_value))) {
+        if (wayland_parse_dbus_reply(dbus, reply, DBUS_TYPE_INT32, size)) {
+            dbus->message_unref(reply);
+            return SDL_TRUE;
+        }
+        dbus->message_unref(reply);
+    }
+
+    return SDL_FALSE;
+}
+
+static SDL_bool
+wayland_dbus_read_cursor_theme(char **theme)
+{
+    static const char *cursor_theme_value = "cursor-theme";
+
+    DBusMessage *reply;
+    SDL_DBusContext *dbus = SDL_DBus_GetContext();
+
+    if (!dbus || !theme) {
+        return SDL_FALSE;
+    }
+
+    if ((reply = wayland_read_dbus_setting(dbus, cursor_theme_value))) {
+        const char *temp;
+        if (wayland_parse_dbus_reply(dbus, reply, DBUS_TYPE_STRING, &temp)) {
+            *theme = SDL_strdup(temp);
+            dbus->message_unref(reply);
+            return SDL_TRUE;
+        }
+        dbus->message_unref(reply);
+    }
+
+    return SDL_FALSE;
+}
+
+#endif
+
 static SDL_bool
 wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorData *cdata, float *scale)
 {
@@ -68,20 +168,25 @@ wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorData *cdata, float
     SDL_WindowData *focusdata;
     int i;
 
-    /* FIXME: We need to be able to query the cursor size from the desktop at
-     * some point! For a while this was 32, but when testing on real desktops it
-     * seems like most of them default to 24. We'll need a protocol to get this
-     * for real, but for now this is a pretty safe bet.
-     * -flibit
+    /*
+     * GNOME based desktops expose the cursor size and theme via the
+     * org.freedesktop.portal.Settings interface of the xdg-desktop portal.
+     * Try XCURSOR_SIZE and XCURSOR_THEME first, so user specified sizes and
+     * themes take precedence over all, then try D-Bus if the envvar isn't
+     * set, then fall back to the defaults if none of the preceding values
+     * are available or valid.
      */
-    xcursor_size = SDL_getenv("XCURSOR_SIZE");
-    if (xcursor_size != NULL) {
+    if ((xcursor_size = SDL_getenv("XCURSOR_SIZE"))) {
         size = SDL_atoi(xcursor_size);
     }
+#if SDL_USE_LIBDBUS
+    if (size <= 0) {
+        wayland_dbus_read_cursor_size(&size);
+    }
+#endif
     if (size <= 0) {
         size = 24;
     }
-
     /* First, find the appropriate theme based on the current scale... */
     focus = SDL_GetMouse()->focus;
     if (focus == NULL) {
@@ -98,15 +203,29 @@ wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorData *cdata, float
         }
     }
     if (theme == NULL) {
+        char *xcursor_theme = NULL;
+        SDL_bool free_theme_str = SDL_FALSE;
+
         vdata->cursor_themes = SDL_realloc(vdata->cursor_themes,
                                            sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1));
         if (vdata->cursor_themes == NULL) {
             SDL_OutOfMemory();
             return SDL_FALSE;
         }
-        theme = WAYLAND_wl_cursor_theme_load(SDL_getenv("XCURSOR_THEME"), size, vdata->shm);
+        xcursor_theme = SDL_getenv("XCURSOR_THEME");
+#if SDL_USE_LIBDBUS
+        if (xcursor_theme == NULL) {
+            /* Allocates the string with SDL_strdup, which must be freed. */
+            free_theme_str = wayland_dbus_read_cursor_theme(&xcursor_theme);
+        }
+#endif
+        theme = WAYLAND_wl_cursor_theme_load(xcursor_theme, size, vdata->shm);
         vdata->cursor_themes[vdata->num_cursor_themes].size = size;
         vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme;
+
+        if (free_theme_str) {
+            SDL_free(xcursor_theme);
+        }
     }
 
     /* Next, find the cursor from the theme... */