Xmonad and dzen

Xmonad is a tiling window manager for X. It is written in haskell and offers a lot of cool features to play with.

Due to it's design principles and slim codebase xmonad does not have any builtin means for statusbars, although it does support a configuration option, that will set aside unmanaged screen space. That is where dzen kicks in.

Following we'll build, step by step, a script that will collect and display useful information in a dzen statusbar under xmonad.

What is needed?

How will it look like?

What configuration does xmonad need?

Follow the documentation in xmonad's intro and tour guides.

The Script

What does the script provide?

  1. clock showing the time, date and day of week
  2. e-mail notifier
  3. temperature monitor for the cpu
  4. world clock showing the time in different cities
  5. and optionally a weather forecast

We will use the Z Shell for big parts of our scripting for two reasons. First it is my shell of choiche, and second it has very interesting scripting features, that allow to us to program using almost only shell built-ins.

General layout of the script

In order to keep the script easy to maintain and extendable we will split the functionality into smaller parts. The general idea is to have a main part that calls functions, which in turn will provide the requested bit of information.

How all fits together

Sketch of the main part:

# main interval in seconds
INTERVAL=1
 
# intervals for the functions
DATEIVAL=1
MAILIVAL=60
 
while true; do
 
   if[ $DATECOUNTER -ge $DATEIVAL ]; then
     PDATE=$(datefunction)
     DATECOUNTER=0
   fi
 
   if[ $MAILCOUNTER -ge $MAILIVAL ]; then
     PMAIL=$(mailfunction)
     MAILCOUNTER=0
   fi
   .
   .
   .
 
   print $PMAIL $PDATE ...
 
   DATECOUNTER=$((DATECOUNTER+1))
   MAILCOUNTER=$((MAILCOUNTER+1))
 
   sleep $INTERVAL
done

Basically our main part consists of an infinite loop that runs at a constant interval of time. Using distinct counters we can call the information collecting functions at any multiple of that interval.

This comes in handy for things that need shorter or longer update periods than others. E.g. the clock should be updated at least every minute while the weather forecast can have an update interval of 30 minutes or even more.

The functions

Time and date
DATE_FORMAT='%A, %d.%m.%Y %H:%M:%S'
 
fdate() {
    date +$DATE_FORMAT
}

Function fdate will call the standard date command with the $DATE_FORMAT format specifier.:: The default output looks like: Wednesday, 12.09.2007 20:52:42

World clock
# In which global times are we interested?
TIME_ZONES=(Australia/Sydney America/Los_Angeles America/New_York)
 
fgtime() {
    local i
 
    for i in $TIME_ZONES
        { print -n "${i:t}:" $(TZ=$i date +'%H:%M')' ' }
}

Function fgtime walks through all entries in the TIME_ZONES array. For each entry it will literally print the part after / and call the system date command with the corresponding timezone set.:: Typical output looks like: Sydney: 05:03 Los_Angeles: 12:03 New_York: 15:03

CPU temperature
fcputemp() {
   print -n ${(@)$(</proc/acpi/thermal_zone/THRM/temperature)[2,3]}
}

Function fcputemp reads the /proc/acpi/thermal_zone/THRM/temperature file, stores the values read into an array and prints out the second and third values which happen to be the temperature and the temperature unit.:: Typical output: 23 C

Mail notifier

We'll need to keep our mails in maildir style folders. You should dump your old mbox format mailboxes anyways, as maildir is easier to handle and scales better.

fmail() {
    local -A counts; local i
 
    for i in "${MAILDIR:-${HOME}/Mail}"/**/new/*
        { (( counts[${i:h:h:t}]++ )) }
    for i in ${(k)counts}
        { print -n $i: $counts[$i]' ' }
}

Function fmail expects your maildir mailboxes to reside either in $HOME/Mail or in some directory specified with the variable $MAILDIR.

The first for loop walks through all subdirectories of the maildir folder named “new” (this is where maildir keeps new, unread mails) and builds up an associative array (hash) named counts. The keys of the hash are the folder/mailbox names and the values are the message counts.

The second for loop iterates through all hash keys and prints out mailboxname and mailcount.

Typical output: Inbox: 5 xmonad: 8

Weather forecast

We could handle this with pure shell methods, too. Though I decided to take the easy way and use perl and weather.com's services.

Weather.com is one of the biggest weather sites on the net and provides weather data for almost any spot on the globe. They also provide a free service that is perfect if you want to programtically process weather data. Just sign up for free on their site, get your Partner ID and Licence Key and you are ready to go.

In order to keep things really easy we'll use perl's weather.com module.

So here is the perl script that will make up a nice weather forecast:

#!/usr/bin/perl
#
# (c) 2007 by Robert Manea
 
use Weather::Com::Finder;
 
my $PartnerId  = 'XXXXXXXX';
my $LicenseKey = 'YYYYYYYY';
my $iconpath = '/PATH/TO/WEATHER/ICONS';
 
my %weatherargs = (
                                        'partner_id' => $PartnerId,
                                        'license'    => $LicenseKey,
);
 
my $weather_finder = Weather::Com::Finder->new(%weatherargs);
 
# Fill in your location
my $locations = $weather_finder->find('Regensburg, Germany');
 
my $temp_today =
    $locations->[0]->current_conditions()->temperature();
my $desc_today = $locations->[0]->current_conditions()->icon();
 
my $forecast = $locations->[0]->forecast();
my $temp_tomorrow =
    $forecast->day(1)->high();
my $desc_tomorrow = $forecast->day(1)->day->icon();
    $forecast->day(1)->day->conditions;
my $temp_dat =
    $forecast->day(2)->high();
my $desc_dat = $forecast->day(2)->day->icon();
 
my $wvar =
          "^i(${iconpath}/${desc_today}-scaled.xpm)^p(2)${temp_today}".
          "^p(4)^i(${iconpath}/${desc_tomorrow}-scaled.xpm)^p(2)${temp_tomorrow}".
          "^p(4)^i(${iconpath}/${desc_dat}-scaled.xpm)^p(2)${temp_dat}";
 
 
print "$wvar", "\n";

Fill in your Partner Id, License Key, and location.

A packaged version of this script and some icons can be found here.

We'll define a function for our main script that calls the perl weather forecaster:

WEATHER_FORECASTER=/path/to/dzenWeather.pl
 
fweather() {
   $WEATHER_FORECASTER
}

Putting it all together

#!/bin/zsh
#
# xmonad statusline, (c) 2007 by Robert Manea
#
 
# Configuration
DATE_FORMAT='%A, %d.%m.%Y %H:%M:%S'
TIME_ZONES=(Australia/Sydney America/Los_Angeles America/New_York)
WEATHER_FORECASTER=/path/to/dzenWeather.pl
DZEN_ICONPATH=
#MAILDIR=
 
# Main loop interval in seconds
INTERVAL=1
 
# function calling intervals in seconds
DATEIVAL=1
GTIMEIVAL=60
MAILIVAL=60
CPUTEMPIVAL=1
WEATHERIVAL=1800
 
# Functions
fdate() {
    date +$DATE_FORMAT
}
 
fgtime() {
    local i
 
    for i in $TIME_ZONES
        { print -n "${i:t}:" $(TZ=$i date +'%H:%M')' ' }
}
 
fcputemp() {
   print -n ${(@)$(</proc/acpi/thermal_zone/THRM/temperature)[2,3]}
}
 
fmail() {
    local -A counts; local i
 
    for i in "${MAILDIR:-${HOME}/Mail}"/**/new/*
        { (( counts[${i:h:h:t}]++ )) }
    for i in ${(k)counts}
        { print -n $i: $counts[$i]' ' }
}
 
fweather() {
   $WEATHER_FORECASTER
}
 
 
# Main
 
# initialize data
DATECOUNTER=$DATEIVAL;MAILCOUNTER=$MAILIVAL;GTIMECOUNTER=$GTIMEIVAL;CPUTEMPCOUNTER=$CPUTEMPIVAL;WEATHERCOUNTER=$WEATHERIVAL
 
while true; do
   if [ $DATECOUNTER -ge $DATEIVAL ]; then
     PDATE=$(fdate)
     DATECOUNTER=0
   fi
 
   if [ $MAILCOUNTER -ge $MAILIVAL ]; then
     TMAIL=$(fmail)
       if [ $TMAIL ]; then
         PMAIL="^fg(khaki)^i(${DZENICONPATH}/mail.xpm)^p(3)${TMAIL}"
       else
         PMAIL="^fg(grey60)^i(${DZENICONPATH}/envelope.xbm)"
       fi
     MAILCOUNTER=0
   fi
 
   if [ $GTIMECOUNTER -ge $GTIMEIVAL ]; then
     PGTIME=$(fgtime)
     GTIMECOUNTER=0
   fi
 
   if [ $CPUTEMPCOUNTER -ge $CPUTEMPIVAL ]; then
     PCPUTEMP=$(fcputemp)
     CPUTEMPCOUNTER=0
   fi
 
   if [ $WEATHERCOUNTER -ge $WEATHERIVAL ]; then
     PWEATHER=$(fweather)
     WEATHERCOUNTER=0
   fi
 
   # Arrange and print the status line
   print "$PWEATHER $PCPUTEMP $PGTIME $PMAIL ^fg(white)${PDATE}^fg()"
 
   DATECOUNTER=$((DATECOUNTER+1))
   MAILCOUNTER=$((MAILCOUNTER+1))
   GTIMECOUNTER=$((GTIMECOUNTER+1))
   CPUTEMPCOUNTER=$((CPUTEMPCOUNTER+1))
   WEATHERCOUNTER=$((WEATHERCOUNTER+1))
 
   sleep $INTERVAL
done

After saving the script above to a file, let's name it status.sh, we can add an entry to .xinitrc in order to automatically launch it on xmonad startup:

status.sh | dzen2 -ta r -fn '-*-profont-*-*-*-*-11-*-*-*-*-*-iso8859' -bg '#2c2c32' -fg 'grey70' -p -e '' &

You can modify the print line to your likings, e.g. put a small rectangle between the different bits of information: ::

print "${PWEATHER}^p(3)^r(3x3)^p(3)${PGTIME}^p(3)^r(3x3)^p(3)${PMAIL}^p(3)^r(3x3)^p(3)^fg(white)${PDATE}^fg()"

Have fun, Rob.

Discussion

simguin, %2010/%06/%06 %16:%Jun:

This guide is kinda incomplete since you don't explain how to get dzen to work inside xmobar..

Enter your comment (wiki syntax is allowed):
BVTCN
 
dzen/dzen-and-xmonad.txt · Last modified: 2009/04/17 00:58 by rob
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki