Goal: Separate Main Flow, Code, and Data.

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


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.


For a more sophisticated HerbstluftWM configuration in Perl. You may also take a look at this script. I am also inspired by this configuration script. This give influence to my script.

Modularized HerbstluftWM in Many Languages

This article is one part of a collection. All integrated, one 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 ]

Directory Structure

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

HerbstluftWM: Directory Structure

Modularizing in Perl

While main script using .pl extension (perl library), module use .pm. extension (perl module).

Declare a module

Perl module have to explicitly define what to export. Anything exported become public in caller script. And the rest is private to module.

Here we export hc function variable from helper module.

package helper;

use warnings;
use strict;

use config;

use Exporter;
our @ISA = 'Exporter';
our @EXPORT = qw(hc);

Call a module

use helper;

System Calls

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

function hc() {
    system("herbstclient @_");

# Read the manual in $ man herbstluftwm
hc('emit_hook reload');

# gap counter
system("echo 35 > /tmp/herbstluftwm-gap");

Array: Tag Names and Keys

our @tag_names = (1..9);
our @tag_keys  = (1..9, 0);

HerbstluftWM: Tag Status

Hash: Color Schemes

Using key-value pairs, a simple data structure.

our %color = (
    'white' => '#ffffff',
    'black' => '#000000',

    'grey50'  => '#fafafa',
    'grey100' => '#f5f5f5'

# background before wallpaper
system("xsetroot -solid '$color{'blue500'}'");

View Source File:

Hash: Config

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

# Modifier variables
my $s = 'Shift';
my $c = 'Control';
my $m = 'Mod4';
my $a = 'Mod1';

our %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.

helper::do_config("keybind",   %config::keybinds);
helper::do_config("keybind",   %config::tagskeybinds);
helper::do_config("mousebind", %config::mousebinds);
helper::do_config("attr",      %config::attributes);
helper::do_config("set",       %config::sets);
helper::do_config("rule",      %config::rules);

View Source File:

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. Passing hash argument in Perl is rather cryptic.

sub do_config($\%) {
    my ($command, $ref2hash) = @_;
    my %hash = %$ref2hash;

    # loop over hash
    while(my ($key, $value) = each %hash) { 
        hc("$command $key $value");
        # uncomment to debug in terminal
        # print("$command $key $value \n")

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.

        print("$command $key $value \n")

You can see the debugging result in figure below.

HerbstluftWM: Debug Command

View Source File:

Setting the Tags

Perl read all exported variable from modules.

sub set_tags_with_name() {
    hc("rename default '$tag_names[0]' 2>/dev/null || true");
    for my $index (0 .. $#tag_names) {
        hc("add '$tag_names[$index]'");
        my $key = $tag_keys[$index];
        if ("$key" ne "") {
            hc("keybind Mod4-$key use_index '$index'");
            hc("keybind Mod4-Shift-$key move_index '$index'");

Launch the Panel

Two more functions left, it is do_panel and startup_run.

This two also be easy to do in Perl.

sub do_panel() {
    my $dirname = dirname(__FILE__);
    my $panel   = "$dirname/";
    if (not -x $panel) { $panel = "/etc/xdg/herbstluftwm/"; }

    my $monitor_qx = qx(herbstclient list_monitors | cut -d: -f1);
    my @monitors = split /\n/, $monitor_qx;

    for my $monitor (@monitors) {
        # start it on each monitor
        system("$panel $monitor &");

Run Baby Run

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

sub run() {
    my $command = 'silent new_attr bool my_not_first_autostart';

    my $not_first_qx = qx(herbstclient $command);
    my $exitcode = $?;

    if ($exitcode == 0) {
      # non windowed app
        system("compton &");
        system("dunst &");
        system("parcellite &");
        system("nitrogen --restore &");
        system("mpd &");
      # windowed app
        system("xfce4-terminal &");
        system("sleep 1 && firefox &");
        system("sleep 2 && geany &");
        system("sleep 2 && thunar &");

View Source File:

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:

use warnings;
use strict;

use File::Basename;
use lib dirname(__FILE__);

use gmc;
use helper;
use config;
use startup;

Procedural Part:

# background before wallpaper
system("xsetroot -solid '$color{'blue500'}'");

# Read the manual in $ man herbstluftwm
hc('emit_hook reload');

# gap counter
system("echo 35 > /tmp/herbstluftwm-gap");

# do not repaint until unlock

# standard
hc('keyunbind --all');
hc("mouseunbind --all");
hc("unrule -F");


# do hash config
helper::do_config("keybind",   %config::keybinds);
helper::do_config("keybind",   %config::tagskeybinds);
helper::do_config("mousebind", %config::mousebinds);
helper::do_config("attr",      %config::attributes);
helper::do_config("set",       %config::sets);
helper::do_config("rule",      %config::rules);

# unlock, just to be sure

# launch statusbar panel

# load on startup

View Source File:

Coming up Next

After the Window Manager, comes the Panel.

Happy Configuring.