Xmonad/xmonad development tutorial

From HaskellWiki
< Xmonad
Revision as of 19:19, 20 February 2008 by Byorgey (talk | contribs) (more content)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

So, you've got an idea for a great extension to xmonad, and you think other people will enjoy using it, too. The only problem is, you've never written an xmonad extension before, "darcs" sounds scary, and you're not sure what the proper procedures are. Well, never fear --- this tutorial will have you writing xmonad extensions in no time!

As a running example, let's suppose you want to develop an extension to pop up a "Hello world" message when the user presses a certain key combination. We'll walk through the entire process of creating, testing, and submitting such an extension. I encourage you to follow along, either by copying and pasting the code shown here, or you can just use it as a guideline for making your own extension.

Before we begin, here are some other resources which may be useful to you during this process:

Getting started

Getting the latest darcs sources

The first step in creating an xmonad extension is to get the latest development sources using darcs. darcs is a distributed revision control system, written in Haskell, which is used to track the source code for xmonad and the xmonad-contrib extension library. If you already have the darcs sources, you can skip this section.

First, of course, you will have to install darcs itself; it probably already exists as a package for your operating system's package manager. Otherwise, you can grab a tarball from darcs.net.

Next, create a directory where you will store the xmonad repositories.

[brent@xenophon:~/haskell]$ mkdir xmonad-dev-tut
[brent@xenophon:~/haskell]$ cd xmonad-dev-tut/
[brent@xenophon:~/haskell/xmonad-dev-tut]$ 

Now, use 'darcs get' to create new directories and download the current repositories into them:

[brent@xenophon:~/haskell/xmonad-dev-tut]$ darcs get http://code.haskell.org/xmonad
Copying patch 941 of 941... done!
Applying patch 941 of 941... done.
Finished getting.
[brent@xenophon:~/haskell/xmonad-dev-tut]$ darcs get http://code.haskell.org/XMonadContrib
Copying patch 1292 of 1292... done!
Applying patch 1292 of 1292... done.
Finished getting.
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ls
xmonad  XMonadContrib

Be patient, it might take a while to download the entire repositories the first time. After the initial download, however, subsequent pulls should be much faster, since darcs will only need to download any new patches.

Congratulations, you now have the latest xmonad sources!

Staying up-to-date

To keep your local repositories up-to-date, you should periodically run the command 'darcs pull' in each directory in order to download any new patches, especially before starting any new development. Darcs is generally good at merging patches, but it's best not to make it work any harder than necessary.

Building

To build the latest development versions of xmonad and xmonad-contrib, just issue the usual incantations:

[brent@xenophon:~/haskell/xmonad-dev-tut]$ cd xmonad/
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad]$ runhaskell Setup configure --prefix=$HOME --user && runhaskell Setup build && runhaskell Setup install
Configuring xmonad-0.6...
Preprocessing library xmonad-0.6...
Preprocessing executables for xmonad-0.6...
Building xmonad-0.6...
...blah blah...
Writing new package config file... done.
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad]$ cd ../XMonadContrib/
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ runhaskell Setup configure --prefix=$HOME --user && runhaskell Setup build && runhaskell Setup install
Configuring xmonad-contrib-0.6...
Preprocessing library xmonad-contrib-0.6...
Building xmonad-contrib-0.6...
...blah blah BLAH...
Writing new package config file... done.

Now restart xmonad (run 'touch ~/.xmonad/xmonad.hs', then hit mod-q). Note, if you are upgrading from a previous version of xmonad, you may need to first 'unregister' the old packages with ghc. Type 'ghc-pkg list xmonad' and 'ghc-pkg list xmonad-contrib' at a prompt; if multiple versions are listed you need to unregister all but the newest. For example:

[brent@xenophon:~/haskell/xmonad-dev-tut]$ ghc-pkg unregister xmonad-0.5 --user
Saving old package config file... done.
Writing new package config file... done.
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ghc-pkg unregister xmonad-contrib-0.5 --user
Saving old package config file... done.
Writing new package config file... done.

An important note!

If you pull new patches into the xmonad repository or make changes to it, and rebuild the xmonad library, you should do a clean build of the xmonad-contrib library afterwards ('runhaskell Setup clean && runhaskell Setup configure --prefix=... && ... build && ... install'). Otherwise, the Cabal build process for xmonad-contrib may not notice that the xmonad library (which xmonad-contrib depends on) has changed, and xmonad-contrib will not get rebuilt properly, leading to possible crashes at runtime. Annoying, but necessary.

Creating a module

Adding the module file

So, let's add that module! We'll call it XMonad.Actions.HelloWorld, so it needs to go in the XMonad/Actions/ subdirectory of the XMonadContrib repository:

[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ $EDITOR XMonad/Actions/HelloWorld.hs &

To avoid flame wars, I won't reveal my favorite editor here. =)

Now we've got... a nice, blank module! What now? Well, let's begin by adding some standard header stuff. In practice you can just open up some other module and copy and paste, changing the stuff that's appropriate to change.

-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.HelloWorld
-- Copyright   :  (c) 2008 Brent Yorgey
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Brent Yorgey <byorgey@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Provides an action to pop up a "hello, world" window using xmessage.
--
-----------------------------------------------------------------------------

module XMonad.Actions.HelloWorld where

OK, that's a good start. Now let's go to a prompt and see what we've done so far:

[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs whatsnew
No changes!

Huh? No changes? Well, that's because we haven't added HelloWorld.hs to the repository yet, so darcs ignores it. Let's do that now:

[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs add XMonad/Actions/HelloWorld.hs 
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs whatsnew
{
addfile ./XMonad/Actions/HelloWorld.hs
hunk ./XMonad/Actions/HelloWorld.hs 1
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  XMonad.Actions.HelloWorld
...

Ah, much better! In general, the 'darcs whatsnew' command shows any changes that are currently unrecorded. To finish things off for now, we add the helloWorld function, and a required import (XMonad.Core is where we get the spawn function).

import XMonad.Core

helloWorld = spawn "xmessage 'Hello, world!'"

Adding our new module to the xmonad-contrib library

Our new module is great, but it won't get compiled as part of the xmonad-contrib library unless we add a reference to it in xmonad-contrib.cabal.

[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ $EDITOR xmonad-contrib.cabal
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs whatsnew -u xmonad-contrib.cabal 
What's new in "xmonad-contrib.cabal":

{
hunk ./xmonad-contrib.cabal 74
                         XMonad.Actions.FlexibleResize
                         XMonad.Actions.FloatKeys
                         XMonad.Actions.FocusNth
+                        XMonad.Actions.HelloWorld
                         XMonad.Actions.MouseGestures
                         XMonad.Actions.MouseResize
                         XMonad.Actions.NoBorders
}

The + indicates the line that we added to the .cabal file. (Note that the module list is generally kept in alphabetical order.) Be sure to indent with spaces, not tabs! Since indentation is important in .cabal files (just as in Python or Haskell), tabs are not allowed.

Building and testing

We first rebuild and reinstall the xmonad-contrib library. As you can see from the helpful Cabal error, we actually need to reconfigure first, since we changed the xmonad-contrib.cabal file.

[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ runhaskell Setup build && runhaskell Setup install
Setup: xmonad-contrib.cabal has been changed, please re-configure.
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ runhaskell Setup configure --prefix=$HOME --user && runhaskell Setup build && runhaskell Setup install
Configuring xmonad-contrib-0.6...
Preprocessing library xmonad-contrib-0.6...
Building xmonad-contrib-0.6...
[ 91 of 112] Compiling XMonad.Actions.HelloWorld ( XMonad/Actions/HelloWorld.hs, dist/build/XMonad/Actions/HelloWorld.o )
/usr/bin/ar: creating dist/build/libHSxmonad-contrib-0.6.a
...

Of course, it's annoying to always type out the entire incantation for configuring, building, and installing. I have created two bash aliases for this purpose, basically defined as:

alias hinstu='runhaskell Setup configure --prefix=$HOME --user && runhaskell Setup build && runhaskell Setup install'
alias hinst='runhaskell Setup configure && runhaskell Setup build && sudo runhaskell Setup install'

From now on, I'll just use these and assume that you have something similar.

Now, to test out our new module, we can just import it into xmonad.hs, add a keybinding for the helloWorld action, and restart with mod-q. Now (assuming you have the xmessage utility installed) hitting the selected keybinding should pop up a little "Hello, world" window in the upper-left corner of the screen. Neat!

Clean-up and coding standards

Well, our module works, but it's in no state to be distributed to the wider world yet! We'll have to clean it up to conform to a few standards for xmonad-contrib source code. In particular:

  • All exported functions should have Haddock documentation and explicit type signatures.
  • A "Usage" section should be added in the comments, explaining the purpose of the module does and giving examples of its use.
  • It's usual to explicitly list the module's exports, rather than implicitly exporting everything.

There are xmonad other standards, too, but these are the most relevant to us at the moment.

Here's our module after making these updates (not including the header comments):

module XMonad.Actions.HelloWorld (
                                  -- * Usage
                                  -- $usage

                                  helloWorld

                                 ) where

import XMonad.Core

-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Actions.HelloWorld
--
-- Then add a keybinding for 'helloWorld':
--
-- >   , ((modMask x .|. controlMask, xK_h), helloWorld)
--
-- For detailed instructions on editing your key bindings, see
-- "XMonad.Doc.Extending#Editing_key_bindings".

-- | Pop up a "hello, world" window using @xmessage@.  You /must/ have
--   the @xmessage@ utility installed for this to work.
helloWorld :: X ()
helloWorld = spawn "xmessage 'Hello, world!'"

Note how we added a type signature and Haddock documentation for helloWorld(the pipe character | indicates a Haddock comment), and some usage information which will get included in the generated Haddock documentation (Haddock generates documentation for exported functions in the order they are listed in the export list between parentheses following the module declaration, along with any extra comments included there). Some quick notes about Haddock documentation format:

  • Use @ symbols to surround code which should be printed in a verbatim style.
  • Use 'single quotes' around function names to generate links to their documentation. For example, see how 'helloWorld' has single quotes in the usage information shown above.
  • Use "double quotes" around module names to generate links to their documentation.
  • Use /front slashes/ to italicize.
  • Escape literal quotes and frontslashes with a backslash.
  • Literal code blocks can be included with "bird tracks", i.e. greater-than symbols preceding the code. Be sure to put a blank line on either side of such code blocks.

For more information, see the Haddock documentation.

Testing documentation

It's really important to test the generated documentation before submitting our new extension. A module with poor or missing documentation (or great documentation which is not included because of parse errors) will not be very useful to people. Conversely, a module with excellent documentation and good examples will be a pleasure to use, easier to modify and extend, and a help to others trying to use the code as an example for creating their own extension!

To generate the documentation, first rebuild the xmonad-contrib library, then use the 'haddock' option to runhaskell Setup:

[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ hinstu
Configuring xmonad-contrib-0.6...
...blah blah...
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ runhaskell Setup haddock
Preprocessing library xmonad-contrib-0.6...
Running Haddock for xmonad-contrib-0.6...
...lots of warnings that can be ignored...
Documentation created: dist/doc/html/xmonad-contrib/index.html

Generating the Haddock documentation will generate a ton of warnings such as "could not find link destinations for: M.Map"; you can ignore these. The important line is the last one, which says whether the documentation was successfully created, and where it was generated. In this case, to view the generated documentation, you should point your browser to something like file:///path/to/XMonadContrib/dist/doc/html/xmonad-contrib/index.html. Check that the formatting, links, and so on are correct. For example, when first writing this tutorial, I forgot to escape the double quotes around "hello world" in the comments for the helloWorld function, which therefore showed up as a link to the (nonexistent) "hello world" module, instead of showing literal double quote characters. If you make any changes to your documentation, you can simply rerun 'runhaskell Setup haddock' to regenerate; there's no need to rebuild everything as long as you only changed documentation.

Once you are satisfied with your documentation, it's probably a good idea to rebuild the xmonad-contrib library, restart xmonad, and test your extension one more time.

Submitting your extension