10

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?

Andreas
  • 9,245
  • 9
  • 49
  • 97
  • Note that you're assuming something that is illogical -> "*without resorting to higher level APIs like GTK*", it **MUST** be possible or otherwise how does such higher level API do it? – Iharob Al Asimi Mar 02 '18 at 15:14
  • Well, as I said, GTK reads the `GDK_SCALE` environment variable but this is not there on my system so I don't think `gdk_window_get_scale_factor` actually works. – Andreas Mar 02 '18 at 15:15
  • You should not need GTK at all. GTK finds this value somewhere else, that's what I was trying to say. – Iharob Al Asimi Mar 02 '18 at 15:18
  • Sorry, I don't understand what you mean. According to the sources GTK reads `GDK_SCALE` and that's all. – Andreas Mar 02 '18 at 15:20
  • You are asking about *DPI* right? Probably a duplicate of [How to get screen DPI (linux,mac) programatically](https://stackoverflow.com/a/2621524/1983495). – Iharob Al Asimi Mar 02 '18 at 15:24
  • Note that you should not assume a single screen or your app will break when used with multiple displays. – stark Mar 02 '18 at 15:38
  • @Iharob: Yes, I'm after the DPI settings. The question you linked to computes the DPI from the results of `DisplayWidth` and `DisplayWidthMM` (and the `Height` counterparts) but it doesn't work. Even `xdpyinfo` shows 96dpi but it is 168dpi actually. – Andreas Mar 02 '18 at 15:44
  • How did you set such a high DPI? – Iharob Al Asimi Mar 02 '18 at 16:02
  • Ubuntu allows to set a scale factor in its display settings. Setting this factor to 1.75 will give you a 168dpi screen. – Andreas Mar 02 '18 at 19:53

2 Answers2

2

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.

Andreas
  • 9,245
  • 9
  • 49
  • 97
0

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.

Peter
  • 2,240
  • 3
  • 23
  • 37