SDL: x11: Add GTK signal handler for gtk-xft-dpi and reader in GetGlobalContentScale

From bf7b4d4a9edfbbc223bb74eb32e62162fb30c920 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 31 Jul 2025 08:49:36 -0700
Subject: [PATCH] x11: Add GTK signal handler for gtk-xft-dpi and reader in
 GetGlobalContentScale

- This is to support dynamic updates of content scale when running in XWayland. The GTK signal is
  preferred over the XSettings watcher and Xrm database if supported as it will trigger and update
  for both native X11 and XWayland on changes to Xft.dpi.
---
 src/video/x11/SDL_x11modes.c    | 16 +++++++++
 src/video/x11/SDL_x11settings.c | 57 +++++++++++++++++++++++++++++++--
 src/video/x11/SDL_x11settings.h |  4 +++
 3 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/src/video/x11/SDL_x11modes.c b/src/video/x11/SDL_x11modes.c
index 37198a72eb190..2417d33f17b0a 100644
--- a/src/video/x11/SDL_x11modes.c
+++ b/src/video/x11/SDL_x11modes.c
@@ -27,6 +27,8 @@
 #include "edid.h"
 #include "../../events/SDL_displayevents_c.h"
 
+#include "../../core/unix/SDL_gtk.h"
+
 // #define X11MODES_DEBUG
 
 /* Timeout and revert mode switches if the timespan has elapsed without the window becoming fullscreen.
@@ -61,6 +63,20 @@ static float GetGlobalContentScale(SDL_VideoDevice *_this)
             }
         }
 
+        // If that failed, try "Xft.dpi" from GTK if available. On XWayland this
+        // will retrieve the current scale factor which is not updated dynamically
+        // in the Xrm database.
+        SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
+        if (gtk) {
+            GtkSettings *gtksettings = gtk->gtk.settings_get_default();
+            if (gtksettings) {
+                int dpi = 0;
+                gtk->g.object_get(gtksettings, "gtk-xft-dpi", &dpi, NULL);
+                scale_factor = dpi / 1024.0 / 96.0;
+            }
+            SDL_Gtk_ExitContext(gtk);
+        }
+
         // If that failed, try "Xft.dpi" from the XResourcesDatabase...
         if (scale_factor <= 0.0)
         {
diff --git a/src/video/x11/SDL_x11settings.c b/src/video/x11/SDL_x11settings.c
index 7a7ae01318eb6..a1a9846d286c7 100644
--- a/src/video/x11/SDL_x11settings.c
+++ b/src/video/x11/SDL_x11settings.c
@@ -26,6 +26,8 @@
 #include "SDL_x11video.h"
 #include "SDL_x11settings.h"
 
+#include "core/unix/SDL_gtk.h"
+
 #define SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR "Gdk/WindowScalingFactor"
 #define SDL_XSETTINGS_XFT_DPI "Xft/DPI"
 
@@ -65,13 +67,53 @@ static void X11_XsettingsNotify(const char *name, XSettingsAction action, XSetti
     }
 }
 
+static void OnGtkXftDpi(GtkSettings *settings, GParamSpec *pspec, gpointer ptr)
+{
+    SDL_VideoDevice *_this = (SDL_VideoDevice *)ptr;
+
+    SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
+    if (gtk) {
+        int dpi = 0;
+        gtk->g.object_get(settings, "gtk-xft-dpi", &dpi, NULL);
+        
+        if (dpi != 0) {
+            float scale_factor = dpi / 1024.f / 96.f;
+
+            for (int i = 0; i < _this->num_displays; ++i) {
+                SDL_VideoDisplay *display = _this->displays[i];
+                SDL_SetDisplayContentScale(display, scale_factor);
+            }
+        }
+        SDL_Gtk_ExitContext(gtk);
+    }
+}
+
 void X11_InitXsettings(SDL_VideoDevice *_this)
 {
     SDL_VideoData *data = _this->internal;
     SDLX11_SettingsData *xsettings_data = &data->xsettings_data;
 
-    xsettings_data->xsettings = xsettings_client_new(data->display,
-        DefaultScreen(data->display), X11_XsettingsNotify, NULL, _this);
+    GtkSettings *gtksettings = NULL;
+    guint xft_dpi_signal_handler_id = 0;
+
+    SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
+    if (gtk) {
+        // Prefer to listen for DPI changes from gtk. In XWayland this is necessary as XSettings
+        // are not updated dynamically.
+        gtksettings = gtk->gtk.settings_get_default();
+        if (gtksettings) {
+            xft_dpi_signal_handler_id = gtk->g.signal_connect(gtksettings, "notify::gtk-xft-dpi", &OnGtkXftDpi, _this);
+        }
+        SDL_Gtk_ExitContext(gtk);
+    }
+
+    if (gtksettings && xft_dpi_signal_handler_id) {
+        xsettings_data->gtksettings = gtksettings;
+        xsettings_data->xft_dpi_signal_handler_id = xft_dpi_signal_handler_id;
+    } else {
+        xsettings_data->xsettings = xsettings_client_new(data->display,
+            DefaultScreen(data->display), X11_XsettingsNotify, NULL, _this);
+    }
 
 }
 
@@ -82,8 +124,17 @@ void X11_QuitXsettings(SDL_VideoDevice *_this)
 
     if (xsettings_data->xsettings) {
         xsettings_client_destroy(xsettings_data->xsettings);
-        xsettings_data->xsettings = NULL;
     }
+
+    SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
+    if (gtk) {
+        if (xsettings_data->gtksettings && xsettings_data->xft_dpi_signal_handler_id) {
+            gtk->g.signal_handler_disconnect(xsettings_data->gtksettings, xsettings_data->xft_dpi_signal_handler_id);
+        }
+        SDL_Gtk_ExitContext(gtk);
+    }
+
+    SDL_zero(xsettings_data);
 }
 
 void X11_HandleXsettings(SDL_VideoDevice *_this, const XEvent *xevent)
diff --git a/src/video/x11/SDL_x11settings.h b/src/video/x11/SDL_x11settings.h
index 5b368846ed8e0..39926b66c6b82 100644
--- a/src/video/x11/SDL_x11settings.h
+++ b/src/video/x11/SDL_x11settings.h
@@ -27,8 +27,12 @@
 #include <X11/Xlib.h>
 #include "xsettings-client.h"
 
+#include "core/unix/SDL_gtk.h"
+
 typedef struct X11_SettingsData {
     XSettingsClient *xsettings;
+    GtkSettings *gtksettings;
+    guint xft_dpi_signal_handler_id;
 } SDLX11_SettingsData;
 
 extern void X11_InitXsettings(SDL_VideoDevice *_this);