I'll guide you through the different steps I took to install a working desktop environment in Debian using xmonad, conky and dzen2.

The final result of this configuration should look like this:

xmonad with dzen2 and conky
xmonad with dzen2 and conky

Installing the prerequisites

The first thing you should do is install the Haskell compiler and interpreter (I'm assuming you're using Debian Sid). To do that, use your beloved aptitude package manager:

sudo aptitude install ghc6

To install Xmonad I recommend using the Haskell package manager cabal:

sudo aptitude install cabal

After cabal is installed, you'll have to update the package list by running cabal update. However, when doing this, cabal might tell you that there is newer version of the package manager to be installed. Follow the on screen instructions and upgrade to the latest cabal:

cabal install cabal-install

This will install the latest cabal into your home directory. From now on, make sure that /home/<your_username>/.cabal/bin is on the path to be using this newly installed cabal build. Run cabal update again to have the package list updated.

Installing Xmonad

Installing xmonad is as easy as running cabal install xmonad. This will try to download, compile and install xmonad and all the prerequisites. However, you may have some compile errors. This is because to compile Xmonad you need to have some development libraries installed. Typically you'll need libx11-dev, libxinerama-dev and libxext-dev, among others. To install those dependencies just look for them in the apt respositories and install them using aptitude or apt-get:

sudo aptitude install libx11-dev libxinerama-dev libxext-dev

After installing xmonad, is recommended you also install xmonad-contrib, which includes some extra functionalities to Xmonad tant we will find useful later. Note that to install xmonad-contrib you'll need to install the xft development libraries, too:

sudo aptitude install libxft-dev
cabal install xmonad-contrib

Ok, with this steps you already have a fully functional xmonad version installed and ready to be run. We will need to add some eye candy to it now, however, to make it more usable. If you want to have it a try now, though, to see if everything works as expected, just edit your ~/.xsession file to make it look like this:

exec xmonad

And now just start your X with startx (or make your display manager to use the default session). You should see... nothing. If everything worked fine, an empty X screen will be shown. Type Alt+Shift+Enter and an xterm should appear occupying the entire screen.

Installing conky and dzen2

Again, you can install both those packages from source or via the package manager. I'll show you how to install the latest versions from source.

Installing conky

Download the source tarball (at the time of the writing 1.8.1 was the latest version) from sourceforge and decompress it wherever you want:

tar xvzf conky-1.8.1.tar.gz

Now run the typical commands to configure and install the package:

./configure
make
sudo make install

Note that you might need to install additional development libraries to configure and compile conky with all its bells and whistles, like libxdamage-dev, libmpd-dev and others. Do so with aptitude.

Installing dzen2

Download the latest source tarball (0.8.5 at the time of writing) from the official home page and uncompress it wherever you want:

tar xvzf dzen2-latest.tar.gz

And then just compile and install it (have a look at the README if you want to add xft and xinerama support to it):

make
sudo make install

The funny part begins: configuring xmonad and putting it all together

First of all, let me clarify some concepts. We will use xmonad as the Window Manager, dzen2 as the status bar and conky as a system data provider. Some people prefer to use xmobar instead of dzen2 + conky.

First of all, let me give credits to the resources I've used to craft xmonad the way I wanted:

  • kveras at Github gave me the overall skeleton of the configuration. Make sure to check his configs repository to have a look at his configs.
  • Haskell Wiki xmonad Config Archive, which provided some insights.

Xmonad configuration is done via a file in the .xmonad user folder called xmonad.hs. This file is Haskell code, and having a slight understanding of the Haskell syntax is recommended, although I managed to make this one without really having much Haskell knowledge.

Basically when you configure xmonad what you're really doing is compiling it with the options you pass to it, via that configuration file. There are several things (AFAIK) that can be configured:

  • The default terminal
  • The workspaces
  • The keybindings
  • The mod key
  • The Layout Hook
  • The Manage Hook
  • The Log Hook
  • Window border options
The way everything is tied together is done when calling the main method of the configuration file:

main = do
    xmonad $ defaultConfig
      { terminal            = myTerminal
      , workspaces          = myWorkspaces
      , keys                = keys
      , modMask             = modMask
      , layoutHook          = layoutHook
      , manageHook          = manageHook
      , logHook             = myLogHook
      , normalBorderColor   = colorNormalBorder
      , focusedBorderColor  = colorFocusedBorder
      , borderWidth         = 2
}

In that main method you use the defaultConfig and pass a list of the different parameters you want to customize. I'll explain briefly every configuration aspect.

The default terminal

xmonad has a special function that is intended to be used to spawn a terminal. As a daily Linux programmer this is probably very useful. With the terminal parameter, then, you can specify the program that xmonad will launch when this action is invoked. Assigning this variable to the program name (assuming it's in the path) is enough for it to work:

myTerminal = "gnome-terminal"

The workspaces

xmonad allows you to group your windows into workspaces. The concept of workspace is very similar to the virtual desktops we have in Gnome, or the Spaces we have in Mac OS X. Each window will belong to a workspace, each workspace has a name and it's own configuration, and one and only one workspace can be active (and shown) on one screen at the same time.

In the workspaces variable, we'll tell xmonad our default list of workspaces we want to work with, in a form of a string list. In my configuration I've preceded every workspace name with a number:

myWorkspaces = ["1:main","2:web","3:vim","4:chat","5:music", "6:gimp"]

The keybindings

Key binding is a crucial part of the configuration. One of the goals of xmonad is being able to do all window management without having to use the mouse, so your hands are always on the keyboard. Choosing a good key layout is then very important, and having a look at all the things you can do in xmonad is important, too, so you can check what you think it may be more useful to you. The key binding configuration is a bit strange in haskell, and I don't entirely understand it myself, but I'll try to guide you through the basics. Basically you have 3 modifiers: the windows key, ctrl, shift and alt (in fact I think you can deifne any key as a valid modifier, but those are the most used). You then configure pair of key-action, telling xmonad what should it do when certain keys are pressed. Those pairs have this syntax:

((modMask .|. shiftMask,      xK_Return   ), spawn $ XMonad.terminal conf)

The first part tells xmonad the key press, while the second the action to be performed. The key press is expressed in the form of modifier-key. The modifiers can be chained, so you can assign some action when more than a modifier is pressed. In the example, we have modMask .|. shiftMask, xK_Return. This line means press the modKey and the shift key, and then the return key. As you can see, the operator to chain modifiers is .|.. When you want a key binding without modifier, you put a 0 instead. For instance, to bind a key to print the screen, you could use something like this: ((0, xK_Print), spawn "scrot -e 'mv $f ~/screenshots/'"). This command tells xmonad to run the scrot program when the Print Screen key is pressed.

Some xmonad actions require a special attention: those used to manipulate the windows and the workspaces. I'll tell you the most used ones (at least in the default configurations).

  • sendMessage nextLayout is used to tell xmonad to switch to the next layout in the current workspace
  • setLayout $ XMonad.layoutHook conf will reset the current layout to the default one of the current workspace
  • sendMessage ToggleStruts is used to switch between struts mode on and off. If I'm not mistaken, struts are margins put between windows (which by default are not shown)
  • windows W.focusDown sets the focused window to the next window in the stack, allowing you to navigate the focus through different windows
  • windows W.focusUp does the same as above but in the reverse order
  • windows W.swapDown will swap the current window to the space occupied by the next window on the stack, useful to move windows between the different positions on the workspace
  • windows W.swapUp, again, does the same as above in the reverse order
  • windows W.swapMaster is very useful: it will put the focused window into the main space of the current layout (which is usually bigger than the other frames)
  • sendMessage Shrink is used to shrink the size of the main frame size
  • sendMessage Expand, on the other side, is used to expand the main frame size
  • sendMessage (IncMasterN number) will increase the number of panes of the master pane by the given number (which can be a positive or negative integer)
  • nextWS will switch to the next workspace
  • prevWS will switch to the previous workspace
  • shiftToNext will move the focused windo to the next workspace
  • shiftToPrev will move the focused window to the previous workspace
  • W.shift workspace will put the focused window to the selected workspace
  • W.view workspace will put the selected workspace as the workspace of the screen

Here's how the entire keys variable is assigned:

keys' conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
    [ ((modMask,                    xK_p        ), runOrRaisePrompt largeXPConfig)
    , ((modMask .|. shiftMask,      xK_Return   ), spawn $ XMonad.terminal conf)
    , ((modMask,                    xK_F2       ), spawn "gmrun")
    , ((modMask .|. shiftMask,      xK_c        ), kill)
    , ((modMask .|. shiftMask,      xK_l        ), spawn "slock")
    -- Programs
    , ((0,                          xK_Print    ), spawn "scrot -e 'mv $f ~/screenshots/'")
    , ((modMask,		            xK_o        ), spawn "chromium-browser")
    , ((modMask,                    xK_m        ), spawn "nautilus --no-desktop --browser")
    -- Media Keys
    , ((0,                          0x1008ff12  ), spawn "amixer -q sset Headphone toggle")        -- XF86AudioMute
    , ((0,                          0x1008ff11  ), spawn "amixer -q sset Headphone 5%-")   -- XF86AudioLowerVolume
    , ((0,                          0x1008ff13  ), spawn "amixer -q sset Headphone 5%+")   -- XF86AudioRaiseVolume
    , ((0,                          0x1008ff14  ), spawn "rhythmbox-client --play-pause")
    , ((0,                          0x1008ff17  ), spawn "rhythmbox-client --next")
    , ((0,                          0x1008ff16  ), spawn "rhythmbox-client --previous")

    -- layouts
    , ((modMask,                    xK_space    ), sendMessage NextLayout)
    , ((modMask .|. shiftMask,      xK_space    ), setLayout $ XMonad.layoutHook conf)          -- reset layout on current desktop to default
    , ((modMask,                    xK_b        ), sendMessage ToggleStruts)
    , ((modMask,                    xK_n        ), refresh)
    , ((modMask,                    xK_Tab      ), windows W.focusDown)                         -- move focus to next window
    , ((modMask,                    xK_j        ), windows W.focusDown)
    , ((modMask,                    xK_k        ), windows W.focusUp  )
    , ((modMask .|. shiftMask,      xK_j        ), windows W.swapDown)                          -- swap the focused window with the next window
    , ((modMask .|. shiftMask,      xK_k        ), windows W.swapUp)                            -- swap the focused window with the previous window
    , ((modMask,                    xK_Return   ), windows W.swapMaster)
    , ((modMask,                    xK_t        ), withFocused $ windows . W.sink)              -- Push window back into tiling
    , ((modMask,                    xK_h        ), sendMessage Shrink)                          -- %! Shrink a master area
    , ((modMask,                    xK_l        ), sendMessage Expand)                          -- %! Expand a master area
    , ((modMask,                    xK_comma    ), sendMessage (IncMasterN 1))
    , ((modMask,                    xK_period   ), sendMessage (IncMasterN (-1)))


    -- workspaces
    , ((modMask .|. controlMask,   xK_Right     ), nextWS)
    , ((modMask .|. shiftMask,     xK_Right     ), shiftToNext)
    , ((modMask .|. controlMask,   xK_Left      ), prevWS)
    , ((modMask .|. shiftMask,     xK_Left      ), shiftToPrev)
    
    -- quit, or restart
    , ((modMask .|. shiftMask,      xK_q        ), io (exitWith ExitSuccess))
    , ((modMask,                    xK_q        ), spawn "killall conky dzen2 && /home/brafales/.cabal/bin/xmonad --recompile && /home/brafales/.cabal/bin/xmonad --restart")
    ]
    ++
    -- mod-[1..9] %! Switch to workspace N
    -- mod-shift-[1..9] %! Move client to workspace N
    [((m .|. modMask, k), windows $ f i)
        | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
        , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
    ++

    --
    -- mod-{w,e,r}, Switch to physical/Xinerama screens 1, 2, or 3
    -- mod-shift-{w,e,r}, Move client to screen 1, 2, or 3
    --
    [((m .|. modMask, key), screenWorkspace sc >>= flip whenJust (windows . f))
        | (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
        , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]

The mod key

The mod key is the default key xmonad uses to prepend all the keybindings (usually). Some users configure it to alt, others to ctrl, others to the windows key. It's up to you. In my config I have it configured to the windows key:

modMask' :: KeyMask
modMask' = mod4Mask

The Layout Hook

The layout hook is used to manage the layouts of the workspaces. xmonad is very configurable regarding the layouts you can use. By default, the windows are tiled by using half the screen (vertically) for the main frame, and equally sized horizontal windows for the other frames, but you can set it up however you want.

The syntax to configure the layouts is simple, you concatenate which layout you want to use for each workspace, leaving the last one as the default one:

layoutHook'  =  onWorkspaces ["1:main","5:music"] customLayout $ 
                onWorkspaces ["6:gimp"] gimpLayout $ 
                onWorkspaces ["4:chat"] imLayout $
                customLayout2

What's not so easy is to configure those variables we used (customLayout, gimpLayout, imLayout, etc.), but you get the idea: you can configure each workspace to have its own layout, which is very useful. Here's my customLayout variable so you can see how a layout is configured:

customLayout = avoidStruts $ tiled ||| Mirror tiled ||| Full ||| simpleFloat
  where
    tiled   = ResizableTall 1 (2/100) (1/2) []

What I'm doing here is using avoidstruts to have a layout without margins between windows, and then passing a list of the different available layouts for the workspace, separated by the ||| operator. This means that the default layout will be tiled, then Mirror tiled, etc. In this case, what Mirror does is swap the type of split, from vertical to horizontal. After that, I tell the config what I mean by tiled, in this case ResizableTall 1 (2/200) (1/2) []. What this means is to use the layout ResizableTall with 1 master window, the size of each resizing step, the width of the master panel and the slaves window height. You can check the full documentation for ResizableTall in the XMonad.Layout.ResizableTile documentation page.

The same way we've used ResizableTall for this customlayout, lots of extensions exist in xmonad that do all sorts of layout positioning.

The Manage Hook

The manage hook handles how new windows are positioned by default. xmonad allows you to put some windows into their own workspace, or in its own mode. This way, for example, you can have all Firefox windows to open in the "2:web" workspace, or have special program windows not to be tiled, but floated instead.

While I still don't fully understand all the configuration options, this is a typical line to configure a manage hook:

[className    =? c            --> doShift  "1:main"   |   c   <- myDev    ] -- move dev to main

What we're doing with this line is tell xmonad that all windows which has a classname included in the myDev list must be shifted to the "1:main" workspace. I don't know all the criteria you can use to identify a window, but the most used ones are the className and the role. You can check those X properties using the program xprop and clicking on the desired window.

Here's my whole manage hook configuration:

manageHook' :: ManageHook
manageHook' = (composeAll . concat $
    [ [resource     =? r            --> doIgnore            |   r   <- myIgnores] -- ignore desktop
    , [className    =? c            --> doShift  "1:main"   |   c   <- myDev    ] -- move dev to main
    , [className    =? c            --> doShift  "2:web"    |   c   <- myWebs   ] -- move webs to main
    , [className    =? c            --> doShift  "3:vim"    |   c   <- myVim    ] -- move webs to main
    , [className    =? c            --> doShift	 "4:chat"   |   c   <- myChat   ] -- move chat to chat
    , [className    =? c            --> doShift  "5:music"  |   c   <- myMusic  ] -- move music to music
    , [className    =? c            --> doShift  "6:gimp"   |   c   <- myGimp   ] -- move img to div
    , [className    =? c            --> doCenterFloat       |   c   <- myFloats ] -- float my floats
    , [name         =? n            --> doCenterFloat       |   n   <- myNames  ] -- float my names
    , [isFullscreen                 --> myDoFullFloat                           ]
    ]) 

    where

        role      = stringProperty "WM_WINDOW_ROLE"
        name      = stringProperty "WM_NAME"

        -- classnames
        myFloats  = ["Smplayer","MPlayer","VirtualBox","Xmessage","XFontSel","Downloads","Nm-connection-editor"]
        myWebs    = ["Firefox","Google-chrome","Chromium", "Chromium-browser"]
        myMovie   = ["Boxee","Trine"]
        myMusic	  = ["Rhythmbox","Spotify"]
        myChat	  = ["Pidgin","Buddy List"]
        myGimp	  = ["Gimp"]
        myDev	  = ["gnome-terminal"]
        myVim	  = ["Gvim"]

        -- resources
        myIgnores = ["desktop","desktop_window","notify-osd","stalonetray","trayer"]

        -- names
        myNames   = ["bashrun","Google Chrome Options","Chromium Options"]

        -- a trick for fullscreen but stil allow focusing of other WSs
        myDoFullFloat :: ManageHook
        myDoFullFloat = doF W.focusDown <+> doFullFloat

The Log Hook

The log hook is what xmonad uses to tell us information about itself. Usually this hook is used in conjunction with other modules such as xmobar or, in my case, dzen2. It tells us info about the status of the different workspaces, selected windows, etcetera. When configuring this hook, you usually pipe its result to the pipe you want to use to show the info.

The log hook is also very customizable, you can select colors and icons to be shown. The documentation page for dynamicLogWithPP shows you all the options. In my configuration, because I use dzen2 as a status bar, I also use some helpers for the colors:

myLogHook :: Handle -> X ()
myLogHook h = dynamicLogWithPP $ defaultPP
    {
        ppCurrent           =   dzenColor "#ebac54" "#1B1D1E" . pad
      , ppVisible           =   dzenColor "white" "#1B1D1E" . pad
      , ppHidden            =   dzenColor "white" "#1B1D1E" . pad
      , ppHiddenNoWindows   =   dzenColor "#7b7b7b" "#1B1D1E" . pad
      , ppUrgent            =   dzenColor "#ff0000" "#1B1D1E" . pad
      , ppWsSep             =   " "
      , ppSep               =   "  |  "
      , ppLayout            =   dzenColor "#ebac54" "#1B1D1E" .
                                (\x -> case x of
                                    "ResizableTall"             ->      "^i(" ++ myBitmapsDir ++ "/tall.xbm)"
                                    "Mirror ResizableTall"      ->      "^i(" ++ myBitmapsDir ++ "/mtall.xbm)"
                                    "Full"                      ->      "^i(" ++ myBitmapsDir ++ "/full.xbm)"
                                    "Simple Float"              ->      "~"
                                    _                           ->      x
                                )
      , ppTitle             =   (" " ++) . dzenColor "white" "#1B1D1E" . dzenEscape
      , ppOutput            =   hPutStrLn h
    }

As you can see, I can set the colors for the different workspace statuses, and some icons to be able to know which is the current workspace layout. The last line is very important, as it's the line that outputs the results to wherever we choose.

dzen2 and conky

As I told you at the beginning, I'm using dzen2 as a status bar, and conky to get some fancy system information. To configure this, we have to tell xmonad to spawn the dzen2 and conky processes at the beginning. This can be done inside the main method, and here's how it looks like in my configuration:

myXmonadBar = "dzen2 -x '1440' -y '0' -h '24' -w '640' -ta 'l' -fg '#FFFFFF' -bg '#1B1D1E'"
myStatusBar = "conky -c /home/my_user/.xmonad/.conky_dzen | dzen2 -x '2080' -w '1040' -h '24' -ta 'r' -bg '#1B1D1E' -fg '#FFFFFF' -y '0'"
main = do
    dzenLeftBar <- spawnPipe myXmonadBar
    dzenRightBar <- spawnPipe myStatusBar
    xmonad $ defaultConfig
      { terminal            = myTerminal
      , workspaces          = myWorkspaces
      , keys                = keys'
      , modMask             = modMask'
      , layoutHook          = layoutHook'
      , manageHook          = manageHook'
      , logHook             = myLogHook dzenLeftBar >> fadeInactiveLogHook 0xdddddddd
      , normalBorderColor   = colorNormalBorder
      , focusedBorderColor  = colorFocusedBorder
      , borderWidth         = 2
}

Notice that I use two different bars. I'll use the left one for xmonad info and the right one for system info. What's most important here are the coordinates and size of the bars, and also the configuration file for conky. After spawning the processes, in the main method we tell xmonad to use the dzenLeftBar pipe to output the log info.

The right bar is piped to use the conky output, as configured in the .conky_dzen configuration. Conky has lots of options, covered in the Conky Objects documentation page. Here's my config:

background yes
out_to_console yes
out_to_x no
# Update interval in seconds
update_interval 1

TEXT
^i(/home/my_user/.xmonad/dzen2/cpu.xbm) ^fg(\#FFFFFF)${cpu}% ^i(/home/my_user/.xmonad/dzen2/mem.xbm) ^fg(\#FFFFFF)${memperc}% ^fg(\#ebac54) ${if_existing /proc/net/route eth1} ^i(/home/my_user/.xmonad/dzen2/net_wired.xbm) ^fg(\#00aa4a) ^i(/home/my_user/.xmonad/dzen2/net_down_03.xbm)${downspeed eth1} ^fg(\#ff3333) ^i(/home/my_user/.xmonad/dzen2/net_up_03.xbm)${upspeed eth1} ^fg(\#ebac54) ^i(/home/my_user/.xmonad/dzen2/mpd.xbm) ^fg(\#00ffff)${exec rhythmbox-client --print-playing-format "%aa-%at-%tt" | cut -c-70} ^fg(\#ebac54) ^i(/home/my_user/.xmonad/dzen2/volume.xbm) ^fg(\#00ffff)${exec amixer get Headphone | egrep -o "[0-9]+%" | head -1 | egrep -o "[0-9]*"}% ^fg(\#ebac54) ^i(/home/my_user/.xmonad/dzen2/clock.xbm) ^fg(\#FFFFFF) ${time %d %m %Y} ^fg(\#ebac54) ${time %R}

The important things here are the background line and the out_to_x line, which tell conky to run in the background and not to output the results directly to the X. You can then configure the TEXT variable to display whatever variables you want.

Putting it all together

After all the work is done, it's time to finally put it all together. Here's the final configuration file that I currently use and that I've been showing you in this post:

-- ~/.xmonad/xmonad.hs
-- Imports {{{
import XMonad
-- Prompt
import XMonad.Prompt
import XMonad.Prompt.RunOrRaise (runOrRaisePrompt)
import XMonad.Prompt.AppendFile (appendFilePrompt)
-- Hooks
import XMonad.Operations

import System.IO
import System.Exit

import XMonad.Util.Run


import XMonad.Actions.CycleWS

import XMonad.Hooks.ManageDocks
import XMonad.Hooks.ManageHelpers
import XMonad.Hooks.SetWMName
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.UrgencyHook
import XMonad.Hooks.FadeInactive
import XMonad.Hooks.EwmhDesktops

import XMonad.Layout.NoBorders (smartBorders, noBorders)
import XMonad.Layout.PerWorkspace (onWorkspace, onWorkspaces)
import XMonad.Layout.Reflect (reflectHoriz)
import XMonad.Layout.IM
import XMonad.Layout.SimpleFloat
import XMonad.Layout.Spacing
import XMonad.Layout.ResizableTile
import XMonad.Layout.LayoutHints
import XMonad.Layout.LayoutModifier
import XMonad.Layout.Grid

import Data.Ratio ((%))

import qualified XMonad.StackSet as W
import qualified Data.Map as M

--}}}

-- Config {{{
-- Define Terminal
myTerminal      = "gnome-terminal"
-- Define modMask
modMask' :: KeyMask
modMask' = mod4Mask
-- Define workspaces
myWorkspaces    = ["1:main","2:web","3:vim","4:chat","5:music", "6:gimp"]
-- Dzen/Conky
myXmonadBar = "dzen2 -x '1440' -y '0' -h '24' -w '640' -ta 'l' -fg '#FFFFFF' -bg '#1B1D1E'"
myStatusBar = "conky -c /home/my_user/.xmonad/.conky_dzen | dzen2 -x '2080' -w '1040' -h '24' -ta 'r' -bg '#1B1D1E' -fg '#FFFFFF' -y '0'"
myBitmapsDir = "/home/my_user/.xmonad/dzen2"
--}}}
-- Main {{{
main = do
    dzenLeftBar <- spawnPipe myXmonadBar
    dzenRightBar <- spawnPipe myStatusBar
    xmonad $ defaultConfig
      { terminal            = myTerminal
      , workspaces          = myWorkspaces
      , keys                = keys'
      , modMask             = modMask'
      , layoutHook          = layoutHook'
      , manageHook          = manageHook'
      , logHook             = myLogHook dzenLeftBar >> fadeInactiveLogHook 0xdddddddd
      , normalBorderColor   = colorNormalBorder
      , focusedBorderColor  = colorFocusedBorder
      , borderWidth         = 2
}
--}}}


-- Hooks {{{
-- ManageHook {{{
manageHook' :: ManageHook
manageHook' = (composeAll . concat $
    [ [resource     =? r            --> doIgnore            |   r   <- myIgnores] -- ignore desktop
    , [className    =? c            --> doShift  "1:main"   |   c   <- myDev    ] -- move dev to main
    , [className    =? c            --> doShift  "2:web"    |   c   <- myWebs   ] -- move webs to main
    , [className    =? c            --> doShift  "3:vim"    |   c   <- myVim    ] -- move webs to main
    , [className    =? c            --> doShift	 "4:chat"   |   c   <- myChat   ] -- move chat to chat
    , [className    =? c            --> doShift  "5:music"  |   c   <- myMusic  ] -- move music to music
    , [className    =? c            --> doShift  "6:gimp"   |   c   <- myGimp   ] -- move img to div
    , [className    =? c            --> doCenterFloat       |   c   <- myFloats ] -- float my floats
    , [name         =? n            --> doCenterFloat       |   n   <- myNames  ] -- float my names
    , [isFullscreen                 --> myDoFullFloat                           ]
    ]) 

    where

        role      = stringProperty "WM_WINDOW_ROLE"
        name      = stringProperty "WM_NAME"

        -- classnames
        myFloats  = ["Smplayer","MPlayer","VirtualBox","Xmessage","XFontSel","Downloads","Nm-connection-editor"]
        myWebs    = ["Firefox","Google-chrome","Chromium", "Chromium-browser"]
        myMovie   = ["Boxee","Trine"]
        myMusic	  = ["Rhythmbox","Spotify"]
        myChat	  = ["Pidgin","Buddy List"]
        myGimp	  = ["Gimp"]
        myDev	  = ["gnome-terminal"]
        myVim	  = ["Gvim"]

        -- resources
        myIgnores = ["desktop","desktop_window","notify-osd","stalonetray","trayer"]

        -- names
        myNames   = ["bashrun","Google Chrome Options","Chromium Options"]

-- a trick for fullscreen but stil allow focusing of other WSs
myDoFullFloat :: ManageHook
myDoFullFloat = doF W.focusDown <+> doFullFloat
-- }}}
layoutHook'  =  onWorkspaces ["1:main","5:music"] customLayout $ 
                onWorkspaces ["6:gimp"] gimpLayout $ 
                onWorkspaces ["4:chat"] imLayout $
                customLayout2

--Bar
myLogHook :: Handle -> X ()
myLogHook h = dynamicLogWithPP $ defaultPP
    {
        ppCurrent           =   dzenColor "#ebac54" "#1B1D1E" . pad
      , ppVisible           =   dzenColor "white" "#1B1D1E" . pad
      , ppHidden            =   dzenColor "white" "#1B1D1E" . pad
      , ppHiddenNoWindows   =   dzenColor "#7b7b7b" "#1B1D1E" . pad
      , ppUrgent            =   dzenColor "#ff0000" "#1B1D1E" . pad
      , ppWsSep             =   " "
      , ppSep               =   "  |  "
      , ppLayout            =   dzenColor "#ebac54" "#1B1D1E" .
                                (\x -> case x of
                                    "ResizableTall"             ->      "^i(" ++ myBitmapsDir ++ "/tall.xbm)"
                                    "Mirror ResizableTall"      ->      "^i(" ++ myBitmapsDir ++ "/mtall.xbm)"
                                    "Full"                      ->      "^i(" ++ myBitmapsDir ++ "/full.xbm)"
                                    "Simple Float"              ->      "~"
                                    _                           ->      x
                                )
      , ppTitle             =   (" " ++) . dzenColor "white" "#1B1D1E" . dzenEscape
      , ppOutput            =   hPutStrLn h
    }

-- Layout
customLayout = avoidStruts $ tiled ||| Mirror tiled ||| Full ||| simpleFloat
  where
    tiled   = ResizableTall 1 (2/100) (1/2) []

customLayout2 = avoidStruts $ Full ||| tiled ||| Mirror tiled ||| simpleFloat
  where
    tiled   = ResizableTall 1 (2/100) (1/2) []

gimpLayout  = avoidStruts $ withIM (0.11) (Role "gimp-toolbox") $
              reflectHoriz $
              withIM (0.15) (Role "gimp-dock") Full

imLayout    = avoidStruts $ withIM (1%5) (And (ClassName "Pidgin") (Role "buddy_list")) Grid 
--}}}
-- Theme {{{
-- Color names are easier to remember:
colorOrange         = "#FD971F"
colorDarkGray       = "#1B1D1E"
colorPink           = "#F92672"
colorGreen          = "#A6E22E"
colorBlue           = "#66D9EF"
colorYellow         = "#E6DB74"
colorWhite          = "#CCCCC6"
 
colorNormalBorder   = "#CCCCC6"
colorFocusedBorder  = "#fd971f"


barFont  = "terminus"
barXFont = "inconsolata:size=12"
xftFont = "xft: inconsolata-14"
--}}}

-- Prompt Config {{{
mXPConfig :: XPConfig
mXPConfig =
    defaultXPConfig { font                  = barFont
                    , bgColor               = colorDarkGray
                    , fgColor               = colorGreen
                    , bgHLight              = colorGreen
                    , fgHLight              = colorDarkGray
                    , promptBorderWidth     = 0
                    , height                = 14
                    , historyFilter         = deleteConsecutive
                    }
 
-- Run or Raise Menu
largeXPConfig :: XPConfig
largeXPConfig = mXPConfig
                { font = xftFont
                , height = 22
                }
-- }}}
-- Key mapping {{{
keys' conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
    [ ((modMask,                    xK_p        ), runOrRaisePrompt largeXPConfig)
    , ((modMask .|. shiftMask,      xK_Return   ), spawn $ XMonad.terminal conf)
    , ((modMask,                    xK_F2       ), spawn "gmrun")
    , ((modMask .|. shiftMask,      xK_c        ), kill)
    , ((modMask .|. shiftMask,      xK_l        ), spawn "slock")
    -- Programs
    , ((0,                          xK_Print    ), spawn "scrot -e 'mv $f ~/screenshots/'")
    , ((modMask,		            xK_o        ), spawn "chromium-browser")
    , ((modMask,                    xK_m        ), spawn "nautilus --no-desktop --browser")
    -- Media Keys
    , ((0,                          0x1008ff12  ), spawn "amixer -q sset Headphone toggle")        -- XF86AudioMute
    , ((0,                          0x1008ff11  ), spawn "amixer -q sset Headphone 5%-")   -- XF86AudioLowerVolume
    , ((0,                          0x1008ff13  ), spawn "amixer -q sset Headphone 5%+")   -- XF86AudioRaiseVolume
    , ((0,                          0x1008ff14  ), spawn "rhythmbox-client --play-pause")
    , ((0,                          0x1008ff17  ), spawn "rhythmbox-client --next")
    , ((0,                          0x1008ff16  ), spawn "rhythmbox-client --previous")

    -- layouts
    , ((modMask,                    xK_space    ), sendMessage NextLayout)
    , ((modMask .|. shiftMask,      xK_space    ), setLayout $ XMonad.layoutHook conf)          -- reset layout on current desktop to default
    , ((modMask,                    xK_b        ), sendMessage ToggleStruts)
    , ((modMask,                    xK_n        ), refresh)
    , ((modMask,                    xK_Tab      ), windows W.focusDown)                         -- move focus to next window
    , ((modMask,                    xK_j        ), windows W.focusDown)
    , ((modMask,                    xK_k        ), windows W.focusUp  )
    , ((modMask .|. shiftMask,      xK_j        ), windows W.swapDown)                          -- swap the focused window with the next window
    , ((modMask .|. shiftMask,      xK_k        ), windows W.swapUp)                            -- swap the focused window with the previous window
    , ((modMask,                    xK_Return   ), windows W.swapMaster)
    , ((modMask,                    xK_t        ), withFocused $ windows . W.sink)              -- Push window back into tiling
    , ((modMask,                    xK_h        ), sendMessage Shrink)                          -- %! Shrink a master area
    , ((modMask,                    xK_l        ), sendMessage Expand)                          -- %! Expand a master area
    , ((modMask,                    xK_comma    ), sendMessage (IncMasterN 1))
    , ((modMask,                    xK_period   ), sendMessage (IncMasterN (-1)))


    -- workspaces
    , ((modMask .|. controlMask,   xK_Right     ), nextWS)
    , ((modMask .|. shiftMask,     xK_Right     ), shiftToNext)
    , ((modMask .|. controlMask,   xK_Left      ), prevWS)
    , ((modMask .|. shiftMask,     xK_Left      ), shiftToPrev)
    
    -- quit, or restart
    , ((modMask .|. shiftMask,      xK_q        ), io (exitWith ExitSuccess))
    , ((modMask,                    xK_q        ), spawn "killall conky dzen2 && /home/my_user/.cabal/bin/xmonad --recompile && /home/my_user/.cabal/bin/xmonad --restart")
    ]
    ++
    -- mod-[1..9] %! Switch to workspace N
    -- mod-shift-[1..9] %! Move client to workspace N
    [((m .|. modMask, k), windows $ f i)
        | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
        , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
    ++

    --
    -- mod-{w,e,r}, Switch to physical/Xinerama screens 1, 2, or 3
    -- mod-shift-{w,e,r}, Move client to screen 1, 2, or 3
    --
    [((m .|. modMask, key), screenWorkspace sc >>= flip whenJust (windows . f))
        | (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
        , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]

--}}}
-- vim:foldmethod=marker sw=4 sts=4 ts=4 tw=0 et ai nowrap

Copy this into your .xmonad folder, assuming you have everything needed, and you should see a desktop similar to the one shown on the image.

Feel free to check my GitHub xmonad repository to find updated configuration files.

Cheers!