Preface

Goal: Separate Main Flow, Code, and Data.

So anyone can focus to alter special customization in Main Script, without changing the whole stuff.

Reading

Before you jump off to scripting, you might desire to read this overview.

All The Source Code:

Impatient coder like me, like to open many tab on browser.


Modularized HerbstluftWM in Many Languages

This article is one part of a collection. All integrated, on related to another. So we can compare each other quickly.

Tutorial/ Guidance/ Article: [ Modularized Overview ] [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]

Source Code Directory: [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]


Screenshot

Since statusbar is out of topic in this tutorial, I present no panel HerbstluftWM screenshot featuring zero gap.

HerbstluftWM: Screenshot


Directory Structure

Directory Structure has been explained in preface. This figure will explain how it looks in BASH script directory.

HerbstluftWM: Directory Structure


Modularizing in BASH

Here we are talking how to create module and call module. It is very straightforward in BASH. No dark magic required.

Declare a module

Nothing to declare, just do not forget the shebang #! and executable permission.

#!/usr/bin/env bash

Call a module

DIR=$(dirname "$0")

. ${DIR}/helper.sh

System Calls

There is no such thing as system calls in bash. Everything in bash is command called in system environment. That is the most advantage of using BASH to configure HLWM.

Here we wrap herbstclient system call in a function named hc.

helper.sh

function hc() {
    herbstclient "$@"
}

autostart.sh

# Read the manual in $ man herbstluftwm
hc emit_hook reload

# gap counter
echo 35 > /tmp/herbstluftwm-gap

Array: Tag Names and Keys

config.sh

tag_names=( {1..9} )
tag_keys=( {1..9} 0 )

HerbstluftWM: Tag Status


Hash: Color Schemes

Using key-value pairs, a simple data structure.

assets/gmc.sh

declare -A color=(
    ['white']='#ffffff'
    ['black']='#000000'

    ['grey50']='#fafafa'
    ['grey100']='#f5f5f5'
)

autostart.sh

# background before wallpaper
xsetroot -solid "${color['blue500']}"

View Source File:

Similar Code: [ BASH Color ] [ Perl Color ] [ Python Color ] [ Ruby Color ] [ PHP Color ] [ Lua Color ] [ Haskell Color ]


Hash: Config

The Hash in Config is very similar with the colors above. Except that it has string interpolation all over the place.

config.sh

# Modifier variables
s=Shift
c=Control
m=Mod4
a=Mod1

declare -A keybinds=(
  # session
    ["$m-$s-q"]='quit'
    ["$m-$s-r"]='reload'
    ["$m-$s-c"]='close'
)

This config will be utilized in main script as shown in the following code.

autostart.sh

do_config "keybind"   "$(declare -p keybinds)"
do_config "keybind"   "$(declare -p tagskeybinds)"
do_config "mousebind" "$(declare -p mousebinds)"
do_config "attr"      "$(declare -p attributes)"
do_config "set"       "$(declare -p sets)"
do_config "rule"      "$(declare -p rules)"

Specific BASH Issue

Be aware of these two. Specific BASH only issue. Since BASH directly interpret the interpolation in system environment. Changing double quote to single quote prevent the terminal spawning, even when you double quote it later.

declare -A keybinds=(
    ["$m-Return"]="spawn ${TERMINAL:-xfce4-terminal}"
)

I also have unsolved issue with tilde ~ expansion inside double quote.v I have still have to use some config without helper.

do_config 'rule'      "$(declare -p rules)"

# avoid tilde problem, not using helper
hc rule windowtype~'_NET_WM_WINDOW_TYPE_(NOTIFICATION|DOCK|DESKTOP)' manage=off
hc rule windowtype~'_NET_WM_WINDOW_TYPE_(DIALOG|UTILITY|SPLASH)' pseudotile=on

View Source File:

Similar Code: [ BASH Config ] [ Perl Config ] [ Python Config ] [ Ruby Config ] [ PHP Config ] [ Lua Config ] [ Haskell Config ]


Processing The Hash Config

This is the heart of this script.

This do-config function has two arguments, the herbstclient command i.e “keybind”, and hash from config. Since BASH does not have built support for passing hash argument, this require a little hack and a few cryptic character.

helper.sh

function do_config()
{
    local command="${1}"
    shift
    
    # associative array hack
    eval "declare -A hash="${1#*=}

    # loop over hash    
    for key in "${!hash[@]}"; do
        local value=${hash[$key]}        
        hc $command $key $value
        
        # uncomment to debug in terminal
        # echo $command $key $value
    done
}

Debug Herbstclient Command

I do not remove line where I do debug when I made this script, so anyone can use it later, avoid examining blindly. Sometimes strange things happen. Just uncomment this line to see what happened.

        echo $command $key $value

You can see the debugging result in figure below.

HerbstluftWM: Debug Command

View Source File:

Similar Code: [ BASH Helper ] [ Perl Helper ] [ Python Helper ] [ Ruby Helper ] [ PHP Helper ] [ Lua Helper ] [ Haskell Helper ]


Setting the Tags

This should be done before doing any config rules. I did copy-paste part of the original configuration, turn the code into function, and make just a few modification.

Nothing special here, BASH read all global variable from other files.

helper.sh

function set_tags_with_name() {
    hc rename default "${tag_names[0]}" 2>/dev/null || true
    
    for index in ${!tag_names[@]} ; do
        hc add "${tag_names[$index]}"
        
        local key="${tag_keys[$index]}"
        if ! [ -z "$key" ] ; then
            hc keybind "$m-$key" use_index "$index"
            hc keybind "$m-Shift-$key" move_index "$index"
        fi
    done
}

Launch the Panel

Two more functions left, it is do_panel and startup_run. Again, I did copy-paste part of the original configuration, turn the code into function, and make just a few modification.

This two should be very easy to do in BASH. Nothing special in BASH. But this could be complex task, in other language, such as Lua.

helper.sh

function do_panel() {
    local panel=~/.config/herbstluftwm/bash/dzen2/panel.sh
    [ -x "$panel" ] || panel=/etc/xdg/herbstluftwm/panel.sh
    for monitor in $(herbstclient list_monitors | cut -d: -f1) ; do
        # start it on each monitor
        "$panel" $monitor &
    done
}

Run Baby Run

This is the last part. It is intended to be modified. Everyone has their own personal preferences.

startup.sh

startup_run() {
    command="silent new_attr bool my_not_first_autostart"
    
    if hc $command ; then
      # non windowed app
        compton &
        dunst &
        parcellite &
        nitrogen --restore &
        mpd &
    
      # windowed app
        xfce4-terminal &
        sleep 1 && firefox &
        sleep 2 && geany &
        sleep 2 && thunar &
    fi
}

View Source File:

Similar Code: [ BASH Startup ] [ Perl Startup ] [ Python Startup ] [ Ruby Startup ] [ PHP Startup ] [ Lua Startup ] [ Haskell Startup ]


Putting It All Together.

The last part is going to main script and putting it all back together.

Now the flow is clear

Header Part: autostart.sh

DIR=$(dirname "$0")

. ${DIR}/assets/gmc.sh
. ${DIR}/config.sh
. ${DIR}/helper.sh
. ${DIR}/startup.sh

Procedural Part: autostart.sh

# background before wallpaper
xsetroot -solid "${color['blue500']}"

# Read the manual in $ man herbstluftwm
hc emit_hook reload

# gap counter
echo 35 > /tmp/herbstluftwm-gap

# do not repaint until unlock
hc lock

# standard
hc keyunbind --all
hc mouseunbind --all
hc unrule -F

set_tags_with_name

# do hash config
do_config "keybind"   "$(declare -p keybinds)"
do_config "keybind"   "$(declare -p tagskeybinds)"
do_config "mousebind" "$(declare -p mousebinds)"
do_config "attr"      "$(declare -p attributes)"
do_config "set"       "$(declare -p sets)"
do_config "rule"      "$(declare -p rules)"

# unlock, just to be sure
hc unlock

# launch statusbar panel
do_panel

# load on startup
startup_run

View Source File:

Similar Code: [ BASH autostart ] [ Perl autostart ] [ Python autostart ] [ Ruby autostart ] [ PHP autostart ] [ Lua autostart ] [ Haskell autostart ]


Coming up Next

After the Window Manager, comes the Panel.


Happy Configuring.