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.