The Association of Mad Scientists

Display Scaling on X11 regardless of your Desktop

I use i3 window manager and a group of custom applications as my desktop environment. It's not pretty, but it's fast, it never crashes, and best of all, it's mine. Every application is handpicked by yours-truly to do exactly the job I need it to, and usually very little more. I've experimented with most major Linux desktop environments, and still do, but on my main workstation, I have a simple, specific setup I always use.

Unfortunately, my main desktop has an issue – I have monitors of different sizes which have the same resolution. I use a three-monitor setup on a cheap nvidia GPU (boy do I regret that purchase), with a 22-inch 1080p monitor on each side of a 40-inch 1080p TV. Because of this setup, I have the opposite problem as most people who are experimenting with scaling on the x11 desktop: I have one extra-low-DPI screen, as opposed to an extra-high one. I don't want to display the side-monitors as zoomed-in, I want to zoom out this huge middle monitor a bit.

As a starting point, I had the following xrandr one-liner generated by arandr:

#!/bin/sh
xrandr --output VGA-0 --primary --mode 1920x1080 --pos 3840x0 --rotate normal --output DVI-D-0 --mode 1920x1080 --pos 0x0 --rotate normal --output HDMI-0 --mode 1920x1080 --pos 1920x0 --rotate normal

Lets parameterize that to make it a bit more readable...

#!/bin/sh

monitor_width='1920'
monitor_height='1080'
resolution='--mode ${monitor_width}x${monitor_height}'
# default values to apply to all monitors
defaults="--rotate normal $resolution"

left_monitor="--output DVI-D-0 --pos 0x0 $defaults"
middle_monitor="--output HDMI-0 --pos 1920x0 $defaults"
right_monitor="--output VGA-0 --primary --pos 3840x0 $defaults"

xrandr $left_monitor $middle_monitor $right_monitor

Hopefully you can understand this one a little easier than the automatically generated one: the defaults are a resolution of 1080p, and not to have any rotation, and each monitor now has a named string detailing its settings.

Now, to the meat of the topic: scaling

If you check out man xrandr, you'll find a genuine wealth of options, the vast majority of which are not available in the arandr GUI interface. It even includes an equation for figuring out the exact amount of keyframing needed for a given projector angle offset. But we just need a small section:

  --scale xxy
    Changes  the dimensions of the output picture. Values superior
    to 1 will lead to a compressed screen (screen dimension bigger
    than the dimension of the output mode), and values below 1
    leads to a zoom in on the output. This option is actually a
    shortcut version of the --transform option.

  --scale-from wxh
    Specifies the size in pixels of the area of the framebuffer to
    be displayed on this output. This option is actually a
    shortcut version of the --transform option.

This gives you two options for intuitively scaling your screen resolution up or down: a ratio of 1, or an effective resolution. Very convenient. I experimented a bit with different options and came up with this script, which currently sets my center monitor to 1.1x1.1 (which scales it to 90.9%), but also allows me to quickly change my scaling options based on the environment variables $middle_scale_factor and $right_scale_factor:

#!/bin/sh

# shell options:
# - display each command as it's being run
# - exit on errors
# - exit on undefined variables
set -eux

monitor_width='1920'
monitor_height='1080'
resolution='--mode ${monitor_width}x${monitor_width}'
# default values to apply to all monitors
defaults="--rotate normal $resolution"

# this syntax allows setting a default for an environment
# variable. It can be overridden by specifying the
# environment variable either globally or at the
# launch of the script.
middle_scale_factor=${middle_scale_factor:-'1.15'}
side_scale_factor=${side_scale_factor:-1}
middle_scale="--scale ${middle_scale_factor}x${middle_scale_factor}"
side_scale="--scale ${side_scale_factor}x${side_scale_factor}"

# You could also do this calculation using `bc(1)',
# but that gave me a float at first, so I just
# switched to python. Python will be there anyway,
# why not? Anyway, the math here just sets the
# horizontal offsets for each monitor, based
# on the scaling factors and the $monitor_width variable
middle_screen_offset=`python -c "print(int($monitor_width * $side_scale_factor))"`
right_screen_offset=`python -c "print(int(${middle_scale_factor} * $middle_screen_offset) + $monitor_width * $side_scale_factor)"`

# apply the settings to each screen
left_screen="--output DVI-D-0 --pos 0x0 $defaults $side_scale"
middle_screen="--output HDMI-0 --pos ${middle_screen_offset}x0 $defaults $middle_scale"
right_screen="--output VGA-0 --primary --pos ${right_screen_offset}x0 $defaults $side_scale"

# apply all the settings
xrandr $left_screen $middle_screen $right_screen

With this script, one can set scaling on a three-monitor setup like mine by only setting environment variables. For example, in my i3 config file, I have a line exec --no-startup-id '/home/scott/.screenlayout/default.sh', so all I have to do is remove the symlink from that file to this script (which is at with-scaling.sh in the same folder), and replace it with a script like the following:

#!/bin/sh

middle_scale_factor=1 side_scale_factor=0.85 ./with-scaling.sh

or, lets say I upgraded all my monitors to 1440p (a man can dream, ok?). A quick .screenlayout/default.sh would be:

#!/bin/sh

monitor_width=2560 monitor_height=1440 ./with-scaling.sh

So, that was a fun little script. It just scratches the surface of the capabilities for xrandr, though, so if you feel like your display setup could use a little tweaking, give xrandr(1) a read.