Configure herbstluftwm to behave like i3 on tag switching

May 20, 2021

I recently switched from i3 to herbstluftwm, motivated by the promise, that herbstluftwm really does decline to try to guess what is the correct behaviour for me, but really just does what I tell it to.

herbstluftwm is fast, lightweight, renders everything I throw at it correctly: Want a second, autohiding panel at the bottom? Sure thing; switching resolutions when accessing the desktop session from remote via nomachine's nx? Just tell me to detect monitors, I'll adept.

The standard behaviour of the i3 window manager when switching tags is a toggling behaviour on multiple keypresses to activate a specific tag, that is it will toggle between the activated and the previously active tab. This is something that really grew on me - switching between documentation and shell or editor, etc. without extranious finger movement.

herbstluftwm suggests the following keybindings in it's default autostart file, at least on ubuntu as well as manjaro linux:

for i in "${!tag_names[@]}" ; do
    hc add "${tag_names[$i]}"
    key="${tag_keys[$i]}"
    if ! [ -z "$key" ] ; then
        hc keybind "$Mod-$key" use_index "$i"
        hc keybind "$Mod-Shift-$key" move_index "$i"
    fi
done

At the moment of writing this text, there is no other aspect of using i3 that I miss – they both are minimalistic, tiling window managers, i.e. not that different. My first thought was to somehow imitate i3's behaviour with a shellscript, and use the spawn command instead of use_index:

hc keybind "$Mod-$key" spawn ${HOME}/.config/herbstluftwm/switch "${tag_names[$i]}"

The switch command is simple enough - compare the argument given with the currently active tag and use_previous if they are the same, or switch to the given tag if not:

#!/bin/bash
hc() {
    herbstclient "$@"
}

if [ "${1}" == "$(hc attr monitors.focus.tag)" ]; then
    hc use_previous
else
    hc use "${1}"
fi

This worked like a charm. But it bothered me a little, that there were a lot of processes spawned that I felt shouldn't be necessary. On further digging into the possibilities, I discovered the compare command. In combination with and and or the simple script switch can be translated into a single herbstclient call:

hc keybind "$Mod-$key" or / and + compare monitors.focus.tag "=" "${tag_names[$i]}" + use_previous / use "${tag_names[$i]}"

Feels more responsive too - the brain is a magical place :-)

The complete loop for all the copy 'n paste needs you (or I) might have:

for i in "${!tag_names[@]}" ; do
  hc add "${tag_names[$i]}"
  key="${tag_keys[$i]}"
  if ! [ -z "$key" ] ; then
      hc keybind "$Mod-$key" or / and + compare monitors.focus.tag "=" "${tag_names[$i]}" + use_previous / use "${tag_names[$i]}"
      hc keybind "$Mod-Shift-$key" move_index "$i"
  fi
done