The .Files

BSPWM Multimonitor Support

I’ve seen a lot of people making posts on r/bspwm asking about this topic and unless you’re doing something particularly strange or are having trouble with your modern (and most likely nvidia) video card, you shouldn’t have much trouble getting this to work.

There’s also the matter of the odd workspace that gets created if one hotplugs a monitor in and out.

Leaving the aforementioned video card issues aside, all of these difficulties stem from people not knowing how bspwm works, people who expect it to work EXACTLY like i3 (or some other wm), people that don’t use the subreddit’s search function, etc.

The bspwm docs detail how the creation of workspaces happens, but the long and short of it is that you absolutely must assign them to a monitor. And while I’ve seen a couple scripts for creating workspaces on demand, I’ve never used them and can’t vouch for them.

Now to the solution. First, we create the workspaces and then we split them between our physical screens.

The first version

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh

M=$(bspc query -M --names)
NUM=$(echo "$M" | awk 'END{print NR}')

ettin() {
    sec=$(echo "$M" | awk NR==2)
    xrandr --output LVDS1 --primary --auto --scale 1.0x1.0 --output "$sec" --right-of LVDS1 --auto --scale 1.0x1.0
    bspc monitor LVDS1 -d 1 2 3 4 5
    bspc monitor "$sec" -d 6 7 8 9 10
}

cyclops() {
    xrandr --output LVDS1 --primary --auto --scale 1.0x1.0 "$(echo "$M" | awk '! /LVDS1/ {print "--output", $1, "--off"}' | paste -sd ' ')"
    bspc monitor -d 1 2 3 4 5 6 7 8 9 10
}

if [ "$NUM" = 1 ]; then
    cyclops
elif [ "$NUM" = 2 ]; then
    ettin
fi

This is a “pure bspwm” solution that uses xrandr and awk as its only dependencies. I’ve hardcoded LVDS1 (my laptop screen) as the primary output, but that could easily be replaced by a variable (just copy sec’s format) or whatever works for you. This could also be made to work for 3 or more screens, you just need to add more variables and split accordingly. Note that you could just as well assign 10 workspaces to each screen but that would make you run out of keys to bind them to pretty quickly. Call this script at the top of your bspwmrc and you’re good to go.

The second version

The first version works great as it is, but if for some reason you require a more “portable” solution, here’s my take.

#!/bin/sh

conectados=$(xrandr -q | awk '/ connected/ {count++} END {print count}')
pri=$(xrandr -q | awk '/ connected/ {print $1}' | sed -sn 1p)
sec=$(xrandr -q | awk '/ connected/ {print $1}' | sed -sn 2p)

if [ "$conectados" = 1 ]; then
    xrandr --output "$pri" --primary --auto --scale 1.0x1.0 "$(xrandr -q | awk '/ disconnected/ {print "--output", $1, "--off"}' | paste -sd ' ')"
elif [ "$conectados" = 2 ]; then
    xrandr --output "$pri" --primary --auto --scale 1.0x1.0 --output "$sec" --right-of "$pri" --auto --scale 1.0x1.0
fi

I created this version some months ago as i was messing around with some different window managers i found on the web, but they didn’t stick. Still, i kept the script because it’s a little bit cleaner i think, and it also let me separate concerns properly as I was using the previous version of the script to also set up my wallpaper and assign certain programs to different workspaces as well.

I had to resort to sed here, for reasons i haven’t taken the time to investigate, but the result is exactly the same.

I run this script from my .xinitrc and i run the following script from my bspwmrc

#!/bin/sh

M=$(bspc query -M --names)
NUM=$(echo "$M" | awk 'END{print NR}')

if [ "$NUM" = 1 ]; then
	bspc monitor -d 1 2 3 4 5 6 7 8 9 10
elif [ "$NUM" = 2 ]; then
	pri=$(echo "$M" | awk NR==1)
	sec=$(echo "$M" | awk NR==2)
	bspc monitor "$pri" -d 1 2 3 4 5
	bspc monitor "$sec" -d 6 7 8 9 10
fi

Addons

Here’s a script to switch back to a single screen

#!/bin/sh

pri=$(xrandr | awk '( $2 == "connected" ) { print $1 }' | sed -sn 1p)
xrandr --output "$pri" --primary --auto --scale 1.0x1.0 "$(xrandr | awk '( $2 == "disconnected" {print "--output", $1, "--off"}' | paste -sd ' ')"

if [ -e /tmp/bspwm_0_0-socket ]; then
    bspc config remove_disabled_monitors
    bspc config remove_unplugged_monitors
    bspc monitor -d 1 2 3 4 5 6 7 8 9 10
fi

And here’s a script to mirror screens

#!/bin/sh

screens=$(xrandr -q | awk '/ connected/ {print $1}')

grae() {
    external=$(echo "$screens" | dmenu -i -w 320 -p "Optimize resolution for: ")
    internal=$(echo "$screens" | grep -v "$external")

    res_external=$(xrandr --query | sed -n "/^$external/,/\+/p" | tail -n 1 | awk '{print $1}')
    res_internal=$(xrandr --query | sed -n "/^$internal/,/\+/p" | tail -n 1 | awk '{print $1}')

    res_ext_x=$(echo "$res_external" | sed 's/x.*//')
    res_ext_y=$(echo "$res_external" | sed 's/.*x//')
    res_int_x=$(echo "$res_internal" | sed 's/x.*//')
    res_int_y=$(echo "$res_internal" | sed 's/.*x//')

    scale_x=$(echo "$res_ext_x / $res_int_x" | bc -l)
    scale_y=$(echo "$res_ext_y / $res_int_y" | bc -l)

    xrandr --output "$external" --auto --scale 1.0x1.0 --output "$internal" --auto --same-as "$external" --scale "$scale_x"x"$scale_y"

    if [ -e /tmp/bspwm_0_0-socket ]; then
        bspc monitor -d 1 2 3 4 5 6 7 8 9 10
    fi
}
grae

The best way to use these is to bind them to a key in your sxhkdrc. Or just run them manually from the terminal.