Preface

Goal: Show the Herbstclient Tag.

Focusing in "herbstclient tag_status". 

HerbstluftWM: Tag Status

This tutorial cover Lemonbar, and in order to use Dzen2, any reader could use the source code in github.


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.


HerbstluftWM Tag Status 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: [ Tag Status Overview ] [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]

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

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


Screenshot

Since window manager is out of topic in this tutorial, I present only panel HerbstluftWM screenshot.

Dzen2

Statusbar: Dzen2 Screenshot

Lemonbar

Statusbar: Lemonbar Screenshot


Directory Structure

Directory Structure has been explained in preface. For both Dzen2 and Lemonbar, the structure are the same. This figure will explain how it looks in BASH script directory.

Statusbar: Directory Structure

Special customization can be done in output script, without changing the whole stuff.


Get Geometry

Let’s have a look at helper.sh in github.

View Source File:

Get Script Argument

The original herbstluftwm panel example, contain statusbar for each monitor. The default is using monitor 0, although you can use other monitor as well.

$ ./panel.sh 0

I do not implement statusbar in multi monitor since I only have my notebook. But I’ll pass the argument anyway for learning purpose.

The original herbstluftwm example, handle this in oneliner fashion.

monitor=${1:-0}

Since we want to port to other language, we need a more clear code. And for that reason, we wrapped them in function in module.

Here it is our code, with similar result. I know BASH is cryptic. But we have good manual in TLDP. BASH do not have a variable return capability, so we have to use global variable $monitor.

helper.sh

# script arguments
function get_monitor() {
    local argument=("$@")
    local num_args=${#argument[@]}

    # ternary operator
    [[ $num_args > 0 ]] && monitor=${argument[0]} || monitor=0
}

And in main code we can call

DIR=$(dirname "$0")
. ${DIR}/helper.sh

get_monitor ${@}
echo $monitor

This will display 0 or else such as 1, depend on the script argument given.

0

Get Monitor Geometry

HerbstluftWM give this little tools to manage monitor geometry by getting monitor rectangle.

$ herbstclient monitor_rect

This will show something similar to this.

0 0 1280 800

HerbstluftWM: Monitor Rectangle

Consider wrap the code into function. And use $geometry as global variable.

helper.sh

function get_geometry() {
    local monitor=$1;
    geometry=( $(herbstclient monitor_rect "$monitor") )
    if [ -z "$geometry" ] ;then
        echo "Invalid monitor $monitor"
        exit 1
    fi
}

Consider call this function from script later. It is a little bit tricky, because the variable $geometry" has space in it, we have to wrap it in "${geometry[@]}".

get_monitor ${@}
get_geometry $monitor
echo ${geometry[@]}

This will produce

0 0 1280 800

Get Panel Geometry

The Panel geometry is completely depend on the user flavor and taste. You can put it, on top, or bottom, or hanging somewhere. You can create gap on both left and right.

Consider this example: helper.sh

function get_bottom_panel_geometry() {
   local panel_height=$1
   shift
   local geometry=("$@")
   
   # geometry has the format X Y W H
     xpos=$(( ${geometry[0]} + 24 ))
     ypos=$(( ${geometry[3]} - $panel_height ))
    width=$(( ${geometry[2]} - 48 ))
   height=$panel_height
}

We are going to use this X Y W H, to get lemonbar parameter.

panel_height=24
get_monitor ${@}
get_geometry $monitor
get_bottom_panel_geometry $panel_height "${geometry[@]}"

echo "Lemonbar geometry: ${width}x${height}+${xpos}+${ypos}"

This will show something similar to this result, depend on your monitor size.

Lemonbar geometry: 1280x24+24+776

Get Lemonbar Parameters

We almost done. This is the last step. We wrap it all inside this function below.

helper.sh

function get_lemon_parameters() {  
    # parameter: function argument
    local monitor=$1
    local panel_height=$2

    # calculate geometry
    get_geometry $monitor
    get_top_panel_geometry $panel_height "${geometry[@]}"
    
    # geometry: -g widthxheight+x+y
    geom_res="${width}x${height}+${xpos}+${ypos}"
    
    # color, with transparency
    local bgcolor="#aa000000"
    local fgcolor="#ffffff"
    
    # XFT: require lemonbar_xft_git 
    local font_takaop="takaopgothic-9"
    local font_bottom="monospace-9"
    local font_symbol="PowerlineSymbols-11"
    local font_awesome="FontAwesome-9"

    # finally
    lemon_parameters="  -g $geom_res -u 2"
    lemon_parameters+=" -B $bgcolor -F $fgcolor" 
    lemon_parameters+=" -f $font_takaop -f $font_awesome -f $font_symbol" 
}

Testing The Parameters

Consider this code 01-testparams.sh. The script call the above function to get lemon parameters.

#!/usr/bin/env bash

# libraries
DIR=$(dirname "$0")
. ${DIR}/helper.sh

# initialize
panel_height=24
get_monitor ${@}

get_lemon_parameters $monitor $panel_height
echo $lemon_parameters 

This will produce output something similar to this result

-g 1280x24+0+0 -u 2 -B #aa000000 -F #ffffff 
-f takaopgothic-9 -f FontAwesome-9 -f PowerlineSymbols-11

Or in Dzen2 version:

-x 0 -y 0 -w 1280 -h 24 -ta l 
-bg #000000 -fg #ffffff -title-name dzentop 
-fn -*-takaopgothic-medium-*-*-*-12-*-*-*-*-*-*-*

View Source File:


Adjusting the Desktop

Since we want to use panel, we have to adjust the desktop gap, giving space at the top and bottom.

$ herbstclient pad 0 24 0 24 0

For more information, do $ man herbsluftclient, and type \pad to search what it means.

In script, it looks like this below.

herbstclient pad $monitor $panel_height 0 $panel_height 0

Color Schemes

Using a simple data structure key-value pairs, we have access to google material color for use with dzen2 or lemonbar. Having a nice pallete to work with, makes our panel more fun.

gmc.sh

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

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

View Source File:


Preparing Output

Let’s have a look at output.sh in github.

View Source File:


Global Variable and Constant

There are ways to define constant in BASH. Constant in PHP may begin with the word readonly.

Mutable State: Segment Variable

The different between interval based and event based is that, with interval based all panel segment are recalculated, while with event based only recalculate the trigerred segment.

In this case, we only have two segment in panel.

  • Tag

  • Title

output.sh In script, we initialize the variable as below

segment_windowtitle=''; # empty string
tags_status=();         # empty array

Each segment buffered. And will be called while rendering the panel.

Global Constant: Tag Name

Assuming that herbstclient tag status only consist of nine number element.

$ herbstclient tag_status
	#1	:2	:3	:4	:5	.6	.7	.8	.9	

We can manage custom tag names, consist of nine string element. We can also freely using unicode string instead of plain one.

output.sh

readonly tag_shows=( "一 ichi" "二 ni" "三 san" "四 shi" 
  "五 go" "六 roku" "七 shichi" "八 hachi" "九 kyū" "十 jū")

Global Constant: Decoration

output.sh Decoration consist lemonbar formatting tag.

readonly separator="%{B-}%{F${color['yellow500']}}|%{B-}%{F-}"

# powerline symbol
readonly right_hard_arrow=""
readonly right_soft_arrow=""
readonly  left_hard_arrow=""
readonly  left_soft_arrow=""

# theme
readonly  pre_icon="%{F${color['yellow500']}}"
readonly post_icon="%{F-}

Segment Variable

As response to herbstclient event idle, these two function set the state of segment variable.

output.sh

function set_tag_value() {
  IFS=$'\t' read -ra tags_status <<< "$(herbstclient tag_status $monitor)"
}

This IFS above turn the tag status string into array of tags for later use.

output.sh

function set_windowtitle() {
    local windowtitle=$1
    local icon="$pre_icon$post_icon"
    
    segment_windowtitle=" $icon %{B-}%{F${color['grey700']}} $windowtitle"
}

We will call these two functions later.


Decorating: Window Title

This is self explanatory. I put separator, just in case you want to add other segment. And put the result in $buffer because BASH can’t return a string.

output.sh

function output_by_title() {
    local text="$segment_windowtitle $separator  "
    buffer=$text
}

Decorating: Tag Status

This transform each plain tag such as .2, to decorated tag names such as 二 ni. Note that it only process one tag. We process all tags in a loop in other function.

This has some parts:

  • Pre Text: Color setting for Main Text (Background, Foreground, Underline). Arrow before the text, only for active tag.

  • Main Text: Tag Name by number, each with their tag state #, +, ., |, !, and each tag has clickable area setting.

  • Post Text: Arrow after the text, only for active tag.

  • Color Reset: %{B-}, %{F-}, %{-u} (Background, Foreground, Underline).

output.sh

function output_by_tag() {
    local    monitor=$1    
    local tag_status=$2    
        
    local  tag_index=${tag_status:1:1}
    local   tag_mark=${tag_status:0:1}
    local   tag_name=${tag_shows[$tag_index - 1]}; # zero based

    # ----- pre tag

    local text_pre=''
    case $tag_mark in
        '#') text_pre+="%{B${color['blue500']}}%{F${color['black']}}"
             text_pre+="%{U${color['white']}}%{+u}$right_hard_arrow"
             text_pre+="%{B${color['blue500']}}%{F${color['white']}}"
             text_pre+="%{U${color['white']}}%{+u}"
        ;;
        '+') text_pre+="%{B${color['yellow500']}}%{F${color['grey400']}}"
        ;;
        ':') text_pre+="%{B-}%{F${color['white']}}"
             text_pre+="%{U${color['red500']}}%{+u}"
        ;;
        '!') text_pre+="%{B${color['red500']}}%{F${color['white']}}"
             text_pre+="%{U${color['white']}}%{+u}"
        ;;
        *)   text_pre+="%{B-}%{F${color['grey600']}}%{-u}"
        ;;
    esac

    # ----- tag by number
    
    # clickable tags
    local text_name=''
    text_name+="%{A:herbstclient focus_monitor \"$monitor\" && "
    text_name+="herbstclient use \"$tag_index\":} $tag_name %{A} "
  
    # non clickable tags
    # local text_name=" $tag_name "
    
    # ----- post tag

    local text_post=''
    if [ $tag_mark = '#' ]
    then        
        text_post+="%{B-}%{F${color['blue500']}}"
        text_post+="%{U${color['red500']}}%{+u}${right_hard_arrow}";
    fi
    
    text_clear='%{B-}%{F-}%{-u}'
     
    buffer="$text_pre$text_name$text_post$text_clear"
}

Combine The Segments

Now it is time to combine all segments to compose one panel. Lemonbar is using %{l} to align left segment, and %{r} to align right segment. All tags processed in a loop.

output.sh

function get_statusbar_text() {
    local monitor=$1
    local text=''

    # draw tags
    text+='%{l}'
    for tag_status in "${tags_status[@]}"
    do
        output_by_tag $monitor $tag_status
        text+=$buffer
    done
    
    # draw window title
    text+='%{r}'
    output_by_title    
    text+=$buffer
    
    buffer=$text
}

Testing The Output

Consider this code 02-testoutput.sh. The script using pipe as feed to lemonbar.

We append -p parameter to make the panel persistent.

#!/usr/bin/env bash

# libraries
DIR=$(dirname "$0")

. ${DIR}/gmc.sh
. ${DIR}/helper.sh
. ${DIR}/output.sh

# process handler

function test_lemon() { 
    monitor=$1
    shift
    parameters=$@
    
    command_out="lemonbar $parameters -p"
    
    {
      # initialize statusbar
      set_tag_value $monitor
      set_windowtitle 'test'

      get_statusbar_text $monitor
      echo $buffer
    } | $command_out

}

# initialize

panel_height=24
get_monitor ${@}
get_lemon_parameters $monitor $panel_height

# test
herbstclient pad $monitor $panel_height 0 $panel_height 0
test_lemon $monitor $lemon_parameters

This will produce a panel on top.

Statusbar: Lemonbar Screenshot

The panel only contain the initialized version of the text. It does not really interact with the HerbstluftWM event.

You can also click the clickable area to see it’s result. It only show text, not executed yet.

herbstclient focus_monitor "0" && herbstclient use "2"
herbstclient focus_monitor "0" && herbstclient use "3"

View Source File:


Coming up Next

It is already a long tutorial. It is time to take a break for a while.

We are going to continue on next tutorial to cover interaction between the script process and HerbstluftWM idle event.


Enjoy the statusbar !