(updated on 31.3.2022)

If this post is too long or confusing for you, I recommed checking out my .dotfiles and looking for the appropriate files there.

The problem of multiple monitors

If you are a new in the realms of Linux, just started out, or are playing around with Archlinux (or like me Archcraft, a customized arch-variation) you might have faced the issue of setting up multiple displays with your window manager. Here I want to show how I tackled that issue under bspwm.

Your issue can of course vary, but for me, the situation boils down to the following use-cases:

  • Using the notebook on its own, without any peripherals.
  • Using the notebook connected with an external monitor (including a docking station with an external monitor).
  • Using the external monitor as a mirror of the primary display or as an extension to the right.
  • Plugging the external monitor in or out at any time, also when the notebook is already running.

There are however some limitations by my Linux setup:

  • Adding or removing a monitor requires a display configuration layout change (for instance a call to xrandr).
  • When using the monitor as extension, some of the desktops on the primary monitor should appear on the new monitor …
  • … or vice versa, desktops on the external monitor should switch back to the main monitor when the external display is being unplugged.
  • Minor tweaks: Wallpaper should be adjusted and I dont want any dummy desktops (that are automatically created by bspwm when a new monitor appears).

Most solutions regarding that cases utilize some sophisticated script-logic, often involving calls to xrandr. While it was possible to craft my own scipts based on those examples, it seemed overly tedious and not very elegant, especially when adjusting the behaviour or adding unknown monitors and new configurations. But what also appears in the various threads and tutorials is an utility called autorandr, which turned out perfect for my use-cases.

This utility lives on top of xrandr, detecting current connected displays and setting up the layout. The following will be a little walkthrough of how I set it all up.

Step 1: Setting up the different layouts

While running the notebook without any additional displays, I saved the layout with autorandr.

autorandr --save single

Then I connected a second monitor on HDMI1. With xrandr I set it up to mirror my primary display (eDP1), then saved the new setup with autorandr.

xrandr --output eDP1 --primary --mode 1920x1080 --rotate normal --output HDMI1 --mode 1920x1080 --rotate normal --same-as eDP1
autorandr --save hdmi-mirror

Lastly, I changed the layout, to have the second monitor as an extension to the right of the primary display.

xrandr --output eDP1 --primary --mode 1920x1080 --rotate normal --output HDMI1 --mode 1920x1080 --rotate normal --right-of eDP1
autorandr --save hdmi-right

What use was that? Now autorandr knows our possible display configurations. We can change them manually by running the follwowing:

autorandr hdmi-right

Also when plugging in or removing a monitor, autorandr will match the best fitting saved profile and apply the configuration.

Step 2: Connecting bspwm

So we have already tackled the first issue, that is automatically detecting and applying changes to the monitor setup. What remains is adjusting the bspwm-settings to that.

Lucky for us, autorandr provides us with hooks, such as the postswitch-hook and preswitch-hook. those scripts will be automaticallye executed before and after a display change.

So I placed this script at .config/autorandr/preswitch. For it to work properly, I need to list all possible external monitors that I would plug in. What it does, is simply adding an empty dummy desktop “Desktop” to any external monitor and bumping both of my real “designated external” desktops, that might be on the external monitor, back to the main display. This is necessary as the monitor might be unplugged so we need to “save” those desktops and its windows. The dummy-desktop is necessary, as bspwm doesn’t allow a monitor without any desktops. Finally I will remove the monitors from bspwm.

declare -a MONITORS=(DP1-1 HDMI1)

for monitor in ${MONITORS[@]}; do
    bspc monitor $monitor -a Desktop
done

bspc desktop 8 --to-monitor eDP1
bspc desktop 9 --to-monitor eDP1

for monitor in ${MONITORS[@]}; do
    bspc monitor $monitor -r
done

So before a switch, all desktops are back on the main monitor and any additional monitor is removed. Now we need to take care of what happens after the switch. Therefore I have custom postswitch scripts, depending on the new layout.

If the new layout is only the main monitor, we are good. If the new layout is however the mirrored setup, I need .config/autorandr/hdmi-mirror/postswitch. Here I will need to remove the additional monitor from bspwm (again, as it automaticall adds it). What helps, is that autorandr’s env-variable tells us what monitors are active.

IFS=':' read -ra MONITORS <<< $AUTORANDR_MONITORS

for monitor in ${MONITORS[@]}; do
    if [ $monitor != eDP1 ]
    then
        bspc monitor $monitor -r
    fi
done

If the new layout is an extension of the main display, we need to shift the two “external” desktops back from the main monitor to the new one, in .config/autorandr/hdmi-right/postswitch.

bspc desktop 8 --to-monitor HDMI1
bspc desktop 9 --to-monitor HDMI1

Step 3: Housekeeping

Lastly, we need to fix some minor stuff. I therefore have a script .config/autorandr/postswitch.d/auto, which will always be executed after a switch and there I re-set the wallpaper, remove the (maybe existing) dummy-desktop and notify the display-change:

bspc desktop Desktop -r
bash $HOME/.fehbg
notify-send "Switching setup to $AUTORANDR_CURRENT_PROFILE"

Thats basically all that is needed to seemlessly switch between different layouts. However, to round things up, I did two more things:

In my .config/bspwm/bspwmrc I force a reload of a certain layout at startup, since my setup seems to be in a kind of intermediate layout, where no pre- or postswitch hook was executed and therefore some of my desktop are messy. This basically gives me a clean display configuration after startup.

...
autorandr single
...

Secondly, for conviences sake, I wanted a little rofi-applet to switch between layouts by a simple shortcut. Here it is:

function get_layouts()
{
    autorandr | grep " (detected)"
    autorandr | grep --invert-match " (detected)"
}

function main()
{
    local layouts="$(get_layouts)"

    local layout=$( (echo -e "${layouts}")  | rofi -p "Select monitor setup" -dmenu -selected-row 0)
    local matching=$( (echo "${layouts}") | grep "^${layout}$")

    autorandr --load $matching
}

main

Overall everything works out fine for me and the solution seems quite small, efficient and adaptable to me. I hope you found the post helpful! If you have questions, comments or want to give feedback, make sure to reach out to me via mail!