MonadicT
I see dead objects!
Search

Configuring XMonad

XMonad is a minimal tiling window manager. This post shows how to setup xmnoad on a Ubuntu 14.04 machine and configure it in small steps. xmonad configuration is done through a Haskell program and this proves to be one of the major obstacles to using xmonad effectively. On the other hand, if you can understand basic Haskell syntax, there is not another window manager that I know of which is as extensible as xmonad. xmonad is to window management as emacs is to, well, let's stop right there! You get the idea!

Installing xmonad

My usual sequence for setting up a Linux computer is to install Ubuntu or something similar. The distribution you use really doesn't matter since we are going to be avoiding all the bells and whistles that come with each distribution. Let's assume that you have X installed on the system and a desktop such as gnome or Kde. In my case, I installed Ubuntu 14.04 and installed gnome desktop using sudo apt-get install gnome-desktop. I then created a file called /usr/share/applications/xmonad.desktop with the following contents. With this in place, Gnome Display Manager gives me an additional choice of window managers to run when I login.

There are many other ways to get xmonad to run as the window manager. ~/.xessionrc is another place where xmonad can be started on login.

[Desktop Entry]
Type=Application
Name=Xmonad
Exec=xmonad
NoDisplay=true
X-GNOME-WMName=Xmonad
X-GNOME-Autostart-Phase=WindowManager
X-GNOME-Provides=windowmanager
X-GNOME-Autostart-Notify=true

Configuring xmonad

xmonad runs happily with no custom configuration. However, it is going to be a bland experience. Customizing xmonad requires you to have a ~/.xmonad/xmonad.hs file. Adding content to this file will be the main focus of this post.

Hello, xmonad.hs

This is the simplest configuration of XMonad window manager. It just runs xmonad with defaultConfig. defaultConfig is just a record containing xmonad configuration options. As we will see later, we can use Haskell's record update syntax to change the options.

import XMonad
main = xmonad defaultConfig

Taking back our Alt key

If you are an emacs user, you are likely to be missing some favorite Alt key combinations. Let's change XMonad key to the Window key, which most of us happen to have on our keyboards. The following will tell XMonad to use Windows key instead of Alt.

import XMonad
main = xmonad defaultConfig {
  modMask = mod4Mask
}

Configuring XMonad for a Desktop Environment

A list of configuration options can be seen at XConfig. Generally, we run Xmonad in a Desktop Environment. In such cases, Xmonad configuration should start with Desktop Config. You can use a generic desktop configuration or specific to the environment you are using. Since I am running a Gnome environment, my xmnoad.hs will be as shown below.

import XMonad
import XMonad.Config.Desktop
main = xmonad desktopConfig {
    modMask = mod4Mask
}

Restarting Xmonad will now an interesting effect. Our window is moved down by some distance leaving some room for running toolbars. We can try running GNOME Panel as the status bar. That, of course, is sub-optimal for my work flow and I prefer something like dzen2. dzen2 reads its standard input stream and displays it in the window it creates.

Configuring dzen2 and conky

dzen2 can be configured using ~/.Xresource1. Let's set foreground and background colors for dzen2 as below.

dzen2.background: black
dzen2.foreground: white
dzen2.font: -*-fixed-*-*-*-*-11-*-*-*-*-*-*-*

Running dzen2 will now display with black background and white foreground. Each line it reads from its standard input stream will be displayed by dzen2. Dzen2 also supports a custom formatting language. ^fg(black) will switch foreground color to black and ^fg() will switch it back to default foreground color. ^p(10) will move the current position to right by 10 pixels. A negative number will cause it to move to left.

Now that we know how dzen2 works, let's see how we can display current time at the top right-hand side of our screen. We could have a shell script which pipes its output to dzen2 and loops over a sequence of date and sleep commands. Fortunately, we can do much better using a program called conky. Conky is configured using an initialization file called ~.conkyrc and the following is what is in mine. Here, conky displays a line of text at a half-second interval. The line after TEXT controls what conky displays.

own_wndow yes     # Need for display glicthes
double_buffer yes # ditto
update_interval 1 # Every second
total_run_times 0 # Run for ever
use_spacer yes    # Stop things from moving around
minimum_size 250 5
maximum_width 400
out_to_console yes

TEXT
Load: ${loadavg 1 2 3} Cpu: %${cpu} Mem: ${mem} \
Swap: ${swap} Wifi: ${wireless_essid wlan0} ${wireless_bitrate wlan0} \
${wireless_link_qual_perc wlan0} \
${exec acpi | awk '/Discharging/{print "^fg(orange)Bat:", $4,"^fg()"}/Charging/{print "Bat:", $4}'}  \
Audio: ${exec amixer -c 0 get Master | grep Mono: | cut -d " " -f8|tr -d "[]"} \
${time %b %d %I:%M:%S}

Running the following command will show a status bar with some useful information.

conky|dzen2 -x 400 -ta r

We can now see how to configure Xmonad to start conky and dzen2.

import XMonad
import XMonad.Config.Desktop

conkyStatusBar = "conky |dzen2 -x 400 -y 0 -h 16 -ta r"

main = do
      spawnPipe conkyStatusBar
      xmonad desktopConfig {
          modMask = mod4Mask
      }

Displaying window manager log

All window managers maintain the current state of the desktop and xmonad is no exception. Xmonad has a logHook which is called whenever its internal state changes. It would be nice to see the list of workspaces, current workspace, title of window with focus. To do this, we specify a hook dynamicLogWithPP whose configuration is customized to pipe the log information to another dzen2 process.

import System.IO
import XMonad
import XMonad.Config.Desktop
import XMonad.Util.Run (spawnPipe)
import XMonad.Hooks.DynamicLog (defaultPP, ppOutput, ppOrder, dynamicLogWithPP)
import XMonad.Hooks.EwmhDesktops (ewmhDesktopsLogHook)
import XMonad.Hooks.SetWMName (setWMName)

foreground = "yellow"
conkyStatusBar = "conky |dzen2 -x 400 -y 0 -h 16  -ta r"

xmonadStatusBar = "dzen2 -ta l -x 0 -y 0 -w 600 -e 'entertitle=uncollapse' -h 16 " ++ "\
        \-bg " ++ background ++ " -fg " ++ foreground

main = do
      spawnPipe conkyStatusBar
      spawnPipe xmonadStatusBar
      xmproc <- spawnPipe xmonadStatusBar
      xmonad desktopConfig {
          modMask = mod4Mask
          , logHook    = dynamicLogWithPP $ defaultPP
                         { ppOutput = hPutStrLn xmproc
                           , ppOrder = take 3 . drop 0
                          }
      }

Configuring terminal window

The default terminal window in my environment is xterm. I like urxvt with the following parameters.

myTerminal = "urxvt -bg black -fg yellow -fade 30 -fadecolor white +sb"

Adding this to my xmonad.hs, it now looks as below.

import System.IO
import XMonad
import XMonad.Config.Desktop
import XMonad.Util.Run (spawnPipe)
import XMonad.Hooks.DynamicLog (defaultPP, ppOutput, ppOrder, dynamicLogWithPP)
import XMonad.Hooks.EwmhDesktops (ewmhDesktopsLogHook)
import XMonad.Hooks.SetWMName (setWMName)

foreground = "yellow"
conkyStatusBar = "conky |dzen2 -x 400 -y 0 -h 16  -ta r"

xmonadStatusBar = "dzen2 -ta l -x 0 -y 0 -w 600 -e 'entertitle=uncollapse' -h 16 "

myTerminal = "urxvt -bg black -fg yellow -fade 30 -fadecolor white"

main = do
      spawnPipe conkyStatusBar
      spawnPipe xmonadStatusBar
      xmproc <- spawnPipe xmonadStatusBar
      xmonad desktopConfig {
          modMask = mod4Mask
          , logHook    = dynamicLogWithPP $ defaultPP
                         { ppOutput = hPutStrLn xmproc
                           , ppOrder = take 3 . drop 0
                          }
          , terminal = myTerminal
      }


Function key support

At this point, many Fn keys don't work as expected. Support for multimedia keys can be added by extending the desktopConfig element with `additionalKeys` method. Muting, volume control, brightness control are all done by executing an OS command. Details depend on your specific system configuration and here is what works on mine.

import Graphics.X11.ExtraTypes.XF86 (xF86XK_AudioLowerVolume, xF86XK_AudioRaiseVolume, xF86XK_AudioMute, xF86XK_MonBrightnessDown, xF86XK_MonBrightnessUp)
import System.IO
import XMonad
import XMonad.Config.Desktop
import XMonad.Hooks.DynamicLog (defaultPP, ppOutput, ppOrder, dynamicLogWithPP)
import XMonad.Hooks.EwmhDesktops (ewmhDesktopsLogHook)
import XMonad.Hooks.SetWMName (setWMName)
import XMonad.Prompt (defaultXPConfig)
import XMonad.Prompt.RunOrRaise (runOrRaisePrompt)
import XMonad.Util.EZConfig (additionalKeys)
import XMonad.Util.Run (spawnPipe)

conkyStatusBar = "conky |dzen2 -x 400 -y 0 -h 16  -ta r"

xmonadStatusBar = "dzen2 -ta l -x 0 -y 0 -w 600 -e 'entertitle=uncollapse' -h 16 "

myTerminal = "urxvt -bg black -fg yellow -fade 30 -fadecolor white"

main = do
      spawnPipe conkyStatusBar
      spawnPipe xmonadStatusBar
      xmproc <- spawnPipe xmonadStatusBar
      xmonad $ desktopConfig {
          modMask = mod4Mask
          , logHook    = dynamicLogWithPP $ defaultPP
                         { ppOutput = hPutStrLn xmproc
                           , ppOrder = take 3 . drop 0
                          }
          , terminal = myTerminal
        }
        `additionalKeys`
          [((mod4Mask .|. controlMask, xK_f), runOrRaisePrompt defaultXPConfig)
           , ((0, xF86XK_AudioLowerVolume), spawn "amixer set Master 1-")
           , ((0, xF86XK_AudioRaiseVolume), spawn "amixer set Master 1+")
           , ((0, xF86XK_AudioMute), spawn "amixer -D pulse set Master 1+ toggle")
           , ((0, xF86XK_MonBrightnessDown), spawn "xbacklight -10")
           , ((0, xF86XK_MonBrightnessUp), spawn "xbacklight +10")
          ]

With these key bindings, we can now control audio volume and monitor brightness. A visual indication of this would be a nice addition. We can get the audio status from amixer and massaging the output through a few utilities as shown below. We augment our .conkyrc with this snippet and we are done!

Vol: ${exec amixer -c 0 get Master | grep Mono: | cut -d " " -f8|tr -d "[]"}

Startup actions

Now for the very last bits of my configuration. When I start my session, I would like to have my editor, browser and a few terminal windows started automatically. In addition, I would like to see them moved to a different virtual workspace. xmonad's startupHook is the ideal place to spawn what we need. Now the desktopConfig is augmented with more spawn commands.

xmonad $ desktopConfig {
    ...
    , startupHook = do
        spawnOnce "google-chrome --class chrome"
        spawnOnce "urxvt --title cljswatch -fg yellow -bg black -cd /home/praki/workspace/MonadicT.github.io"

  }

Moving the window to predetermined workspaces is accomplished using xmonad's mnagaeHook, Below is our version where windows are floated (for pop ups) or moved to workspaces. We can use any attribute of the window and take suitable action. What we are doing below is moving chrome to wodkspace 2, windows with title "logs" to workspace 3 etc.

We set xmonad's manageHook by combining the default hook with ours as shown below.

myManageHook
    = composeAll . concat $
      [ [className =? c --> doFloat | c <- myFloats]
      , [title =? t --> doFloat | t <- myOtherFloats]
      , [resource =? r --> doFloat | r <- myIgnores]
      , [className =? "Gimp" --> doF (W.shift "5:misc")]
      , [className =? "VirtualBox" --> doF (W.shift "5:misc")]
      , [className =? "Wine" --> doF (W.shift "5:misc")]
      , [className =? "chrome" --> doShift "2"]
      , [title =? "logs" --> doShift "3"]
      , [title =? "cljswatch" --> doShift "4"]
      ]
    where
      myFloats = ["Gimp", "Nvidia-settings", "XCalc", "XFontSel", "Xmessage", "xmms"]
      myOtherFloats = ["Downloads", "Firefox - Restore Previous Session", "Save As...", "Ediff", "Ediff<2>"]
      myIgnores = []

 xmonad $ desktopConfig {
          ...
          , manageHook = myManageHook <+> manageHook defaultConfig
}