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.
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.
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.
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.
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
# 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
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
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
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 }
#!/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
This guide is kinda incomplete since you don't explain how to get dzen to work inside xmobar..