Codezen

On Bspwm Tweaking

software

Jack

I’ve written before on my travels through the tiling WM landscape. It’s been awhile though.

My most recent discovery is bspwm, which is a tiling WM that mixes automatic (think Xmonad) and manual (think ion3/notion) tiling as well as a hands off, but play nice approach to other desktop necessities like status bars and trays.

Why bspwm?

To summarize, bspwm fits into the sweet spot where it has the minimalism of Xmonad, trading out the Haskell for shell scripts and a whole lot of scripting potential.

My Setup

First an obligatory double-wide screenshot.

2015-03-12-000528_3840x1080_scrot

Very simple, but there are a few upgrades from the examples in the git repo, despite the fact that the color scheme is the same.

Named Desktops

Bspwm natively supports named desktops, but they’re cumbersome to use since you have to use bspc from somewhere. The example config spawns 10 desktops by number and calls it good. Well, coming from notion (a fork of ion3), I got used to the ability to name desktops something useful to remember what the hell I was doing on them, and then shift between them easily.

As such, I’ve added two scripts, which are trivial wrappers around dmenu and bspc

#!/bin/bash

DMENU_NF="#A3A6AB"
DMENU_NB="#34322E"
DMENU_SF="#F6F9FF"
DMENU_SB="#5C5955"

DESKTOP_NAME=bspc query -D | dmenu -b -nf "$DMENU_NF" -nb "$DMENU_NB" -sf "$DMENU_SF" -sb "$DMENU_SB" -p 'Desktop:'

if [ -z $DESKTOP_NAME ]; then
    exit 0
fi

for existing_desktop in bspc query -D; do
    if [ "$DESKTOP_NAME" == "$existing_desktop" ]; then
        bspc desktop "$DESKTOP_NAME" -m bspc query -M -m focused
        bspc desktop -f "$DESKTOP_NAME"
        exit 0
    fi
done

bspc monitor -a "$DESKTOP_NAME"
bspc desktop -f "$DESKTOP_NAME"
#!/bin/bash

DMENU_NF="#A3A6AB"
DMENU_NB="#34322E"
DMENU_SF="#F6F9FF"
DMENU_SB="#5C5955"

DESKTOP_NAME=echo '' | dmenu -b -nf "$DMENU_NF" -nb "$DMENU_NB" -sf "$DMENU_SF" -sb "$DMENU_SB" -p 'Rename:'
if [ -z $DESKTOP_NAME ]; then
    exit 0
fi

bspc desktop -n "$DESKTOP_NAME"

I put these into my $PATH someplace, I use ~/bin for all of my custom scripts.

Then, in sxhkdrc:

super + {_,ctrl} + d
    /home/jack/bin/bspwm_{_,re}name_desktop

super + alt + d
    bspc desktop focused -r

This allows you to use Super + d to create a new named desktop, or switch to it if it already exists. Super + ctrl + d will rename the currently focused desktop. Finally, Super + alt + d will destroy the focused desktop (but only if it’s empty and not the only desktop on its monitor, which is a restriction of bspwm).

Panel Improvements

I’ve combined the various panel example files into a custom single file that’s still run from bspwmrc.

This covers the enumerated desktops, split monitor status, tray, and hostname tweaks I mentioned above.

Here’s the full panel script, commented with improvements:

#! /bin/sh
#

# Absorb all of the rote variable setting to make our panel self-contained

# From example .profile changes

PANEL_FIFO="$HOME/.config/bspwm/panfifo"
PANEL_HEIGHT="16"
PANEL_FONT_FAMILY="-*-terminus-medium-r-normal-*-12-*-*-*-c-*-*-1"

# From panel_colors file

COLOR_FOREGROUND='#FFA3A6AB'
COLOR_BACKGROUND='#FF34322E'
COLOR_ACTIVE_MONITOR_FG='#FF34322E'
COLOR_ACTIVE_MONITOR_BG='#FF58C5F1'
COLOR_INACTIVE_MONITOR_FG='#FF58C5F1'
COLOR_INACTIVE_MONITOR_BG='#FF34322E'
COLOR_FOCUSED_OCCUPIED_FG='#FFF6F9FF'
COLOR_FOCUSED_OCCUPIED_BG='#FF5C5955'
COLOR_FOCUSED_FREE_FG='#FFF6F9FF'
COLOR_FOCUSED_FREE_BG='#FF6D561C'
COLOR_FOCUSED_URGENT_FG='#FF34322E'
COLOR_FOCUSED_URGENT_BG='#FFF9A299'
COLOR_OCCUPIED_FG='#FFA3A6AB'
COLOR_OCCUPIED_BG='#FF34322E'
COLOR_FREE_FG='#FF6F7277'
COLOR_FREE_BG='#FF34322E'
COLOR_URGENT_FG='#FFF9A299'
COLOR_URGENT_BG='#FF34322E'
COLOR_LAYOUT_FG='#FFA3A6AB'
COLOR_LAYOUT_BG='#FF34322E'
COLOR_TITLE_FG='#FFA3A6AB'
COLOR_TITLE_BG='#FF34322E'
COLOR_STATUS_FG='#FFA3A6AB'
COLOR_STATUS_BG='#FF34322E'

# Kill any panel processes older than us, instead of bailing like the example
# does. That caused one too many panel-less boots for me.

while [ $(pgrep -cx panel) -gt 1 ] ; do
	pkill -ox -9 panel
done

# Kill any remaining trays / xtitle instances so we don't have multiples.

killall -9 stalonetray
killall -9 xtitle

# Setup taken from example, tell bspwm to avoid our status/tray and to start
# sending status updates to a FIFO

trap 'trap - TERM; kill 0' INT TERM QUIT EXIT

[ -e "$PANEL_FIFO" ] && rm "$PANEL_FIFO"
mkfifo "$PANEL_FIFO"

bspc config top_padding $PANEL_HEIGHT
bspc control --subscribe > "$PANEL_FIFO" &

# Here are the subprograms that add information to the status FIFO which are
# interpreted by panel_bar, below. Each output is detected by its first
# character, which is how the bspwm internal information is presented.

# T - xtitle output
# S - date output (same as example)
# B - battery output

# Title

xtitle -sf 'T%s' > "$PANEL_FIFO" &

# Simple date

function clock {
    while true; do
        date +"S%m/%d %H:%M"
        sleep 1
    done
}

clock > "$PANEL_FIFO" &

# No frills battery monitor (Linux specific, probably)
# This is only enabled on certain hostnames, at the end of this file.

BAT="/sys/class/power_supply/BAT0"

function bat_percent {
    while true; do
        CHARGE_NOW=cat $BAT/charge_now
        CHARGE_FULL=cat $BAT/charge_full
        PERCENT=echo "($CHARGE_NOW * 100)/$CHARGE_FULL" | bc
        STATUS=cat $BAT/status

        if [ $STATUS == "Charging" ]; then
            STATUS="+"
        elif [ $STATUS == "Discharging" ]; then
            STATUS="-"
        else
            STATUS=""
        fi

        echo "B$STATUS$PERCENT"
        sleep 1
    done
}

# Now panel_bar, which was mostly taken from the example panel_bar, with a
# handful of improvements.

# - functionified, from panel_bar file of example
# - the output changes based on the number of monitors, to place a single
# monitors's information on that same monitor, instead of all in one corner.
# - added B header for battery
# - all the desktop indicators are enumerated

num_mon=$(bspc query -M | wc -l)

wm_info_array=("" "" "" "" "")

function panel_bar {
    while read line < $PANEL_FIFO; do
        case $line in
            S*)
                # clock output
                date="${line#?}"
                ;;
            B*)
                # battery output
                percent="${line#?}"
                ;;
            T*)
                # xtitle output
                title="%{F$COLOR_TITLE_FG}%{B$COLOR_TITLE_BG} ${line#?} %{B-}%{F-}"
                ;;
            W*)
                # bspwm internal state
                wm_infos=""
                cur_mon=-1
                desktop_num=1

                IFS=':'
                set -- ${line#?}
                while [ $# -gt 0 ] ; do
                    item=$1
                    name=${item#?}
                    case $item in
                        M*)
                            # active monitor
                            cur_mon=$((cur_mon + 1))
                            wm_infos=""
                            if [ $num_mon -gt 1 ] ; then
                                wm_infos="$wm_infos %{F$COLOR_ACTIVE_MONITOR_FG}%{B$COLOR_ACTIVE_MONITOR_BG} ${name} %{B-}%{F-}  "
                            fi
                            ;;
                        m*)
                            # inactive monitor
                            cur_mon=$((cur_mon + 1))
                            wm_infos=""
                            if [ $num_mon -gt 1 ] ; then
                                wm_infos="$wm_infos %{F$COLOR_INACTIVE_MONITOR_FG}%{B$COLOR_INACTIVE_MONITOR_BG} ${name} %{B-}%{F-}  "
                            fi
                            ;;
                        O*)
                            # focused occupied desktop
                            wm_infos="$wm_infos%{F$COLOR_FOCUSED_OCCUPIED_FG}%{B$COLOR_FOCUSED_OCCUPIED_BG}%{U$COLOR_FOREGROUND}%{+u} ${desktop_num}. ${name} %{-u}%{B-}%{F-}"
                            desktop_num=$((desktop_num + 1))
                            ;;
                        F*)
                            # focused free desktop
                            wm_infos="$wm_infos%{F$COLOR_FOCUSED_FREE_FG}%{B$COLOR_FOCUSED_FREE_BG}%{U$COLOR_FOREGROUND}%{+u} ${desktop_num}. ${name} %{-u}%{B-}%{F-}"
                            desktop_num=$((desktop_num + 1))
                            ;;
                        U*)
                            # focused urgent desktop
                            wm_infos="$wm_infos%{F$COLOR_FOCUSED_URGENT_FG}%{B$COLOR_FOCUSED_URGENT_BG}%{U$COLOR_FOREGROUND}%{+u} ${desktop_num}. ${name} %{-u}%{B-}%{F-}"
                            desktop_num=$((desktop_num + 1))
                            ;;
                        o*)
                            # occupied desktop
                            wm_infos="$wm_infos%{F$COLOR_OCCUPIED_FG}%{B$COLOR_OCCUPIED_BG} ${desktop_num}. ${name} %{B-}%{F-}"
                            desktop_num=$((desktop_num + 1))
                            ;;
                        f*)
                            # free desktop
                            wm_infos="$wm_infos%{F$COLOR_FREE_FG}%{B$COLOR_FREE_BG} ${desktop_num}. ${name} %{B-}%{F-}"
                            desktop_num=$((desktop_num + 1))
                            ;;
                        u*)
                            # urgent desktop
                            wm_infos="$wm_infos%{F$COLOR_URGENT_FG}%{B$COLOR_URGENT_BG} ${desktop_num}. ${name} %{B-}%{F-}"
                            desktop_num=$((desktop_num + 1))
                            ;;
                        L*)
                            # layout
                            wm_infos="$wm_infos %{F$COLOR_LAYOUT_FG}%{B$COLOR_LAYOUT_BG} ${name} %{B-}%{F-}"
                            ;;
                    esac
                    shift
                    wm_info_array[cur_mon]="$wm_infos"
                done
                ;;
        esac

        if [ $num_mon -eq 1 ]; then
            fmt="%{l}${wm_info_array[0]}%{c}${title}%{r}${percent} ${date}"
        elif [ $num_mon -eq 2 ]; then
            fmt="%{l}${wm_info_array[0]}%{c}${title}%{S+}%{l}${wm_info_array[1]}%{r}${percent} ${date}"
        else
            # Same as 2 -- needs someone to test
            fmt="%{l}${wm_info_array[0]}%{c}${title}%{S+}%{l}${wm_info_array[1]}%{r}${percent} ${date}"
        fi

        printf "%s\n" "$fmt"
    done
}

# Actually invoking the panel and piping to bar

panel_bar | bar -g x$PANEL_HEIGHT -f "$PANEL_FONT_FAMILY" -F "$COLOR_FOREGROUND" -B "$COLOR_BACKGROUND" &

# Hostname tweaks, including forcing stalonetray into a reasonable place, and
# starting the battery monitor on my laptop

HOSTNAME=hostname

# Echelon dual monitor, tray should be upper right corner of left monitor

if [ $HOSTNAME == "echelon" ]; then
    TRAY_GEOM="1x1-1920"

# Toadite single monitor, avoid date and battery

elif [ $HOSTNAME == "toadite" ]; then
    bat_percent > "$PANEL_FIFO" &
    TRAY_GEOM="1x1-100"

# Good for single monitor, just about enough room to avoid the date output

else
    TRAY_GEOM="1x1-75"
fi

stalonetray --geometry $TRAY_GEOM -i $PANEL_HEIGHT -bg "#34322e" --grow-gravity NE --kludges force_icons_size &

wait

I can’t really explain any better than the comments, but I think I’m using a decent setup that will allow for future additions to the panel pretty easily just by mimicking the date and battery status functions and their invocations.

Monitor Binds

I simply added this to my sxhkdrc

super + {q,w}
    bspc monitor -f {L,R}

super + {shift,alt} + {q,w}
    bspc {window,desktop} -m {L,R}

This follows the Xmonad convention, where Q and W represent left and right monitors respectively, so super + q focuses the left monitor, super + w the right monitor. If I had three monitors, I would use QWE for Left, Center, Right.

The second set of binds sets up super + shift + q/w to send windows to a specific monitor’s desktop, and super + alt + q/w to shift an entire desktop to another monitor. I find bspwm’s desktop focus to be a bit wonky with multiple monitors (focusing a desktop will focus it on whatever monitor it’s associated with, rather than the current monitor), but I still only rarely have to shift desktops between monitors.

In order for these binds to work correctly on multiple hosts, in bspwmrc I added this block to rename my monitors consistently across machines.

HOSTNAME=hostname

if [ $HOSTNAME == "echelon" ]; then
    bspc monitor HDMI-0 -n R
    bspc monitor DVI-I-1 -n L
    bspc monitor R -s L
    bspc monitor L -d ""
    bspc monitor R -d ""
elif [ $HOSTNAME == "toadite" ]; then
    bspc monitor LVDS-0 -n L
    bspc monitor L -d ""
fi

This also clears the desktops, down to a single unnamed desktop on each monitor.

Download

You can grab my config files here.

This includes everything you should need to run bspwm with my config. It untars such that the bspwm-config directory is like your HOME, so the config files are in .config, and won’t be visible by default.

The only caveat is that you’ll need to put the bin files in your $PATH someplace. Like the example config, it also expects that you have xtitle and bar-ain't-recursive installed.

If I end up making any more significant changes, I’ll consider putting this up on my Github, but for now I’m pretty content with my setup and don’t feel the need to version control it.

Comments

  1. Thanks for the great write-up! Not sure why, but for me the behavior of [panel_bar | bar] with [read line < $PANEL_FIFO] (like in your script) is a bit different from the bspwm panel example, which instead has [cat "$PANEL_FIFO" | panel_bar] with [read line -r] (from stdin). With your version, redirecting xtitle, clock, and bspc to the fifo frequently fails for some reason (maybe due to something I don't understand about redirection), causing only one to two of the desktop indicator, title, and clock to show up on any given run. I fixed it by making panel_bar a function like you did, but reading from stdin like the example, then catting the fifo to this function.

  2. I like the way you have got rid of some config files but you didn’t say if your desktop numbers in the bar are clickable? So you can click on them instead of always having to use the keyboard.

  3. Thank you for publishing this article. I having been using bspwm for a year and really love it. I am currently working on a new “ricing”. I really like your color scheme in the terminal shots. Would you be willing to provide their color scheme? They are great. So easy to read configs. I spend alot of time working on code and I find this color scheme great.
    thanks

    1. Since you mentioned configs, I’m assuming you mean the syntax highlighting on the site and not the random Vim session in my screens.

      I’m using a plugin called “Crayon Syntax Highlighter” which comes with a theme “Son of Obsidian”, that has a page here: http://studiostyl.es/schemes/son-of-obsidian Unfortunately, it looks like a Visual Studio scheme by default =/

Leave a Reply to gordon daniels Cancel reply

Your email address will not be published. Required fields are marked *