Manjaro i3: HiDPI laptop display with a Low-DPI external display
[ linux , i3 ]

I started using Manjaro Linux (the community-maintained i3-based version) last week after decades of using Windows and macOS. One thing I found especially tricky to do was to set up my desktop environment to support two configurations:

  1. Laptop: when I’m using my laptop as a portable computer: the only display I have is the built-in HiDPI (QHD) one.
  2. Desktop: when I’m sitting at my desk with my main, Low-DPI QHD monitor in front of me and the laptop to its left.

The Laptop configuration

After I installed Manjaro i3, I needed to configure the built-in display to use 200% scaling because at 100%, everything was unreadably small. To achieve this, I needed to change the Xft.dpi setting in my ~/.Xresources:

!! Before:
Xft.dpi:        96

!! After:
Xft.dpi:        192

DPI means Dots Per Inch. It is recommended to specify an integer multiple of 96, the default DPI value; in this case, 2*96 = 192. This way one logical pixel always corresponds to a square block of physical pixels so the image will be sharp.

You can test these changes by issuing the following command:

xrdb -merge ~/.Xresources

You must also make sure this comand is invoked when X starts - this is usually achieved by adding it to ~/.xinitrc or ~/.xprofile (the latter if you use a “desktop manager”, which is terrible Linux jargon for a GUI login screen). In my case, I didn’t need to add anything to these files because the default scripts already merged .Xresources.

The Desktop configuration

If you have multiple displays, you can configure their position, resolution, scaing, etc. using the xrandr command (“randr” means Resize and Rotate, the name of an X extension).

First we need to find out the names of the displays currently connected by running xrandr without parameters:

$ xrandr
Screen 0: minimum 8 x 8, current 7680 x 2880, maximum 32767 x 32767
eDP1 connected 2560x1440+0+0 (normal left inverted right x axis y axis) 310mm x 170mm
   2560x1440     60.00*+  48.00
   1920x1440     60.00
   1856x1392     60.01
   1792x1344     60.01
   2048x1152     60.00    59.90    59.91
   1920x1200     59.88    59.95
   1920x1080     59.96    60.00    59.93
   1600x1200     60.00
   1680x1050     59.95    59.88
   1400x1050     59.98
   1600x900      60.00    59.95    59.82
   1280x1024     60.02
   1400x900      59.96    59.88
   1280x960      60.00
   1368x768      60.00    59.88    59.85
   1280x800      59.81    59.91
   1280x720      59.86    60.00    59.74
   1024x768      60.00
   1024x576      60.00    59.90    59.82
   960x540       60.00    59.63    59.82
   800x600       60.32    56.25
   864x486       60.00    59.92    59.57
   640x480       59.94
   720x405       59.51    60.00    58.99
   640x360       59.84    59.32    60.00
DP1 disconnected (normal left inverted right x axis y axis)
DP2 disconnected (normal left inverted right x axis y axis)
HDMI1 connected primary 5120x2880+2560+0 (normal left inverted right x axis y axis) 600mm x 340mm
   2560x1440     59.95*+  74.97
   1920x1080     60.00    50.00    59.94
   1920x1080i    60.00    50.00    59.94
   1280x1440     59.91
   1680x1050     59.88
   1280x1024     75.02    60.02
   1440x900      59.90
   1280x960      60.00
   1280x720      60.00    50.00    59.94
   1024x768      75.03    70.07    60.00
   832x624       74.55
   800x600       72.19    75.00    60.32    56.25
   720x576       50.00
   720x480       60.00    59.94
   640x480       75.00    72.81    66.67    60.00    59.94
   720x400       70.08
HDMI2 disconnected (normal left inverted right x axis y axis)
VIRTUAL1 disconnected (normal left inverted right x axis y axis)

The list contains all possible outputs, whether they are connected or disconnected and all resolutions the connected displays support.

For my configuration, I first specified that my external display is to the right of my internal one and just to make sure everything is correct, I explicitly set the resolution and rotation:

xrandr \
    --output eDP1 \
        --mode 2560x1440 \
        --pos 0x0 \
        --rotate normal \
    --output HDMI1 \
        --primary \
        --mode 2560x1440 \
        --pos 2560x0 \
        --rotate normal \

After running this command, my desktop was extended to the external display but it was shown at 200% scaling (as configured earlier with the .Xresources DPI setting). What I needed to do was to tell xrandr to resize the picture of this output to be twice as large as the default. This results in X squeezing the oversized picture inside the 2560x1440 resolution, which effectively result in it being scaled at 100%.

xrandr \
    --output eDP1 \
        --mode 2560x1440 \
        --pos 0x0 \
        --rotate normal \
    --output HDMI1 \
        --primary \
        --mode 2560x1440 \
        --pos 2560x0 \
        --rotate normal \
        --scale 2x2

All I needed was to add --scale to the HDMI output and this immediately worked and solved all of my problems.

Defining i3 shortcuts to switch between configurations

The command above works perfectly but it is quite tedious to run every time (even as a shell script) I sit down at my desk. So I configured the Mod+Alt+s shortcut in i3 to offer a menu to switch screen layouts. This is what I added to my ~/.i3/config:

# Switch between screen layouts
bindsym $mod+Mod1+s mode "$mode_screenlayout"
set $mode_screenlayout Screen layout: (l)aptop, (d)esktop
mode "$mode_screenlayout" {
    bindsym l exec --no-startup-id /home/myusername/scripts/xrandr-laptop.sh, mode "default"
    bindsym d exec --no-startup-id /home/myusername/scripts/xrandr-desktop.sh, mode "default"

  # exit screen layout mode: "Enter" or "Escape"
  bindsym Return mode "default"
  bindsym Escape mode "default"
}

This way, I can press Mod+Alt+s, the options are shown on the i3 status bar, and I can select one by pressing either “l” or “d”. Of course you need to change “myusername” to your user name and create the scripts/xrandr-*.sh shell scripts:

xrandr-laptop.sh:

#!/usr/bin/env bash

xrandr \
    --output eDP1 \
        --mode 2560x1440 \
        --pos 0x0 \
        --rotate normal \
    --output HDMI1 \
        --off

xrandr-desktop.sh:

#!/usr/bin/env bash

xrandr \
    --output eDP1 \
        --mode 2560x1440 \
        --pos 0x0 \
        --rotate normal \
    --output HDMI1 \
        --primary \
        --mode 2560x1440 \
        --pos 2560x0 \
        --rotate normal \
        --scale 2x2

I hope you’ve found this guide useful. If so, please consider sharing it with people who might like it too.