Protesilaos Stavrou
Philosopher. Polymath.

Annex on the multi-monitor setup

Prot's Dots For Debian - Book index

This annex is only meant to share with the public a modified version of a reply I sent via email to a fellow user. Changes are made to ensure compatibility with the rest of the book.

The purpose of annexes is to provide some extra information, with the proviso that the reader is also willing to put in the effort where necessary. They also are published for the longer-term maintainability of “Prot’s Dots For Debian” (PDFD): I can just link to them in case anyone else has a similar question/issue.


This chapter concerns the settings I have in place for allowing my custom desktop session to use a dual monitor setup. My main machine is a laptop (Thinkpad X220) to which I attach an external monitor via the VGA port.

The multi-monitor setup consists of three parts, which are documented in sequence. It is assumed that you have already followed earlier steps in this book to get my code/configs and put them in their right place.

Before we start, here is a short primer on xrandr.

0. Basics of XrandR

The xrandr utility helps us identify the available connected monitors, or “outputs”, with the command xrandr -q.

What I get from that:

xrandr -q

Screen 0: minimum 8 x 8, current 3286 x 1080, maximum 32767 x 32767
LVDS1 connected primary 1366x768+0+0 (normal left inverted right x axis y axis) 280mm x 160mm
   1366x768      60.00*+
   1360x768      59.96
   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
   680x384       60.00
   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)
DP3 disconnected (normal left inverted right x axis y axis)
HDMI1 disconnected (normal left inverted right x axis y axis)
HDMI2 disconnected (normal left inverted right x axis y axis)
HDMI3 disconnected (normal left inverted right x axis y axis)
VGA1 connected 1920x1080+1366+0 (normal left inverted right x axis y axis) 480mm x 270mm
   1920x1080     60.00*+
   1680x1050     59.95
   1600x900      60.00
   1280x1024     75.02    60.02
   1440x900      59.89
   1280x800      59.81
   1152x864      75.00
   1280x720      60.00
   1024x768      75.03    70.07    60.00
   832x624       74.55
   800x600       72.19    75.00    60.32    56.25
   640x480       75.00    72.81    66.67    59.94
   720x400       70.08
VIRTUAL1 disconnected (normal left inverted right x axis y axis)

We can then limit the output to just those we are interested in:

xrandr -q | grep -w connected

LVDS1 connected primary 1366x768+0+0 (normal left inverted right x axis y axis) 280mm x 160mm
VGA1 connected 1920x1080+1366+0 (normal left inverted right x axis y axis) 480mm x 270mm

With this in mind, we proceed to my scripts.

1. Monitor layout for the X display server

We first need to tell the X server how we want our monitors to be drawn. This concerns their geometry and relative positions. The script dedicated to this task is own_script_laptop_dual_monitor (refer to the chapter about my local ~/bin). Let us then have a look into its main elements.

This function accepts an argument that determines whether the laptop screen will be designated as the “primary” one or not. It also establishes the coordinates of the laptop’s built-in display. The --mode is the monitor’s dimensions (resolution), while --pos means to place the monitor at the upper left point of the notional space that X assigns to the displays. The X×Y coordinates are Width×Height:

laptop_coordinates() {
    if [ "$#" == 1 ]; then
        xrandr --output LVDS1 --primary --mode 1366x768 --pos 0x0
    else
        xrandr --output LVDS1 --mode 1366x768 --pos 0x0
    fi
}

And this one below sets the coordinates and dimensions for the external monitor. The laptop is specified as the primary display. Note the --pos of the external display, which has an X axis that continues right after the end of the laptop monitor. The Y axis remains at 0, which means that the two monitors are aligned at the top horizontally but not at the bottom, because the laptop’s is shorter.

multihead_coordinates() {
    echo "Configuring LVDS1 + VGA1 XrandR coordinates"
    # pass a single argument to activate the --primary option
    laptop_coordinates 'primary'

    # configure the external display on the VGA port
    xrandr --output VGA1 --mode 1920x1080 --pos 1366x0
}

Then I have a very simple command to see if the VGA monitor is connected. If it is not, then this variable is empty:

my_laptop_external_monitor=$(xrandr --query | grep 'VGA1 connected')

Finally I run a check against this variable. I leave out some extra commands here, for the sake of our topic. The elif condition is there for cases where I need to use the script outside BSPWM (e.g. to set up LightDM—contact me if this is something you are interested in):

if [ -n "$my_laptop_external_monitor" ] && [ "$DESKTOP_SESSION" == 'bspwm' ]; then
    multihead_coordinates
elif [ -n "$my_laptop_external_monitor" ]; then
    multihead_coordinates
else
    laptop_coordinates
fi

2. BSPWM multihead

The above will just prepare the dual monitors for the X display server. We still need to configure BSPWM accordingly, otherwise we will not get the desired results. I do this in a separate script, for the sake of portability. This one is own_script_bspwm_per_host_configs (it has a few more commands that I omit for the sake of brevity and to remain on topic).

First we check the number of monitors. Basically to confirm that an external display is on. This time we use BSPWM client program, just to be sure that it is running and finding the information it needs:

monitor_count="$(bspc query -M | wc -l)"

I then have a very simple command for single monitor setups. This just defines the workspaces/desktops on the first available monitor (i.e. the only one). Note that I only specify a single desktop because I use my custom script for dynamic desktops, which is documented in the chapter about the advanced features of my BSPWM.

bspwm_generic_workspaces() {
    bspc monitor -d 1
}

Then I have settings for the workspaces/desktops and a few other things. I just include the desktops’ part to keep this article on point.

bspwm_laptop_dual_monitor() {
    bspc monitor LVDS1 -d 1
    bspc monitor VGA1 -d 8
}

So I place just a single desktop on each monitor. The actual numbers have no real significance here. You can switch desktops with the motions I document in the basics of my BSPWM (use super+NUM or to switch monitors while retaining their currently-focused desktop go with super+,)

I implement one desktop per monitor because I use my own “dynamic desktops” script. Otherwise you can have something like:

bspwm_laptop_dual_monitor() {
	bspc monitor LVDS1 -d 1 2 3 4 5
	bspc monitor VGA1 -d 6 7 8 9 0
}

In my experience the numbers need to be in sequence. You cannot have them alternate (LVDS has 1 3 5 /// VGA has 2 4 6).

At any rate, here is the excerpt of the final piece of the script. I put it first and explain it below:

# run the script that adds the appropriate `xrandr` settings
if [ "$(command -v own_script_laptop_dual_monitor 2> /dev/null)" ]; then
    own_script_laptop_dual_monitor
fi

# Is an external monitor connected to my laptop?
if [ "$monitor_count" == 2 ]; then
    echo "Monitor count is equal to 2"
    echo "Defining per-monitor workspaces"
    bspwm_laptop_dual_monitor
else
    bspwm_generic_workspaces
fi

As you can see, it first runs the script I mentioned in the previous section. This is because I only autostart this one script at BSPWM launch (more on that below).

Then it simply configures the workspaces/desktops depending on whether there is an external monitor or not.

That is all to it. The final piece is to run this script when BSPWM starts. So this is the very last line in my bspwmrc:

_check own_script_bspwm_per_host_configs && own_script_bspwm_per_host_configs

The _check function expands into command -v "$1" > /dev/null where the argument $1 is the name of the script/command to check against (see the file to understand how I use this throughout it). The command basically checks for the existence of the script before running it.

3. Panel settings (lemonbar)

Thus far everything should work except the system panel. As explained in much greater detail in the chapter about the top panel, my script that configures lemonbar is called melonpanel. This is a long script. The part about monitors concerns the actual placement of the panel on which all the “modules” are printed (information about desktops, the date, sound volume, etc.). Here are the extracted parts:

laptop_external_monitor="$(xrandr --query | grep 'VGA1 connected')"

if [ -n "$laptop_external_monitor" ]; then
   printf "%s%s\n" "%{Sf}$(_panel_layout)" "%{Sl}$(_panel_layout)"
else
    printf "%s\n" "%{Sf}$(_panel_layout)"
fi

Focus on the %{Sf} and %{Sl} constructs. This is lemonbar syntax for the first and second monitor respectively. Basically to place the same panel on both of them.

Concluding notes

I understand this is a rather complex topic. Hopefully you now have a basis to work with. The way I would troubleshoot any problems is to make sure that the X server draws the screens correctly and that BSPWM places the right desktops on each monitor.

For example, say desktop 8 is on the external display, while desktop 1 is on the laptop. Switch to 8 and spawn a terminal emulator. Then switch to 1 and do the same. If you get terminals on both monitors, then your only problem is with the panel.