Nepherte (dot) be

Howto: Step by Step Configuration of XMonad

After months of false announcements on my behalf, I finally finished up writing my XMonad configuration tutorial. Since not everyone uses XMonad, let alone has ever heard of it, Here’s a brief introduction: XMonad is a dynamically tiling X11 window manager, written and configured in Haskell. Tiling window managers arrange windows in nonoverlapping tiled patterns and strive to make it possible for the user to productively manage windows without the use of the mouse.

While many tiling window managers come with a rather simple configuration file, Haskell, a λ-calculus based functional programming language, lifts up XMonad up to one of the most extendible and finest window manager around. And therein lies “the problem”. Haskell is not what you would call a programming language used by regular folks (if regular folks actually know a programming language) nor does its syntax resemble to Java or C.

This guide will lead you step by step through the process of configuring XMonad. I will take my own config file as example. A lot of this can also be found scattered over the xmonad homepage. I’v taken the liberty to bundle all the information.

Table of Contents
The working directory

The main working directory, where the configuration is done, where logs are stored and where the compiled binary is placed, is located at /home/<username>/.xmonad. The 2 most important files for this guide are xmonad.hs and xmonad.error. Configuration is done in the first one and errors are spit out in the last one. So if there is problem with compiling the config file, look at xmonad.error first. All the code snippets I’ll be posting have to be put in xmonad.hs. Mind you, I will only mention the code related to the section it is under. You should be capable of putting it all together or simply use my entire config file.

The basic configuration

A working configuration could be as easy as

-- Import statements
import XMonad
 
-- Run XMonad
main = do
	xmonad $ defaultConfig

It basically runs xmonad with the default configuration file (defaultConfig), found here. Note that you don’t have to actually download the template config file I linked to. Everything is already included in the XMonad installation. However, you can use the file as your xmonad.hs and you’ll end up with the same result.

Xmonad provides a few basic Desktop Environment (DE) integration configurations you could use: Gnome (gnomeConfig), KDE (kdeConfig) and XFCE (xfceConfig). Each DE config file will gracefully handle the panels inherent to the DE, shortcut keys, etc. In short you’ll be running your DE as always but the window manager is replaced with XMonad. Since I favor Gnome, we’ll use this instead of the default config. Note that you are not required to use a DE at all, even if you are using a DE specific config.

-- Import statements
import XMonad
import XMonad.Config.Gnome
 
-- Run XMonad
main = do
	xmonad $ gnomeConfig

From now on, we will override and extend the gnome configuration step by step until we have the setup we want.

The default terminal

You’ll probably use the terminal a lot so it is always nice to use the terminal program you prefer. In this example, I use gnome-terminal with a customized profile for XMonad.

-- Import statements
import XMonad
import XMonad.Config.Gnome
 
-- Define terminal
myTerminal = "gnome-terminal --window-with-profile=XMonad"
 
-- Run XMonad
main = do
	xmonad $ gnomeConfig {
	  terminal  = myTerminal
	}

You can consider the variable terminal as a sort of setting/property which we now have overrided with myTerminal. Feel free to replace myTerminal with the one you prefer.

Workspaces

Just as is the case with a desktop environment, XMonad can make use of workspaces and I encourage you to use them extensively. This is how you define workspaces:

-- Import statements
import XMonad
import XMonad.Config.Gnome
 
-- Define amount and names of workspaces
myWorkspaces = ["1:main","2:web","3:chat","4:media","5:graph","6:browse","7:dev","8:mail"]
 
-- Run XMonad
main = do
	xmonad $ gnomeConfig {
	  workspaces  = myWorkspaces
	}
Attaching applications to workspaces

Workspaces are a great way to separate types of programs from one another. As the names of my workspaces suggest, I prefer for example that development, web and multimedia applications don’t get mixed up with eachother. As opposed to a regular floating window manager, you don’t have to put the applications on the workspaces yourself. You can tell xmonad to do it for you instead. Here’s an example for 2 workspaces:

-- Import statements
import XMonad
import XMonad.Config.Gnome
import qualified XMonad.StackSet as W
 
-- Define amount and names of workspaces
myWorkspaces = ["1:main","2:web","3:chat","4:media","5:graph","6:browse","7:dev","8:mail"]
 
-- Define the workspace an application has to go to
myManageHook = composeAll . concat $
            [ -- The applications that go to web
              [ className =? b --&gt; doF (W.shift "2:web") | b &lt; - myClassWebShifts]
              -- The applications that go to chat
            , [ className =? c --&gt; doF (W.shift "3:chat") | c &lt; - myClassChatShifts]
            ]
		where
	          myClassWebShifts    = ["Firefox", "Filezilla"]
		  myClassChatShifts   = ["emesene", "Xchat"]
 
-- Run XMonad
main = do
	xmonad $ gnomeConfig {
	    workspaces  = myWorkspaces
	  , manageHook  = myManageHook
	}

This will place firefox and filezilla on the “2:web” workspace while emesene and xchat are put on the “3:chat” workspace. There are a few ways you can catch an application: class names (className) and title names (title). To figure out what the application’s class or title name is, you can run xprop in a console and click on the app you want to grab the info from.

Due to some encoding issue <- is displayed as < – near the variables b and c. Make sure to remove the space between them.

Floating windows

By default, XMonad will tile all windows. Occasionally you don’t want to tile a certain application because it just gets too messy. The classic examples are MPlayer and GIMP. If you let xmonad tile mplayer, the aspect ratio’s of your movies are ruined and GIMP just has too many tool aid boxes which would normally get tiled just like all other windows. You will need to add them to myManageHook:

-- Import statements
import XMonad
import XMonad.Config.Gnome
 
-- Define the workspace an application has to go to
myManageHook = composeAll . concat $
            [  -- The applications that float
              [ className =? i --&gt; doFloat | i &lt; - myClassFloats]
            ]
		where
		  myClassFloats	      = ["Gnome-mplayer","MPlayer"]
 
-- Run XMonad
main = do
	xmonad $ gnomeConfig {
	    manageHook  = myManageHook
	}

In this example, mplayer and gnome-mplayer will not be tiled but floated instead.

Tiling layouts

There are multiple tiling layouts available, or more specifically, the way your windows will be arranged. The 4 most commonly used layouts are Tall, Full, SimpleFloat and Mirror Tall. But first some further explanation about tiling layouts.

Most layouts have a master pane and one or more slave panes. When you start your first application window, it will open up fully maximized. When opening other application windows, the latest launched application will be placed in the master pane. Every other window will be reorganized, resized and placed in the slave pane(s) according to the used tiling algorithm. That’s the principle of tiling. Now, you can surely think of a few ways how these master and slave panes can be organized and that’s where the layouts kick in. A possible organization of these panes is called a layout.

  • Tall is a layout with 2 columns of which the size ratio can be defined by the user. You can set the maximum number of windows in the master pane, which occupies the left part of the screen. In each pane, the windows are organized vertically.
  • Full is the layout where each application window will occupy the whole screen. The most current one will be shown, all the others will be hidden behind the current one.
  • SimpleFloat is a layout where all windows are put in float mode. Each window also gets a small title bar.
  • Mirror Tall is similar to Tall but it uses rows instead of columns and windows are organized horizontally in each pane.

This is how you set the 4 layouts I’ve mentioned:

--import statements
import XMonad
import XMonad.Config.Gnome
import XMonad.Layout.SimpleFloat
 
-- Define default layouts used on most workspaces
defaultLayouts = tiled ||| Mirror tiled ||| simpleFloat ||| Full
  where
       -- default tiling algorithm partitions the screen into two panes
          tiled   = Tall nmaster delta ratio
 
       -- The default number of windows in the master pane
          nmaster = 1
 
       -- Default proportion of screen occupied by master pane
          ratio   = 1/2
 
       -- Percent of screen to increment by when resizing panes
          delta   = 3/100
 
main = do
	xmonad $ gnomeConfig {
		layoutHook  = defaultLayouts
	}

These layouts apply to all your workspaces. You could also set one or more layouts for one specific workspace with onWorkspace. The next example will only set the Full layout for the “4:media” workspace. With the noBorders option, we remove the borders around a window (the ones you get when a window is active).

--import statements
import XMonad
import XMonad.Config.Gnome
import XMonad.Layout.SimpleFloat
import XMonad.Layout.NoBorders (noBorders)
import XMonad.Layout.PerWorkspace (onWorkspace)
 
-- Define default layouts used on most workspaces
defaultLayouts = tiled ||| Mirror tiled ||| simpleFloat ||| Full
  where
       -- default tiling algorithm partitions the screen into two panes
          tiled   = Tall nmaster delta ratio
 
       -- The default number of windows in the master pane
          nmaster = 1
 
       -- Default proportion of screen occupied by master pane
          ratio   = 1/2
 
       -- Percent of screen to increment by when resizing panes
          delta   = 3/100
 
-- Define layout for specific workspaces
mediaLayout = noBorders $ Full
 
-- Put all layouts together
myLayouts = onWorkspace "media" mediaLayout $ defaultLayouts
 
main = do
	xmonad $ gnomeConfig {
		layoutHook  = myLayouts
	}
Key combinations

XMonad can be controlled by the keyboard without the use of a mouse whatsoever. Not only is it very useful to know the most important key combinations in XMonad, but it is also a good idea to create new key combinations to facilitate your work in XMonad.That way you don’t have to open up a terminal every time you want to do something.

In XMonad you can add new key combinations, redefine and remove existing ones. Let us first define the new keys we want to add. The format in which you define new keys is

((key_modifier, key), action)

If you don’t want to use a key modifier you can use 0. If you want a combination of key modifiers, you can combine them with .|. You can find an exhaustive list of keys here. If you are unshure about the value of a key, you can use the xev command that prints all the information of a pressed key in the terminal. Prefix that value witk xK_ or simply use the key code, which can be useful for multimedia keys such as play/volume up & down/mute/media player/…

-- Import statements
import XMonad.Util.Run
import XMonad.Actions.CycleWS
 
-- Define keys to add
keysToAdd x     = [ -- Gnome run dialog
                       ((modMask x, xK_F2), spawn "/home/bart/.run/run.py --interface=full")
                    -- Gnome close window
                    ,  ((modMask x, xK_F4), kill)
                    -- Shift to previous workspace
                    ,  (((modMask x .|. controlMask), xK_Left), prevWS)
                    -- Shift to next workspace
                    ,  (((modMask x .|. controlMask), xK_Right), nextWS)
                    -- Shift window to previous workspace
                    ,  (((modMask x .|. shiftMask), xK_Left), shiftToPrev)
                    -- Shift window to next workspace
                    ,  (((modMask x .|. shiftMask), xK_Right), shiftToNext)
                    -- Mute volume
                    ,  ((0,0x1008ff12), spawn "amixer -Dpulse set Master toggle")
                    -- Launch mpd
                    , ((0,0x1008ff32), spawn "mpd")
                  ]

Next you need to define the keys you want to remove. You only have to specify the key combination and not the action it performs. An example

keysToRemove x  = [ -- Old run dialog binding
                       (modMask x , xK_p)
                    -- Old close window binding
                    ,  (modMask x, xK_c)
                    -- Unused gmrun binding
                    ,  (modMask x .|. shiftMask, xK_p)
                    -- Unused gnome session logout dialog
                    ,  (modMask x .|. shiftMask, xK_q)
                  ]

Last thing remaining is putting it all together.

-- Import statements
import XMonad
import XMonad.Config.Gnome
import qualified Data.Map as M
 
-- Merge keys to add and existing keys
newKeys x       = M.union (keys gnomeConfig x) (M.fromList (keysToAdd x))
 
-- Delete the keys to remove from existing keys
myKeys x        = foldr M.delete (newKeys x) (keysToRemove x)
 
main = do
	xmonad $ gnomeConfig {
		keys = myKeys
	}
Status bar

A status bar always comes in handy: to know what workspace you are on, in what workspace there are still windows, to display conky stats, time & date, the current song you are listening too, … XMonad nicely integrates with xmobar and dzen2. In this tutorial, I’ll be using 2 dzen2 status bars. Make sure you have dzen2 and conky installed. First we specify the commands that launches the dzen2 status bars.

myStatusBar = "dzen2 -x '0' -y '0' -h '24' -w '960' -ta 'l' -fg '#FFFFFF' -bg '#000000' -fn '-*-bitstream vera sans-medium-r-normal-*-11-*-*-*-*-*-*-*'"
conkyStatsBar = "conky -c .conkyrc_console | dzen2 -x '960' -y '0' -h '24' -w '890' -ta 'l' -fg '#3EB5FF' -bg '#000000' -fn '-*-bitstream vera sans-medium-r-normal-*-11-*-*-*-*-*-*-*'"

You can adapt the commands to suit your needs. Set the horizontal, vertical positions and alignment of the bars with the -x, -y, -ta switches. Use the -c switch in conky to specify the conky file to run. For more information see the dzen2 manual pages.

Now you can format the status bars with the dynamicLogHook XMonad library.

import XMonad
import XMonad.Hooks.DynamicLog
import System.IO
 
myLogHook :: Handle -&gt; X ()
myLogHook h = dynamicLogWithPP $ defaultPP
    {
        ppCurrent           =   dzenColor "#3EB5FF" "black" . pad
      , ppVisible           =   dzenColor "white" "black" . pad
      , ppHidden            =   dzenColor "white" "black" . pad
      , ppHiddenNoWindows   =   dzenColor "#444444" "black" . pad
      , ppUrgent            =   dzenColor "red" "black" . pad
      , ppWsSep             =   " "
      , ppSep               =   "  |  "
      , ppTitle             =   (" " ++) . dzenColor "white" "black" . dzenEscape
      , ppOutput            =   hPutStrLn h
    }

In a last step, you launch each dzen2 instance

import XMonad.Hooks.UrgencyHook
import XMonad.Hooks.FadeInactive
import XMonad.Util.Run
import XMonad.Config.Gnome
 
main = do
    workspaceBar &lt; - spawnPipe myStatusBar
    conkyStats &lt;- spawnPipe conkyStatsBar
    xmonad $ withUrgencyHook NoUrgencyHook {
        logHook             = myLogHook workspaceBar &gt;&gt; fadeInactiveLogHook 0xdddddddd
}

Due to some encoding issue, again there is a space between <- near workspaceBar which shouldn’t be there. The tiling algorithm however will overlap these status bars so you will have to tell XMonad not to do so.

import XMonad.Hooks.ManageDocks
 
main = do
    xmonad {
      manageHook          = manageDocks &lt; +&gt; myManageHook
}

Recall that we have specified myManageHook earlier in this tutorial.

4 Comments

    Great tutorial!
    Could you please post your .conkyrc_console file?
    Thanks

  • I kinda accidently published this article, because it’s not reallly finished yet :) It should be though in less than a week. Anyways, you can find .conkyrc_console here: http://www.nepherte.be/files/config/home/bart/conkyrc_console

  • Thanks for the tutorial. I don’t see, though, where conkyStats is used. Is there a line missing? Can you post your complete xmonad.hs?

    Thanks!

  • There are 2 lines in particular you have to look at:
    1/ conkyStatsBar = “conky -c .conkyrc_console | dzen2 -x ’960′ -y ’0′ -h ’24′ -w ’890′ -ta ‘l’ -fg ‘#3EB5FF’ -bg ‘#000000′ -fn ‘-*-bitstream vera sans-medium-r-normal-*-11-*-*-*-*-*-*-*’”

    2/ conkyStats < - spawnPipe conkyStatsBar

    The first line is just the full command that has to be executed. It's a conky which outputs to a dzen2 status bar. The second line runs the command conkyStatsBar and puts it in conkyStats. conkyStats isn't used anywhere else. So you could leave out the assignment to conkyStats like this: spawnPipe conkyStatsBar. Note that workspaceBar is used later on in the logHook so you can't leave out that assignment.

    You can find one of my xmonad.hs files here (this one doesn't have the conkyStatsBar but it has numerous other conky bars you can take a look at): http://www.nepherte.be/files/config/home/bart/xmonad/xmonad.hs

Leave a Reply