+++
title = "Fractional display scaling on X11"
date = 2021-12-27

[taxonomies]
categories = ["Linux"]

[extra]
author = "Emil Miler"
+++

X11 supports fractional scaling perfectly well, but most GUI toolkits don't implement it properly. Scaling works fine in a pure QT environment, but when using other GUI toolkits (such as GTK+), things don't work as expected due to them being poorly written. This article describes several methods of universal mixed display scaling with broken GUI toolkits and a simple window manager (dwm).

<!-- more -->

A huge chunk of this article is inspired by William Storey and Rico Sta. Cruz (see [Resources](#resources)).

## How scaling works

There is a more detailed explaination written by Giuseppe Bilotta in [his article](http://wok.oblomov.eu/tecnologia/mixed-dpi-x11/).

In short:  Scaling support has to be implemented properly by the application, or subsequently the GUI toolkit. Most toolkits and applications are broken and fractional scaling has to be hacked together to be universal.

There are several ways to set UI scaling and each works for different things. They need to be combined to affect *most* of the system and they can either affect font size or UI elements, such as margins, paddings, etc. The intended X11 way is setting `Xft.dpi` in `.Xresources`. Some toolkits do not support this and instead use custom environment variables.

### Calculating DPI

The DPI can either be calculated or obtained for a display datasheet. Scaled DPI can be calculated by subtracting n% to get the desired value. For instance, my display has 277 DPI and my desired scaling is 150%.

```
277 - 50% = 138.5
```

Even though any scaling value can be set, it is best to select one of the following values closest to the calculation result. The reason for this is that arbitrary fractions may result in broken bitmap scaling.

|            |              |      |      |      |      |
|------------|--------------|------|------|------|------|
| **Scale:** | 100%         | 125% | 150% | 175% | 200% |
| **DPI:**   | 96 (default) | 120  | 144  | 168  | 192  |

In my particular case, the most optimal DPI value would be 144.

## Simple UI scaling

|                             |                      |
|-----------------------------|----------------------|
| Multiple mixed-DPI monitors | No                   |
| Requires xserver restart    | Yes                  |
| Performance                 | Fast                 |
| Image Quality               | Good                 |

This allows us to scale the UI by any factor, but it will not work with mixed-DPI monitors. Switching to different scaling also requires an xserver restart, or restarting all individual applications.

More information about different toolkits can be found at [Archlinux Wiki](https://wiki.archlinux.org/title/HiDPI#GUI_toolkits).

### Xft.dpi

Global xrandr configuration for UI scaling is set in `.Xresources` by setting `Xft.dpi` to the desired value, eg.

```
Xft.dpi: 144
```

### QT

QT scaling on legacy applications can be enabled by exporting `QT_AUTO_SCREEN_SCALE_FACTOR=1`.

In case the core protocol does not report our primary monitor DPI correctly, we have to set the DPI manually, eg. in our `.profile` or `.bash_profile`.

```
export QT_AUTO_SCREEN_SCALE_FACTOR=0
export QT_SCALE_FACTOR=1.5
export QT_FONT_DPI=96
```

`QT_AUTO_SCREEN_SCALE_FACTOR=0` disables the automatic scaling, as some applications with forces dcaling would get scaled twice. `QT_SCALE_FACTOR=1.5` sets the desired scale factor (150%). Since we have set xrdb DPI to support other toolkits, this alo will result in huge fonts, so they need to be reset back to default by `QT_FONT_DPI=96`. This should result in good-looking QT scaling.

### GDK3

Since we are using a simple window manager, the `Xft.dpi` setting should scale everything properly.

In case your icons don't scale well, you can force GDK scaling with `GDK_SCALE` and `GDK_DPI_SCALE`.

```
export GDK_SCALE=2
export GDK_DPI_SCALE=0.5
```

This would scale everything up by the factor of 2 (it only accepts an integer) and scale fonts back down by 50%. Despite fonts being scaled properly, the resulting margins/paddings and icons are larger, since they get scaled by 200%, not 150%.


## Mixed monitors with different DPI

|                             |                          |
|-----------------------------|--------------------------|
| Multiple mixed-DPI monitors | Yes                      |
| Requires xserver restart    | No                       |
| Performance                 | Poor                     |
| Image Quality               | Poor on low-DPI displays |

In case you want multiple monitors with mixed DPI, this method has to be used. Despite working as intended, the downscaling results in a poorer image quality on low-DPI displays. Large scaling factors may also result in poor performance. In my case, browser lag while scolling was very noticeable.

First step is to scale the UI by 200%, similar to the previous step.

Add the following to `.Xresources`:

```
Xft.dpi: 192
```

and to `.bash_profile`:

```
export GDK_SCALE=2
export GDK_DPI_SCALE=0.5
export QT_AUTO_SCREEN_SCALE_FACTOR=0
export QT_SCALE_FACTOR=2
export QT_FONT_DPI=96
```

Next step is to configure downscaling in xrandr by a specific factor for each monitor.

```
...todo...
```

Changing the scaling factor can be done simply by running xrandr again with chosen scaling factor and everything works as it should.

Citing Giuseppe Bilotta:

>If you think this idea is a bit stupid, shed a tear for the future of the display servers: this same mechanism is essentially how Wayland compositors -- Wayland being the purported future replacement for X -- cope with mixed-DPI setups.

## Resources

- <https://ricostacruz.com/til/fractional-scaling-on-xorg-linux>
- <https://wiki.archlinux.org/title/HiDPI>
- <https://blog.summercat.com/configuring-mixed-dpi-monitors-with-xrandr.html>
- <https://github.com/burntcustard/x11-fractional-display-scaling>
- <http://wok.oblomov.eu/tecnologia/mixed-dpi-x11/>
