Andreas
Andreas

Reputation: 10077

How to get system scale factor in X11

I want to make my application, which is purely in X11, high DPI aware. In order to do this, I need a way to find out the system scale factor that is configured in the display settings. Is there any way to get this system scale factor from an X11 app without resorting to higher level APIs like GTK?

FWIW, I've checked the GTK sources to see how gdk_window_get_scale_factor() does it and it seems to read an environment variable named GDK_SCALE. This environment variable, however, isn't there on my system at all, even though I've set scaling to 1.75 on my 4K monitor.

So how can I programmatically retrieve the system scaling factor?

Upvotes: 10

Views: 3492

Answers (2)

Peter
Peter

Reputation: 2320

I'm not sure if a answer regarding Gtk is helpful, the question is tagged with gtk and the author seem to use it. With GNOME I can apply different scaling factors per monitor on Wayland, X11 uses on scaling factor for all monitors. The following code reports the scaling factors:

#include <gtk/gtk.h>
#include <stdio.h>

static void activate(GtkApplication *app, gpointer user_data) {
    GtkWidget *window;
    window = gtk_application_window_new(app); // creates GtkApplicationWindow, returns a GtkWidget*
    gtk_window_set_title(GTK_WINDOW (window), "Window"); // macro, checks types and does function style cast
    gtk_window_set_default_size(GTK_WINDOW (window), 200, 200);
    gtk_widget_show_all(window);
    
    GdkWindow* gdk_window = gtk_widget_get_window(window);
    GdkDisplay* gdk_display = gdk_display_get_default();

    // prints sadly always '1' on my system
    GdkMonitor* gdk_monitor = gdk_display_get_monitor_at_window(gdk_display, gdk_window);
    int scale = gdk_monitor_get_scale_factor(gdk_monitor);
    printf("scale is %d\n", scale);

    // seems to work fine
    int n = gdk_display_get_n_monitors(gdk_display);
    for (int i = 0; i < n; ++i) {
        GdkMonitor* monitor = gdk_display_get_monitor(gdk_display, i);
        int scale = gdk_monitor_get_scale_factor(monitor);
        printf("monitor %d, scale %d\n", i, scale);
    }
}

int main(int argc, char **argv) {
    GtkApplication *app;
    int status;

    app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run(G_APPLICATION (app), argc, argv);
    g_object_unref(app); // free memory of GtkApplication

    return status;
}

Compile

gcc `pkg-config --cflags gtk+-3.0` -o gtk_scale gtk_scale.c `pkg-config --libs gtk+-3.0`

When I'm running the application on Wayland, with the left monitor (primary) set to scale factor 1 and the right monitor is set to scale factor 2:

./gtk_scale 
scale is 1         # wrong, application windows resides on right screen with scale factor 2
monitor 0, scale 2 # correct
monitor 1, scale 1 # correct

The more convenient function gdk_display_get_monitor_at_window() seem to always return the scale factor of the primary monitor instead of the actual scale factor used by the application window.

Gets the monitor in which the largest area of window resides, or a monitor close to window if it is outside of all monitors.

Therefore I recommend instead currently gdk_monitor_get_scale_factor() which provides the correct results. If multiple displays are conneted - like in my example - you will need to figure out now on which one your application window is shown.

Gets the internal scale factor that maps from monitor coordinates to the actual device pixels. On traditional systems this is 1, but on very high density outputs this can be a higher value (often 2).

This can be used if you want to create pixel based data for a particular monitor, but most of the time you’re drawing to a window where it is better to use gdk_window_get_scale_factor() instead.

The other function mentioned there is the one Andreas has investigated initially, for me it returns always 2.

Upvotes: 0

Andreas
Andreas

Reputation: 10077

To answer my own question, I've now tried three approaches:

  1. XRandR
  2. X11's DisplayWidth/Height and DisplayWidthMM/HeightMM
  3. Looking at xdpyinfo output

Neither returns the correct DPI. Instead, the Xft.dpi Xresource seems to be the key to this problem. Xft.dpi always seems to carry the correct DPI so we can just read it to get the system scale factor.

Here's some source taken from here:

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>

double _glfwPlatformGetMonitorDPI(_GLFWmonitor* monitor)
{
    char *resourceString = XResourceManagerString(_glfw.x11.display);
    XrmDatabase db;
    XrmValue value;
    char *type = NULL;
    double dpi = 0.0;

    XrmInitialize(); /* Need to initialize the DB before calling Xrm* functions */

    db = XrmGetStringDatabase(resourceString);

    if (resourceString) {
        printf("Entire DB:\n%s\n", resourceString);
        if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == True) {
            if (value.addr) {
                dpi = atof(value.addr);
            }
        }
    }

    printf("DPI: %f\n", dpi);
    return dpi;
}

This does the trick for me.

Upvotes: 2

Related Questions