BSPWM Multimonitor Support
2020-12-09


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

```sh {linenos=table}
#!/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.

```sh
#!/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`

```sh
#!/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

```sh
#!/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

```sh
#!/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.