Oct 31, 2013 - Howto Configure your XMonad

Comments

Configuration

After one has successfully installed the Xmonad window manager, the next task is to take a look at the configuration. Over all you can start to use an unconfigured Xmonad. But the main advantages of this window manager comes from a nice configuration.

A Xmonad is configured in the xmonad.hs file. This file is typically located within your home directory in: ~/.xmonad/xmonad.hs.

As you can see, the configuration is a Haskell file. This means, that you configure your Xmonad in the language Haskell. So you have all the power of Haskell at your and, to define your configuration.

My whole xmonad.hs configuration can be found on github.com/frosch03.

What parts can you configure

The configuration of a Xmonad is done within the module, that contains the main function. Within main, the window manager is started with the xmonad function. That function is supplied with a data structure, called defaultConfig. Within Haskell a data structure can be altered, while passing it to a function, in the following way:

    xmonad \$ defaultConfig { workspaces = myWorkspaces }

This tells us, that the function xmonad is called with o parameter called defaultConfig, where the value of workspaces is overwritten by the value of myWorkspaces.

According to this, the following sections describe parts of the defaultConfig data structure, that are used to overwrite the default one, while xmonad is called.

The virtual Desktops

First of all, there are virtual desktops. A virtual desktop, in the xmonad sense, is a subset of the running applications. These applications are shown in the context of their desktop. One application is always mapped to exactly one desktop.

It is usual not to use more than 9 desktops in xmonad. This is due to the fact, that each desktop is accessed by a shortcut and it might be a good idea to use a combination of modifiers together with a number key. But after all it is up to you to use as many desktops as you wish.

To setup your desktops you define a list of strings. Each string stands for a desktop. One way is to use just numbers for your desktop's names.

    myWorkspaces = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]

But because we have the whole power of Haskell to configure Xmonad, one can write this as:

    myWorkspaces = map show [1..9]

I like to give names to my desktops. These names remind me, what kind of applications i can expect at these desktops. So my desktop configuration would look something like this:

    myWorkspaces
      = [ "console"
        , "code"
        , "web"
        , "im"
        , "mail"
        , "skype"
        , "gimp"
        , "musi"
        , "irc"
        ]

The mappings of Applications onto Desktops

So tiling window managers are famous for automatically organizing your windows, right? This includes routing applications onto specific desktops. The configuration of the routing is described within this section.

First of all, different applications need a way to differentiate between them. For my configuration, i go mainly with the X11 properties WM_CLASS and _NET_WM_NAME. The XMonad/ManageHook.hs exports the className function, that matches WM_CLASS, and also the title function, that matches _NET_WM_NAME.

If you have a look into XMonad/ManageHook.hs, you can find more functions to match window properties.

In order to route the applications to desktops, i like to define various lists of strings. These strings are used to match similar applications, that i like to go onto the same desktop. For example, i use multiple applications for instant messaging like Pidgin or Empathy. They go into the comApps list:

    webApps    = ["Firefox", "Google-chrome", "Chromium"]
    comApps    = ["Pidgin", "jabber", "Jabber", "Empathy"]
    mailApps   = ["OUTLOOK.EXE", "Wine", "mutt", "mail"]
    gimpApp    = ["Gimp", "gimp"]
    skypeApp   = ["Skype", "skype", "MainWindow"]
    ircApps    = ["workies", "irc"]

These are the strings i use, to route most of my applications. In the next step, you see, how this lists help to do the actual routing.

The actual routing is attached to the myManagedHook label, which is composed from a list of list of atomic routes.

    myManagedHook = composeAll . concat \$
    [ [ className =? c --> doFloat               | c <- floatClass ]
    , [ title     =? t --> doFloat               | t <- floatTitle ]
    , [ title     =? x --> doF (S.shift "1")     | x <- hacking]
    , [ title     =? x --> doF (S.shift "code")  | x <- coding]
    , [ className =? x --> doF (S.shift "web")   | x <- webApps ]
    , [ className =? x --> doF (S.shift "im")    | x <- comApps ]
    , [ title     =? x --> doF (S.shift "im")    | x <- comApps ]
    , [ className =? x --> doF (S.shift "mail")  | x <- mailApps ]
    , [ title     =? x --> doF (S.shift "mail")  | x <- mailApps ]
    , [ className =? x --> doF (S.shift "gimp")  | x <- gimpApp ]
    , [ className =? x --> doF (S.shift "skype") | x <- skypeApp ]
    , [ title     =? x --> doF (S.shift "irc")   | x <- ircApps ]
    , [ isFullscreen   --> doFullFloat]
    ]

This syntax may look a little confusing, but what it actually say's (corresponding to the irc-line) is: For each string inside ircApps, if that string equals the title of the application, shift the application onto the desktop called irc.

Later, while calling the xmonad function, the manageHook value will be overwritten with this definition. (Actually, the manageHook property will be extended with this definitions)

The Layouts

Routing applications to different desktops was the first automation step. In this section, the second step is described, namely how to align multiple applications within that desktop. In this section, that will describe some of the existing layouts. There are many more layouts defined in the xmonad-contrib library. For inspiration you might want to have a look to other XMonad configurations. Also you can risk a look into the xmonad-contrib sources. There are all the layouts defined, but as usual, the documentation could be better.

A layout definition for a desktop starts with the function call onWorkspaces. There are two parameters to supply, the list of the names of desktops, where the layout should apply, and the actual layout itself.

    onWorkspaces
      ["1"]
      (     Full
        ||| (Tall 1 (1/2) (3/100))
        ||| Mirror (Tall 1 (1/2) (3/100))
      )

This translates to the following. The desktop with name 1 aligns its applications with one of the three supplied layouts. The layout Full simply shows an application in full screen. The (Tall 1 (1/2) (3/100)) layout splits the screen vertically into two half's ((1/2)), separated by gaps 3% ((3/100)). With Mirror the supplied layout is modified, such that it splits the screen horizontally.

Multiple layouts are combined with the (|||) operator.

The result of such a onWorkspaces definition is again something, that can be supplied to another onWorkspaces definition. Therefore the layout definition of a XMonad is a long concatenation of single layout statements. My whole layout definition is bound to the myLayout name. Note the where clause with all the local definitions.

    myLayout
     = smartBorders 
     \$ avoidStruts 
     \$ onWorkspaces ["1"]      (Full ||| tiled ||| Mirror tiled)
     \$ onWorkspaces ["code"]   (codeColumn ||| codeRow)
     \$ onWorkspaces ["web"]    (Full ||| (gaps [(L,250), (R,250)] \$ Full))
     \$ onWorkspaces 
         ["im"]
         (imLayout gridLayout ||| imLayout Full ||| imLayout Circle)
     \$ onWorkspaces ["mail"]   (Full)
     \$ onWorkspaces ["skype"]  (skypeLayout)
     \$ onWorkspaces ["gimp"]   (gimp)
     \$ onWorkspaces ["musi"]   (Circle)
     \$ tiled ||| Mirror tiled ||| Full ||| Circle
     where
      tiled          = Tall nmaster delta ratio
      nmaster        = 1 
      ratio          = 1/2
      delta          = 3/100
      ratio'         = 1/3
      delta'         = 0
      imLayout l     = reflectHoriz (pidginLayout l)
      pidginLayout l = withIM (18/100) (Role "buddy_list") l
      skypeLayout    = reflectHoriz \$ (withIM (1%6) (Role "MainWindow") Grid)
      codeColumn     
        = named "Column Code"
        \$ Tall nmaster delta' ratio'
      codeRow  
        = named "Row Code"
        \$ Mirror
        \$ reflectHoriz
        \$ Tall nmaster delta' ratio'
      gridLayout     
        = spacing 8
        \$ Grid      
      gimp 
        = withIM (0.11) (Role "gimp-toolbox")
        \$ reflectHoriz
        \$ withIM (0.15) (Role "gimp-dock") Full

This shows, that on the code desktop there are just two different variations of the tiled layout. On the code desktop, the split is at (1/3), either horizontally or vertically, of the screen. The idea behind this layout is, that the smaller window can hold a interpreter window, like the ghci. Within the bigger window, an editor with the corresponding source file has place.

For the web desktop, i used earlier only a full screen layout:

    onWorkspaces ["web"] (Full)

The actual layout, shown earlier, showed a Full layout and also a gaps layout. This gaps layout inserts a spacer of 250 pixels on the left and the right. I think, that a browser that is smaller, can help reading text on pages with no line break.

For the im desktop a layout is used, that places a specific window on one side. withIM (18/100) (Role "buddy_list") points out, that a window with the role buddy_list gets a column of 18% of the screen width. Usually this is on the left side, but the call to reflectHoriz switches that to the right. The rest of the screen is managed with either gridLayout, Full, or Circle. So on the im desktop, there are three layouts to choose from.

The other desktops have similar layouts or just a single one.

The Key Bindings

Another important thing for XMonad to configure, are the keybindings. They are important, because XMonad is mostly controlled via the keyboard. There are keybindings for opening a terminal, moving windows within the layout, shifting them to other desktops, or switching the actual one. Here are keybindings cheetshets for the default key bindings.

Additionally one can change this key bindings or add new ones. The following lists my key bindings, which i add to the default ones.

    myKeys x
      = M.fromList \$
      [ ((modMask x, xK_p),  shellPrompt myXPConfig)
      , ((modMask x, xK_y),  scratchpadSpawnActionTerminal "urxvt")
      , ((modMask x, xK_m),  windows shiftMaster)
      , ((modMask x .|. shiftMask, xK_m), manPrompt myXPConfig)
      , ((modMask x .|. shiftMask, xK_s), sshPrompt myXPConfig)
      , ((modMask x .|. shiftMask, xK_f), focusUrgent)
      , ((modMask x .|. shiftMask, xK_j), windows swapDown)
      , ((modMask x .|. shiftMask, xK_k), windows swapUp)
      , ((modMask x .|. shiftMask, xK_Return), spawn "/usr/bin/urxvt")
    
      , ( (modMask x .|. controlMask, xK_l)
        , spawn "gnome-screensaver-command --lock"
        )
      , ( (modMask x .|. controlMask, xK_a)
        , spawn "/home/frosch03/whatIsThisPlace"
        )
      , ( (0, xK_XF86ScreenSaver)
        , spawn "gnome-screensaver-command --lock"
        )
      , ( (0, xK_XF86AudioRaiseVolume)
        , spawn "/home/frosch03/.xmonad/volUp.sh"
        )
      , ( (0, xK_XF86AudioLowerVolume)
        , spawn "/home/frosch03/.xmonad/volDn.sh"
        )
    ]
    newKeys x = myKeys x `M.union` keys defaultConfig x

One can choose the modifiers out of:

modMask corresponds to your modifier key (for me that is the windows-key)

shiftMask is your shift key

controlMask corresponds to ctrl

They are combined with the (.|.) operator. A key binding is defined with a tuple. The first element is also a tuple from the modifiers in the front, and the key in the second. The second element is the command, that is issued, if the key is pressed.

The last line shows, that this configurations are additional. As you can see, my definition is bound to the identifier myKeys. The union of my keys and the default keys (keys defaultConfig) is labeled newKeys, which will be used later to overwrite the default configuration.

The xmonad call within main

Last but not least, here is the call to xmonad within the main function. This call looks like the following:

    xmonad
      \$ withUrgencyHook NoUrgencyHook
      \$ defaultConfig
        { borderWidth        = 1
        , normalBorderColor  = "#333333"
        , focusedBorderColor = "#999999"
        , modMask            = mod4Mask
        , keys               = newKeys
        , workspaces         = myWorkspaces
        , manageHook         =     manageDocks
                               <+> manageHook defaultConfig
                               <+> myManagedHook
        , layoutHook         = myLayout
        , logHook            = (  dynamicLogWithPP 
                                \$ myDzen2PP 
                                   { ppOutput  = \x -> writeFile "/tmp/dmpipe1" \$ "1 " ++ x ++ "\n"}
                               )
        } 

The additional call to withUrgencyHook is for my information bar. That configuration will be covered in another article. Also the line logHook is for displaying the bar.

Beside that, the width of the border is set to 1 pixel via borderWidth. A color is specified for the focused window with focusedBorderColor, and another color for the normal windows border via normalBorderColor. The modifier key modMask is set to the mod4Mask, which in my case corresponds to the windows key.

Then the different configurations, defined earlier, are placed. The key's newKeys is already the combination of the default keys, and the newly defined myKeys. Therefore keys is just overwritten with newKeys. The same is true for myWorkspaces, and myLayout. For the manageHooks, the defined myManakgeHook gets combined with the default one, and the manageDocks.

Finally

And there you have it, that are the basic building blocks of a XMonad configuration. You can compile your configuration by:

    xmonad --recompile

If that doesn't throws any errors, the running XMonad can be restarted by:

    xmonad --restart

That's it, for the basic configuration part. In another post, i will describe, how to build a information bar with dzen2.

Again, you can find my whole configuration file on github.com/frosch03.