/ root / pages / xmonad_status.html

You're using an old link! - Thankfully, you no longer need to specify a nonstandard port (8080) to access my site. You could've used the more standard: http://pbrisbin.com/pages/xmonad_status.html.

Dzen2 Status Bar[s] in XMonad


One of the trickiest things for a new user of XMonad is adding a statusbar. This is mainly because xmonad's statusbar support is so transparent and extensible, that any documentation for setting it up could be completely different from any other. Do you want a dynamicLog? a customLog? xmobar? dzen? one bar? two?

Here I'll outline my method. Two separate dzen2 bars aligned to look like one bar across the top of your screen. The left, fed by an xmonad dynamicLogWithPP to show workspaces (with coloring and ugencyHooks), the current layout, and the current window title. The right, fed by conky -c ~/.dzen_conkyrc to show music, system stats and of course the time.

Many thanks go to moljac and lifeafter2am on the Arch forums. They offered their xmonad.hs's to me and helped get me setup this way.

What it looks like

Full desktop:

XMonad 
        Shot

With an urgencyHook notification:

XMonad Shot 1

To achieve this, we set up a dynamicLog in xmonad.hs and adjust our main function to output to this bar and also spawn our right bar as fed by conky.

Imports and the Main function

The following imports will need to be added to your xmonad.hs in order to get this working the way I'll describe:

import XMonad.Util.Run
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.UrgencyHook
import XMonad.Hooks.ManageDocks
import System.IO

Your main function should look something like this:

-- Main
main = do
  d <- spawnPipe myStatusBar -- the d here...
  spawn myOtherBar

  xmonad $ withUrgencyHook NoUrgencyHook $ defaultConfig
  { terminal           = myTerminal
  , workspaces         = myWorkspaces
  , borderWidth        = myBorderWidth
  , normalBorderColor  = myNormalBorderColor
  , focusedBorderColor = myFocusedBorderColor
  , layoutHook         = myLayout
  , manageHook         = myManageHook
  , keys               = myKeys
  , logHook            = myLogHook d -- ... must match the d here
  }

The portions relevant to this tutorial are spawnPipe myStatusBar which feeds xmonad's output to our first status bar (yet undefined), spawn myOtherBar which will launch our conky dzen (also yet undefined) and the logHook function which will determine what's outputted by xmonad.

d, myStatusBar, and myOtherBar are variable names that can be whatever you want, just be consistent when you reference them.

Your LogHook

Your logHook will setup the output of workspaces, layouts, and titles to dzen2. You can determine formatting, padding, shortening, etc. Directly below this function is where I like to define the my*Bar variables just to keep it in the same area of my config.

Here's a commented version of myLogHook which, hopefully, is illustrative enough to not warrant further explanation.

-- Status Bars
myLogHook h = dynamicLogWithPP $ defaultPP -- the h here...

  -- display current workspace as darkgrey on light grey (opposite of default colors)
  { ppCurrent         = dzenColor "#303030" "#909090" . pad

  -- display other workspaces which contain windows as a brighter grey
  , ppHidden          = dzenColor "#909090" "" . pad

  -- display other workspaces with no windows as a normal grey
  , ppHiddenNoWindows = dzenColor "#606060" "" . pad

  -- display the current layout as a brighter grey
  , ppLayout          = dzenColor "#909090" "" . pad

  -- if a window on a hidden workspace needs my attention, wrap its workspace with bright red braces
  , ppUrgent          = wrap (dzenColor "#ff0000" "" "{") (dzenColor "#ff0000" "" "}") . pad

  -- show the current window's title as a brighter grey and bracketed, also shorten if it goes over 40 characters
  , ppTitle           = wrap "^fg(#909090)[ " " ]^fg()" . shorten 40 

  -- no separator between workspaces
  , ppWsSep           = ""
  
  -- put a few spaces between each object
  , ppSep             = "  "

  , ppOutput          = hPutStrLn h -- ... must match the h here
  }

-- sets up dzen options for two bars displayed as one spanning my 1920 px wide monitor
myStatusBar = "dzen2 -p -ta l -fn Verdana-8 -x 0 -y 0 -w 700 -h 15 -fg '#606060' -bg '#303030' -e 'onexit=ungrabmouse'"
myOtherBar  = "conky -c ~/.dzen_conkyrc | dzen2 -p -ta r -fn Verdana-8 -x 700 -y 0 -w 1220 -h 15 -fg '#606060' -bg '#303030' -e 'onexit=ungrabmouse'"

Lastly, you'll need to add two little things to make sure you leave a gap for the new statusbar:

-- add avoidStruts to your layoutHook like so
myLayout = avoidStruts $ Tall ||| Wide ||| Full ...

-- add manageDocks to your managehook
myManageHook = composeAll
  [ className =? "Mplayer" --> doFloat
  , className =? "Zenity"  --> doFloat 
  ] <+> manageDocks

Things to know

The above expects a 1920 px wide monitor, it expects an xft-capable dzen2, and it expects a file at ~/.dzen_conkyrc to populate myOtherBar (sample available here).

Also, the default xmonad --restart keybinding will leave dzen and conky zombie processes. My live xmonad.hs has a M-q keybinding to get around this; feel free to steal it.

Happy haskelling!

Update

So I think I've come up with an interesting way to do this whole dzen thing. One that leverages the fact that our WM's config is written in our WM's language It involves defining a function that builds dzen command strings, then passing in some defined geometries based on monitor size, etc.

First step, setup a "theme config" in your xmonad.hs like so:

-- variables for use in makeDzen, and mylogHook
myDzenFont   = "Verdana-8"       -- xft enabled dzen req'd
conkyFile    = "~/.dzen_conkyrc" -- populates the right status bar

colorWhite   = "#ffffff"         -- white
colorRed     = "#ff0000"         -- bright red
colorGrey0   = "#303030"         -- grey
colorGrey1   = "#606060"         -- light grey
colorGrey2   = "#909090"         -- lighter grey

barHeight    = 17
monitorWidth = 1920              -- two bars span this width
leftBarWidth = 700               -- the right bar will span difference

Now you've got some named values to use in your other functions.

And here's a short function I wrote to accept dzen geometry and text alignment and return the dzen command string. By using the variables defined above, we can now do something like this:

-- custom function to build dzen commands
makeDzen x y w h a = "dzen2 -p" ++
                     " -ta "    ++ a ++
                     " -x "     ++ show x ++
                     " -y "     ++ show y ++
                     " -w "     ++ show w ++
                     " -h "     ++ show h ++
                     " -fn '"   ++ myDzenFont ++ "'" ++ 
                     " -fg '"   ++ colorGrey1 ++ "'" ++
                     " -bg '"   ++ colorGrey0 ++ "' -e ''"

-- define the bars
myLeftBar  = makeDzen 0 0 leftBarWidth barHeight "l"
myRightBar = "conky -c " ++ conkyFile ++ " | " ++ makeDzen leftBarWidth 0 (monitorWidth - leftBarWidth) barHeight "r"

And we can even use the color configs in an updated version of myLogHook. Notice some other functions in the where clause:

-- LogHook
myLogHook h = (dynamicLogWithPP $ defaultPP 
  { ppCurrent         = dzenColor colorGrey0 colorGrey2 . pad
  , ppHidden          = dzenFG    colorGrey2 . pad
  , ppHiddenNoWindows = dzenFG    colorGrey1 . pad
  , ppLayout          = dzenFG    colorGrey1 . pad
  , ppUrgent          = myWrap    colorRed   "{"  "}"  . pad
  , ppTitle           = myWrap    colorGrey2 "[ " " ]" . shorten 40 
  , ppWsSep           = ""
  , ppSep             = " "
  , ppOutput          = hPutStrLn h
 }) >> updatePointer (Relative 0.95 0.95)

  where

    dzenFG c     = dzenColor c ""
    myWrap c l r = wrap (dzenFG c l) (dzenFG c r)

Ain't haskell a great way to configure a WM?

Update 2

Well, continuing with the above changes, I've taken this a bit further. I've been reading Real World Haskell lately and was looking for an outlet to try and make everything in that book a bit more relevant.

I realized I could define a data type to represent a Dzen. I could give it constructors like x offset, y offset, font, colors, etc. Then I read about writing a custom instance of Show which tells haskell how to print a String representation of this new data type.

Note - Some might say this overcomplicates a pretty straightforward task. I'm not touting it as some importable module that people should start using as a dzen solution in XMonad. I just find it a really interesting approach. You should probably just declare a simple string for your dzen commands and KISS. That said, I think my memory usage has actually gone down slightly since using this approach ;).

Here are my data and instance functions for my new Dzen type:

data TextAlign = LeftAlign | RightAlign | Centered

instance Show TextAlign where
  show LeftAlign  = "l"
  show RightAlign = "r"
  show Centered   = "c"

-- see /usr/share/doc/dzen2/README
data DzenConf = DzenConf 
  { x_position :: Int       -- x position
  , y_position :: Int       -- y position
  , width      :: Int       -- width
  , height     :: Int       -- line height
  , alignment  :: TextAlign -- alignment of title window
  , font       :: String    -- font
  , fg_color   :: String    -- foreground color
  , bg_color   :: String    -- background color
  , exec       :: String    -- exec flags, ex: "onstart=lower"
  , input      :: String    -- stdin, ex: "conky -c ~/.dzen_conkyrc"
  , addargs    :: [String]  -- additional arguments, ex: ["-p", "-tw", "5"]
  }

instance Show DzenConf where
  show d = unwords $ addstdin (input d)
    [ "dzen2"
    , "-fn", quote $ font       d
    , "-fg", quote $ fg_color   d
    , "-bg", quote $ bg_color   d
    , "-ta", show  $ alignment  d
    , "-x" , show  $ x_position d
    , "-y" , show  $ y_position d
    , "-w" , show  $ width      d
    , "-h" , show  $ height     d
    , "-e" , quote $ exec       d
    ] ++ addargs d

    where
      -- only add the "|" if stdin is defined
      addstdin s xs = if null s then xs else s:"|":xs
      quote s       = "'" ++ s ++ "'"

I know... so wtf good is this anyway?

--
-- well, now you can do this:
--

main = do
  spawn . show $ myRightBar
  d <- spawnPipe . show $ myLeftBar

  xmonad $ defaultConfig
    { ...
    , logHook = myLogHook d
    , ...
    }

--
-- where the bars are defined like so:
--

-- fully define the left bar
myLeftBar :: DzenConf
myLeftBar = DzenConf
  { x_position  = 0
  , y_position  = 0
  , width       = leftBarWidth
  , height      = barHeight
  , alignment   = LeftAlign
  , font        = myFont
  , fg_color    = colorFG
  , bg_color    = colorBG
  , exec        = "onstart=lower"
  , input       = []
  , addargs     = ["-p"]
  }

-- only define what's different on the right bar
myRightBar :: DzenConf
myRightBar = myLeftBar
  { x_position = leftBarWidth
  , width      = rightBarWidth
  , alignment  = RightAlign
  , input      = "conky -c ~/.dzen_conkyrc"
  }

All of this within our window manager's config file. Call me a nerd, but I think that's crazy.

Comments

on Sat, 10 Jul 2010 12:50:09 -0400, nwt wrote:

Thanks so much. Do you know of a way to make conky display e. g. die CPU usage with constant width?

It's quite annoying to so often see the bar hopping a bit when the percentage number changes from one digit to two digits.

on Sun, 11 Jul 2010 11:40:13 -0400, me wrote:

Well, the text is rendered by dzen entirely, so all formatting/alignment is done there.

On my system (Arch), the dzen README is located in /usr/share/doc/dzen2; it's also available online. You should check out the Positioning section.

You could use ^pa(+-X) to define absolute horizontal positions for all your elements.

Good luck.





pbrisbin dot com 2010