Preface
Goal: Show the Herbstclient Tag.
Focusing in "herbstclient tag_status".
This tutorial cover Lemonbar, and in order to use Dzen2, any reader could use the source code in github.
Table of Content
-
Preface: Table of Content
-
2: Get Geometry
Reference
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.
-
Lemonbar: gitlab.com/…/dotfiles/…/perl/
Screenshot
Since window manager is out of topic in this tutorial, I present only panel HerbstluftWM screenshot.
Dzen2
Lemonbar
1: 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 Perl script
directory.
Special customization can be done in output script, without changing the whole stuff.
2: Get Geometry
Let’s have a look at helper.pm
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.pm 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. Here it is our code in Perl.
helper.pm
# script arguments
sub get_monitor {
my @arguments = @_;
my $num_args = $#arguments;
# ternary operator
my $monitor = ($num_args > 0) ? $arguments[0] : 0;
return $monitor;
}
And in main code we can call
use File::Basename;
use lib dirname(__FILE__);
use helper;
my $monitor = helper::get_monitor(@ARGV);
print $monitor."\n";
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
Consider wrap the code into function. And get an array as function return.
helper.pm
sub get_geometry {
my $monitor = shift;
my $geometry_qx = qx(herbstclient monitor_rect "$monitor");
if ($geometry_qx eq "") {
print "Invalid monitor $monitor\n";
exit 1
}
my @geometry = split / /, $geometry_qx;
return @geometry;
}
Consider call this function from script later.
To print array in Perl,
we just have to wrap it in "@geometry"
.
my $panel_height = 24;
my $monitor = helper::get_monitor(@ARGV);
my @geometry = helper::get_geometry($monitor);
print "@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.pm
sub get_bottom_panel_geometry {
my $height = shift;
my @geometry = @_;
# geometry has the format X Y W H
return ($geometry[0] + 24, $geometry[3] - $height,
$geometry[2] - 48, $height);
}
We are going to use this X Y W H
,
to get lemonbar parameter.
my $panel_height = 24;
my $monitor = helper::get_monitor(@ARGV);
my @geometry = helper::get_geometry($monitor);
my ($xpos, $ypos, $width, $height) =
helper::get_bottom_panel_geometry($panel_height, @geometry);
print "Lemonbar geometry: ${width}x${height}+${xpos}+${ypos}\n";
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.pm
sub get_lemon_parameters {
# parameter: function argument
my $monitor = shift;
my $panel_height = shift;
# calculate geometry
my @geometry = get_geometry($monitor);
my ($xpos, $ypos, $width, $height) =
get_top_panel_geometry($panel_height, @geometry);
# geometry: -g widthxheight+x+y
my $geom_res = "${width}x${height}+${xpos}+${ypos}";
# color, with transparency
my $bgcolor = '#aa000000';
my $fgcolor = '#ffffff';
# XFT: require lemonbar_xft_git
my $font_takaop = "takaopgothic-9";
my $font_bottom = "monospace-9";
my $font_symbol = "PowerlineSymbols-11";
my $font_awesome = "FontAwesome-9";
# finally
my $parameters = " -g $geom_res -u 2"
. " -B $bgcolor -F $fgcolor"
. " -f $font_takaop -f $font_awesome -f $font_symbol";
return $parameters;
}
3: Testing The Parameters
Consider this code 01-testparams.pl
.
The script call the above function to get lemon parameters.
#!/usr/bin/perl
use warnings;
use strict;
use File::Basename;
use lib dirname(__FILE__);
use helper;
# initialize
my $panel_height = 24;
my $monitor = helper::get_monitor(@ARGV);
my $lemon_parameters = helper::get_lemon_parameters(
$monitor, $panel_height);
print $lemon_parameters."\n";
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:
4: 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.
system("herbstclient pad $monitor $panel_height 0 $panel_height 0");
5: 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.pm
our %color = (
'white' => '#ffffff',
'black' => '#000000',
'grey50' => '#fafafa',
'grey100' => '#f5f5f5'
);
View Source File:
-
Lemonbar: gitlab.com/…/dotfiles/…/perl/gmc.pm
6: Preparing Output
Let’s have a look at output.pm
in github.
View Source File:
7: Global Variable and Constant
There are a several ways to define constant in Perl.
Constant in Perl may begin with the word use const
.
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.pm
In script, we initialize the variable as below
my $segment_windowtitle = ''; # empty string
my @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.pm
use constant TAG_SHOWS => ['一 ichi', '二 ni', '三 san', '四 shi',
'五 go', '六 roku', '七 shichi', '八 hachi', '九 kyū', '十 jū'];
Global Constant: Decoration
output.pm
Decoration consist lemonbar formatting tag.
use gmc;
# decoration
use constant SEPARATOR => "%{B-}%{F$color{'yellow500'}}|%{B-}%{F-}";
# Powerline Symbol
use constant RIGHT_HARD_ARROW => "";
use constant RIGHT_SOFT_ARROW => "";
use constant LEFT_HARD_ARROW => "";
use constant LEFT_SOFT_ARROW => "";
# theme
use constant PRE_ICON => "%{F$color{'yellow500'}}";
use constant POST_ICON => "%{F-}";
8: Segment Variable
As response to herbstclient event idle, these two function set the state of segment variable.
output.pm
sub set_tag_value {
my $monitor = shift;
my $tag_status_qx = qx(herbstclient tag_status $monitor);
$tag_status_qx =~ s/^\s+|\s+$//g;
@tags_status = split(/\t/, $tag_status_qx);
}
This function above turn the tag status string into array of tags for later use.
output.pm
sub set_windowtitle {
my $windowtitle = shift;
my $icon = PRE_ICON."".POST_ICON;
$segment_windowtitle = " $icon "
. "%{B-}%{F$color{'grey700'}} $windowtitle";
}
We will call these two functions later.
9: Decorating: Window Title
This is self explanatory. I put separator, just in case you want to add other segment. And then returning string as result.
output.pm
sub output_by_title {
my $text = "$segment_windowtitle ".SEPARATOR." ";
return $text;
}
10: 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.pm
sub output_by_tag {
my $monitor = shift;
my $tag_status = shift;
my $tag_index = substr($tag_status, 1, 1);
my $tag_mark = substr($tag_status, 0, 1);
my $tag_name = TAG_SHOWS->[$tag_index - 1]; # zero based
# ----- pre tag
my $text_pre = '';
if ($tag_mark eq '#') {
$text_pre = "%{B$color{'blue500'}}%{F$color{'black'}}"
. "%{U$color{'white'}}%{+u}".RIGHT_HARD_ARROW
. "%{B$color{'blue500'}}%{F$color{'white'}}"
. "%{U$color{'white'}}%{+u}";
} elsif ($tag_mark eq '+') {
$text_pre = "%{B$color{'yellow500'}}%{F$color{'grey400'}}";
} elsif ($tag_mark eq ':') {
$text_pre = "%{B-}%{F$color{'white'}}"
. "%{U$color{'red500'}}%{+u}";
} elsif ($tag_mark eq '!') {
$text_pre = "%{B$color{'red500'}}%{F$color{'white'}}"
. "%{U$color{'white'}}%{+u}";
} else {
$text_pre = "%{B-}%{F$color{'grey600'}}%{-u}";
}
# ----- tag by number
# clickable tags
my $text_name = "%{A:herbstclient focus_monitor \"$monitor\" && "
. "herbstclient use \"$tag_index\":} $tag_name %{A} ";
# non clickable tags
# my $text_name = " $tag_name ";
# ----- post tag
my $text_post = "";
if ($tag_mark eq '#') {
$text_post = "%{B-}%{F$color{'blue500'}}"
. "%{U$color{'red500'}}%{+u}"
. RIGHT_HARD_ARROW;
}
my $text_clear = '%{B-}%{F-}%{-u}';
return $text_pre . $text_name . $text_post . $text_clear;
}
11: 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.pm
sub get_statusbar_text {
my $monitor = shift;
my $text = '';
# draw tags
$text .= '%{l}';
foreach my $tag_status (@tags_status) {
$text .= output_by_tag($monitor, $tag_status);
}
# draw window title
$text .= '%{r}';
$text .= output_by_title();
return $text;
}
12: Testing The Output
Consider this code 02-testoutput.pl
.
The script using pipe as feed to lemonbar.
We append -p
parameter to make the panel persistent.
#!/usr/bin/perl
use warnings;
use strict;
use File::Basename;
use lib dirname(__FILE__);
use helper;
# process handler
sub test_lemon {
use IO::Pipe;
use output;
my $monitor = shift;
my $parameters = shift;
my $pipe_out = IO::Pipe->new();
my $command = "lemonbar $parameters -p";
my $handle = $pipe_out->writer($command);
# initialize statusbar
output::set_tag_value($monitor);
output::set_windowtitle('test');
my $text = output::get_statusbar_text($monitor);
print $pipe_out $text."\n";
flush $pipe_out;
$pipe_out->close();
}
# initialize
my $panel_height = 24;
my $monitor = helper::get_monitor(@ARGV);
my $lemon_parameters = helper::get_lemon_parameters(
$monitor, $panel_height);
# test
system("herbstclient pad $monitor $panel_height 0 $panel_height 0");
test_lemon($monitor, $lemon_parameters);
This will produce a panel on top.
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 !