bspwm is a tiling window manager whose messenging architecture accommodates window management gymnastics via sxhkd (or any other keyboard handler) and shell scripting versus the native language customizations which would be required with other tiling window managers, such as notion (lua) and xmonad (haskell), two tiling window managers I have used extensively in the past. Each has their own unique qualities and strengths.
bspwm is somewhat unique in that there is no default configuration— though, there is a complete sample sxhkdrc configuration file providing the usual window manager operations to get you started. Anyone familiar with shell scripting can quickly tailor bspwm via bspc commands to fit one’s workflow.
The following sxhkdrc code snippets illustrate some ways the behaviour of bspwm can be tailored in a dual monitor multihead environment.
is a terminal application whose visibility is toggled via xdotool. Many common terminal applications such as xterm, urxvt and terminator, allow setting their classname for identification by “bspc query –tree”.
VTE terminals such as roxterm do not permit changing the classname of the application, providing the role name instead, in compliance with GTK naming policy. Unfortunately, bspwm does not use the role window attribute. For such terminals, the grep for classname “scratchpad” in the scripts that follow, should be replaced with “grep ‘{term}.*—–s–’” where {term} is the terminal application name, as a means of identifying the sticky scratchpad. To add a bspwm rule for such terminals, an “external_rules_command” script needs to trap the window role or title (in this example) and echo the desired bspwm window property..
#!/bin/sh
title=$(xwinfo -n $1)
case "$title" in
…
scratchpad) echo “sticky=true” ;;
…
esac
By default, a sticky scratchpad visible on a desktop, will switch monitors if the desktop is swapped with the other monitor. As per my personal UI policy, the scratchpad when opened, occupies the main (biggest) window. I used to configure the scratchpad like most tiling managers do, as a floating window, but as I prefer a sizeable window to work in, a floating window often hides desktop content I am interested in. Having a sticky normal terminal window and attaching tmux via byobu to the scratchpad made it so much more useful that I rarely find need to open new terminal windows.
bspwm by default assigns desktops to monitors, so when desktops are switched, the associated monitor also receives focus. This can be a preferable workflow where it is desirable to open specific applications on specific monitors—such as, always opening an image editor on the highest resolution monitor.
I prefer to be able to focus any desktop on the currently focused monitor à la xmonad. If the requested desktop is the sole desktop for a monitor, the monitor desktops are swapped, as a monitor must have at least one desktop.
alt + {1-9,0}
D={1-9,0};
M=$(bspc query –monitors –desktop $D);
if [[ $(bspc query –desktops –monitor $M | wc -l) -gt 1 ]]; then
if [[ $(bspc query –desktops –desktop focused) != $D ]]; then
bspc desktop $D –to-monitor focused;
bspc desktop $D –focus;
fi;
elif [[ $(bspc query –monitors –monitor focused) != $M ]]; then
bspc desktop DVI-0:focused –swap DVI-1:focused;
fi
the UI work flow policy I prefer is to have window actions focus and place the window in the main (biggest) window on the target desktop. Most tiling window managers place the new window in the current active window of that desktop, which ever one it may be—all the more difficult to remember when the desktop is hidden!
As focusing a window necessarily raises the desktop it is on, the current desktop must be restored. If the target desktop resides on the opposite monitor, focus must be returned to the original monitor, as well!
alt + shift + {1-9,0}
D={1-9,0};
M=$(bspc query –monitors –monitor focused);
N=$(bspc query –monitors –desktop $D);
W=$(bspc query –windows –window focused);
[[ $M != $N ]] && bspc monitor $N –focus;
E=$(bspc query –desktops –desktop focused);
bspc window $W –to-desktop $D;
bspc window $W –focus;
bspc window –swap biggest;
bspc desktop $E –focus;
[[ $M != $N ]] && bspc monitor $M --focus
whilst retaining focus of the current window frame and desktop.
alt + apostrophe
M=$(bspc query –monitors –window last);
bspc window –swap last;
[[ $M = $(bspc query --monitors --monitor focused) ]] && bspc window --focus last
bspwm implements a swap monitor action (kudos to the developer for accommodating so many user requests) but I like to be able to retain or swap monitor focus as well, depending on the direction of the hotkey relative to the monitor which has focus—essentially, if the key action points to the opposite monitor, swap the desktop moving the focus with the desktop, else retain focus on the current monitor whilst cycling the desktop!
Sounds more complicated than it is but is a thing of beauty and illustrates how malleable bspwm is.
alt + shift + bracket{left,right}
S=$(bspc query –tree | grep ‘scratchpad’);
xdotool search –onlyvisible –classname ‘scratchpad’ windowunmap;
M=$(bspc query –monitors –monitor focused);
bspc desktop DVI-0:focused –swap DVI-1:focused;
[[ -z $S ]] || xdotool search –classname ‘scratchpad’ windowmap;
[[ $M = DVI-{1,0} ]] && bspc monitor --focus last
is similar to sending a selected window to a specific desktop but doesn’t require knowing which desktop is showing on the opposite monitor. As above, if the key action points to the opposite monitor, the window retains focus, else the current desktop retains focus!
alt + ctrl + bracket{left,right}
D=DVI-{0,1};
M=$(bspc query –monitors –monitor focused);
W=$(bspc query –windows –window focused);
S=$(bspc query –tree | grep ‘scratchpad’ | grep $W);
bspc monitor $D –focus;
if [[ -z $S ]]; then
bspc window $W –to-desktop $(bspc query –desktops –desktop focused);
bspc window $W –focus;
else
xdotool search –onlyvisible –classname ‘scratchpad’ windowunmap;
xdotool search –classname ‘scratchpad’ windowmap;
fi;
bspc window –swap biggest;
[[ $M = $D ]] && bspc monitor $D --focus
hide the scratchpad or popup the scratchpad terminal window on the current desktop.
alt + o
if ! xdotool search –onlyvisible –classname ‘scratchpad’ windowunmap; then
if ! xdotool search –classname ‘scratchpad’ windowmap; then
urxvt -title ‘scratchpad’ -name ‘scratchpad’ -e byobu;
bspc window –swap biggest;
fi;
fi
tiling window managers are commonly configured to show desktop information and the requisite nerdy system stats in a horizontal status line. bspwm also supports a status line facility but creating a vertical conky “dock” in .conkyrc with..
own_window_type dock
own_window_hints sticky,above
that can be toggled on the side of the monitor provides a distraction free desktop when desired versus a continually updating status line at the top or bottom of the screen (whilst saving precious screen real estate on laptop displays) and allows using conky in all its graphical glory!
Being able to display a conky panel above the desktop windows via xdotool eliminates the need to run conky on the desktop background with a compositor (to make the desktop windows transparent and the video demands that entails) and makes the system information available on demand (plus, not all applications support transparency).
A bspwm window rule that sets “fullscreen=true” should be used with “fullscreen=true lower=true”, else the conky panel will subsequently be hidden (below the desktop windows). Sometimes the conky panel will be hidden depending on whether it is visible at the time a fullscreen window is opened (a bspwm conflict with the conky panel window hints?)—raising conky resets its visibility within bspwm.
If the screen display width is sufficient which, in this case, is arbitrarily set to anything more than my trusty netbook, the monitor padding is toggled to automatically adjust all the visible windows on the monitor (versus displaying Conky above the current windows)— pretty slick! My netbook is exempt because its screen width already limits the usefulness of multiple windows.
alt + backslash
if [[ $(xrandr | grep ‘*’ | cut -dx -f1 | sort | tail -1) -gt 1024 ]]; then
if xdotool search –onlyvisible –classname ‘Conky’ windowunmap; then
bspc config -m DVI-1 right_padding 0;
else
bspc config -m DVI-1 right_padding $CONKY_WIDTH;
xdotool search –classname ‘Conky’ windowmap
|| conky -q -c ~/.config/conky/conkyrc.$(hostname) &
fi;
else
xdotool search –onlyvisible –classname ‘Conky’ windowunmap
|| xdotool search –classname ‘Conky’ windowmap
|| conky -q -c ~/.config/conky/conkyrc.$(hostname) &
fi
alt + ctrl + backslash
bspc config -m DVI-1 right_padding $CONKY_WIDTH;
raise Conky
The CONKY_WIDTH env variable is simply set in the .xinitrc for convenience to hold the desired padding value, in this case, the actual “maximum_width” setting in my conkyrc file, automatically updating the value if it is changed:
export CONKY_WIDTH=$(grep 'maximum_width' ~/.config/conky/conkyrc.$(hostname) | cut -d' ' -f2)
The xdotool raise script..
#!/usr/bin/xdotool
search --onlyvisible --classname $1
windowraise %@
of course, the default keybindings that come with bspwm are quite usable out of the box. Anyone familiar with tiling window managers should have no difficulty acquainting themselves with them. I have normalized the key assignments to provide a preferred and consistent UI behaviour between tiling window managers—a good way to dive into a window manager and ease switching between them.
My search for tiling window managers, while incomplete, may have ended with bspwm.