herbstluft is German for “autumn air” and a unique moniker for a tiling window manager. It differs from bspwm which I have been using extensively for quite awhile now, in that, it is a manual tiling window manager (as opposed to dynamic)—which, interestingly, bspwm is derived from.
The term manual is somewhat of a misnomer because herbstluftwm will populate a frame according to the default layout specified for it (vertical, horizontal, max and grid) and a frame, unless otherwise split, will occupy a screen (monitor) by default. Though, as you open windows, especially while in vertical or horizontal layout, it becomes apparent why you need to manage the layout: everything quickly becomes cramped and the window spaces effectively unusable. Grid layout is a more forgiving, especially if you have a large display.
But what makes herbstluftwm interesting is the degree of control one has over the pixel space of one’s physical monitors. Virtual monitor regions can be defined for a single physical screen and, within each, individual frames and sub-frames which contain application windows.
It is, therefore, possible to effect display manipulations at the frame or monitor level, leaving the adjacent frames and monitor regions intact, allowing for a greater degree of screen real estate control. This is something that I did not appreciate as a dynamic tiling window manager user until now.
herbstluftwm has a similar client/server architecture to bspwm (or should I say, bspwm to herbstluftwm) in that it is controlled via a messenging mechanism provided by the herbstclient command. Coming from bspwm, I have really come to appreciate this approach. Hotkeys via keybinds and console commands allow one to manipulate the desktop environment with ease—and test potential keybinds and scripts.
Two particular features that have stood out immediately are the ability to chain commands and lock/unlock screen refreshes. This eliminates the need for many simple scripts and allows for visually smooth screen transitions devoid of intermediate screen actions. Absolutely brilliant! (but my tiling window manager familiarity is limited to four at the moment, so I cannot speak for other window managers).
One of the first things I did was to transcribe my bspwm configuration to herbstluftwm—a quick way to dive under the hood. A lot of my bspwm configuration was unnecessary because herbstluftwm already supported my particular workflow preferences natively.
I implemented a dynamic Conky panel with my original set of rules tailored to herbstluftwm..
Unlike my bspwm configuration, the inner window gaps are not dynamically adjusted as windows are added or removed, but set to a fixed spacing. This is because windows fill a frame and a wider spacing is used to denote (separate) frame regions. There is all manner of border and background control for windows and frame spaces available to more boldly mark the layout but I prefer to have as simple (distraction free) a presentation as possible and, for today, prefer to use window and frame spacing as a means of layout identification. It works very well visually and has a quietness about it.. sort of like autumn air.
is the primary controller called by other scripts for managing the static border of the specified display by setting the effective monitor regions with an easily configurable desktop_margin pixel setting..
#!/bin/sh
desktop_margin=70
primary=$(herbstclient list_monitors | grep '^0:' | cut -d' ' -f2)
secondary=$(herbstclient list_monitors | grep '^1:' | cut -d' ' -f2)
conky_width=$(( $(grep 'maximum_width' ~/.conkyrc | awk '{ print $2 }') + $(grep 'border_outer_margin' ~/.conkyrc | awk '{ print $2 }') * 2 ))
frame_gap=$(grep '^hc set frame_gap' ~/.config/herbstluftwm/autostart | awk '{ print $4 }')
window_gap=$(grep '^hc set window_gap' ~/.config/herbstluftwm/autostart | awk '{ print $4 }')
window_frame=$(( $window_gap + $frame_gap ))
margin=$(( $desktop_margin - $window_frame ))
window_margin=$(( $window_frame + $margin ))
case "$@" in
conky) primary=$(( 2560 - $conky_width + $frame_gap - $margin ))x$(( 1600 - $margin\2 ))+$(( 1680 + $margin ))+$margin ;;
conkyfullscreen) primary=$(( 2560 - $conky_width - $window_gap - $window_margin ))x$(( 1600 - $window_margin*2 ))+$(( 1680 + $window_margin ))+$window_margin ;;
fullframe) primary=$(( 2560 - $margin*2 ))x$(( 1600 - $margin*2 ))+$(( 1680 + $margin ))+$margin ;;
fullscreen) primary=2560x1600+1680+0 ;;
secondary) margin=$(echo $margin | awk ‘{ print int($1 * 0.25 / 0.282 + 0.5) }’)
secondary=$(( 1680 - $margin*2 ))x$(( 1050 - $margin*2 ))+$margin+$margin ;;
secondary*fullscreen) secondary=1680x1050+0+0 ;;
esac
herbstclient set_monitors $primary $secondary
Note the border adjustment for the secondary monitor so that its margin matches the primary monitor margin visually (by the ratio of the monitors’ dot pitches)!
hides and unhides the Conky panel, and sets the necessary primary monitor region..
#!/bin/sh
function conky_monitor() {
if [[ $(herbstclient attr clients.focus.fullscreen) = true ]]; then
set_monitors conky fullscreen
else
set_monitors conky
fi
}
if [[ $(xrandr | grep '*' | cut -dx -f1 | sort | tail -1) -gt 1024 ]]; then
if xdotool search –onlyvisible –classname ‘Conky’ windowunmap; then
if [[ $(herbstclient attr clients.focus.fullscreen) = true ]]; then
set_monitors fullscreen
else
set_monitors fullframe
fi
else
if ! xdotool search –classname ‘Conky’ windowmap; then
conky -q -c ~/.conkyrc &
xdotool search –sync –onlyvisible –classname ‘Conky’
fi
if herbstclient list_monitors | grep ‘^0:.*[FOCUS]’; then
conky_monitor
else
herbstclient chain lock . focus_monitor 0
conky_monitor
herbstclient chain . focus_monitor 1 . unlock
fi
fi
else
xdotool search –onlyvisible –classname ‘Conky’ windowunmap
|| xdotool search –classname ‘Conky’ windowmap
|| conky -q -c ~/.conkyrc &
fi
Calling toggle_conky and set_monitors secondary during autostart initializes the displays to their monitor margins. Everything it good to go from thereon.
sets the appropriate monitor region for fullscreen and frame based layout modes (as frames define window and frame gaps) for the monitor in focus..
#!/bin/sh
herbstclient lock
if [[ $(xrandr | grep '*' | cut -dx -f1 | sort | tail -1) -gt 1024 ]]; then
if herbstclient list_monitors | grep ‘^0:.*[FOCUS]’; then
if xdotool search –onlyvisible –classname ‘Conky’; then
if [[ $(herbstclient attr clients.focus.fullscreen) = true ]]; then
set_monitors conky
else
set_monitors conky fullscreen
fi
else
if [[ $(herbstclient attr clients.focus.fullscreen) = true ]];
then
set_monitors fullframe
else
set_monitors fullscreen
fi
fi
else
if [[ $(herbstclient attr clients.focus.fullscreen) = true ]]; then
set_monitors secondary
else
set_monitors secondary fullscreen
fi
fi
fi
herbstclient chain . fullscreen toggle . unlock
allows switching to max layout, returning to the current layout without need for scrolling through the layout schemes (typically vertical, horizontal, max and grid) to hunt for it!. My workflow typically moves between a specific layout for a frame and max..
#!/bin/sh
if [[ $(herbstclient attr clients.focus.fullscreen) = true ]]; then
toggle_fullscreen
else
layout=$(herbstclient dump ‘’ ‘@’ | sed ‘s/[ ]* ([^:])./\1/’)
tag=/tmp/herbstluftwm:tag:$(herbstclient list_monitors | grep ‘[FOCUS]’ | cut -d’”’ -f2)
if $layout != max ; then
echo $layout > $tag
herbstclient set_layout max
elif -f $tag ; then
herbstclient set_layout $(cat $tag)
rm -f $tag
else
herbstclient cycle_layout 1
fi
fi
Note: if in fullscreen, just toggle out of it!
A persistent state file for the tag containing the current layout is retrieved for restoring the layout. Using /tmp ensures any unused dangling instances are purged—though, purging in the .xinitrc or autostart is good practice.
The keybind layout control is now defined as..
herbstclient keybind Mod1-space spawn toggle_max
herbstclient keybind Mod1-Shift-space cycle_layout 1 grid vertical horizontal
to display the active tag and window application (offset positioning is specific to the .conkyrc setup)..
${if_match "${exec herbstclient list_monitors | grep '\[FOCUS\]' | cut -d: -f1}" == "1"}
${voffset -211}${font Ubuntu:size=48,weight:normal}${color3}${offset 136}${exec herbstclient list_monitors | grep '\[FOCUS\]' | cut -d'"' -f2}${font Ubuntu:size=16,weight:normal}${color4}${exec herbstclient list_monitors | grep -v '\[FOCUS\]' | cut -d'"' -f2}
${voffset -44}
${font Ubuntu:size=7,weight:normal}${color3}${alignr}${exec herbstclient substitute CLASS clients.focus.class echo CLASS}
${voffset 106}
${else}
${voffset -211}${font Ubuntu:size=48,weight:normal}${color4}${offset 136}${font Ubuntu:size=16,weight:normal}${exec herbstclient list_monitors | grep -v '\[FOCUS\]' | cut -d'"' -f2}${voffset -46}${font Ubuntu:size=48,weight:normal}${color3}${exec herbstclient list_monitors | grep '\[FOCUS\]' | cut -d'"' -f2}
${voffset -162}
${font Ubuntu:size=7,weight:normal}${color3}${alignr}${exec herbstclient substitute CLASS clients.focus.class echo CLASS}
${voffset 106}
${endif}
So I am livin’ in herbstluftwm for now—that, and conditioning myself on my new Poker 2 keyboard—and finding it a delight. Window manager preferences can be a fickle thing amongst the Linux community. The shear choice beckons comparison and biases.
Is herbstluftwm better than bspwm? Both are great tiling window managers. Both are very malleable. Both will stay on my machines. They are both very different and competent window managers and, I am finding myself surprisingly comfortable with manual tiling. I think herbstluftwm’s feature set becomes more self-evident with larger display real estate.
Manual tiling can add a few extra keystrokes here and there. But the benefit is that it is easier to obtain precise placement and control of window objects because the window manager is not deciding that for you.
But sometimes I like to be lazy too..