Difference between revisions of "Xmonad/xmonad development tutorial"

From HaskellWiki
Jump to: navigation, search
m
(update for github etc.)
Line 15: Line 15:
 
you during this process:
 
you during this process:
   
* [http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf YAHT] and the [http://en.wikibooks.org/wiki/Haskell Haskell wikibook] are excellent introductions to the Haskell programming language. [http://learnyouahaskell.com LYAH] is worth a look as well.
 
  +
* [https://github.com/bitemyapp/learnhaskell See here for recommended resources for learning Haskell.]
 
* [http://haskell.org/haskellwiki/Xmonad/Guided_tour_of_the_xmonad_source Guided tour of the xmonad source code].
 
* [http://haskell.org/haskellwiki/Xmonad/Guided_tour_of_the_xmonad_source Guided tour of the xmonad source code].
 
* Automatically-generated [http://www.xmonad.org/xmonad-docs/ documentation for xmonad and xmonad-contrib].
 
* Automatically-generated [http://www.xmonad.org/xmonad-docs/ documentation for xmonad and xmonad-contrib].
* [http://www.darcs.net/manual/ darcs documentation].
 
 
* [http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html#8 xmonad coding style guidelines].
 
* [http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html#8 xmonad coding style guidelines].
 
* [http://www.haskell.org/haddock/doc/html/index.html Haddock documentation].
 
* [http://www.haskell.org/haddock/doc/html/index.html Haddock documentation].
Line 26: Line 25:
 
== Getting started ==
 
== Getting started ==
   
=== Getting the latest darcs sources ===
+
=== Getting the latest sources ===
   
 
The first step in creating an xmonad extension is to get the latest
 
The first step in creating an xmonad extension is to get the latest
development sources using [http://darcs.net/ darcs]. darcs is a distributed revision
+
development sources from github. If you
control system, written in Haskell, which is used to track the source
+
already have the xmonad sources, you can skip this section.
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
 
  +
First, create an account on [https://github.com github] if you don't have one already. Then visit the [https://github.com/xmonad/xmonad xmonad repository on github] and click the "Fork" button to clone it into your personal account. Do the same for the [https://github.com/xmonad/xmonad-contrib xmonad-contrib repository].
already exists as a package for your operating system's package
 
  +
manager. Otherwise, you can grab a tarball from darcs.net.
 
  +
Next, create a local directory where you will store the xmonad repositories.
 
Next, create a directory where you will store the xmonad repositories.
 
   
 
[brent@xenophon:~/haskell]$ mkdir xmonad-dev-tut
 
[brent@xenophon:~/haskell]$ mkdir xmonad-dev-tut
Line 42: Line 39:
 
[brent@xenophon:~/haskell/xmonad-dev-tut]$
 
[brent@xenophon:~/haskell/xmonad-dev-tut]$
   
Now, use <code>darcs get</code> to create new directories and download the
+
Now, use the <code>git</code> command to clone the forked repos locally:
current repositories into them:
 
   
[brent@xenophon:~/haskell/xmonad-dev-tut]$ darcs get http://code.haskell.org/xmonad
+
[brent@xenophon:~/haskell/xmonad-dev-tut]$ git clone git@github.com:byorgey/xmonad-contrib.git
Copying patch 941 of 941... done!
+
Cloning into 'xmonad-contrib'...
Applying patch 941 of 941... done.
+
remote: Counting objects: 13578, done.
Finished getting.
+
remote: Total 13578 (delta 0), reused 0 (delta 0), pack-reused 13578
[brent@xenophon:~/haskell/xmonad-dev-tut]$ darcs get http://code.haskell.org/XMonadContrib
+
Receiving objects: 100% (13578/13578), 9.21 MiB | 2.00 MiB/s, done.
Copying patch 1292 of 1292... done!
+
Resolving deltas: 100% (9879/9879), done.
Applying patch 1292 of 1292... done.
+
Checking connectivity... done.
Finished getting.
+
[brent@xenophon:~/haskell/xmonad-dev-tut]$ git clone git@github.com:byorgey/xmonad.git
  +
...
 
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ls
 
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ls
xmonad XMonadContrib
+
xmonad xmonad-contrib
 
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.
 
   
  +
(Of course, you should replace <code>byorgey</code> with your own username.)
 
Congratulations, you now have the latest xmonad sources!
 
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 <code>darcs pull</code> 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 ===
 
=== Building ===
   
To build the latest development versions of xmonad and xmonad-contrib,
+
To build the latest development versions of <code>xmonad</code> and <code>xmonad-contrib</code>, navigate to the parent directory and issue the command:
just issue the usual incantations:
 
   
[brent@xenophon:~/haskell/xmonad-dev-tut]$ cd xmonad/
 
  +
cabal install xmonad/ xmonad-contrib/
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad]$ cabal install
 
Configuring xmonad-0.9.1...
 
Preprocessing library xmonad-0.9.1...
 
Preprocessing executables for xmonad-0.9.1...
 
Building xmonad-0.9.1...
 
...blah blah...
 
Writing new package config file... done.
 
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad]$ cd ../XMonadContrib/
 
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ cabal install
 
Configuring xmonad-contrib-0.9.1...
 
Preprocessing library xmonad-contrib-0.9.1...
 
Building xmonad-contrib-0.9.1...
 
...blah blah BLAH...
 
Writing new package config file... done.
 
   
 
Now restart xmonad (run <code>touch ~/.xmonad/xmonad.hs</code>, 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 <code>ghc-pkg list xmonad</code> and <code>ghc-pkg list xmonad-contrib</code> at a prompt; if multiple versions are listed you need to unregister all but the newest. For example:
 
Now restart xmonad (run <code>touch ~/.xmonad/xmonad.hs</code>, 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 <code>ghc-pkg list xmonad</code> and <code>ghc-pkg list xmonad-contrib</code> 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
+
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ghc-pkg unregister xmonad-0.5
 
Saving old package config file... done.
 
Saving old package config file... done.
 
Writing new package config file... done.
 
Writing new package config file... done.
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ghc-pkg unregister xmonad-contrib-0.5 --user
+
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ghc-pkg unregister xmonad-contrib-0.5
 
Saving old package config file... done.
 
Saving old package config file... done.
 
Writing new package config file... done.
 
Writing new package config file... done.
Line 97: Line 73:
   
 
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 (<code>cabal clean && cabal install</code>). 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.
 
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 (<code>cabal clean && cabal install</code>). 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.
 
=== An important Haddock/documentation note ===
 
 
The version of [[Haddock]] which is still bundled in several OS package systems (Debian unstable, for example) is too old to handle some of the features used in xmonad. The most recent version of haddock (as of 15 November 2010) is 2.8.1. Until it is updated in your OS's package system, you will have to install the latest version of haddock manually; you can download it from [http://hackage.haskell.org Hackage]. Or, if you have the fabulous [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/cabal%2Dinstall cabal-install tool], you can just type <code>cabal install haddock</code> at a prompt.
 
   
 
== Creating an extension ==
 
== Creating an extension ==
Line 106: Line 78:
 
=== Adding the module file ===
 
=== 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:
+
So, let's add that module! We'll call it <code>XMonad.Actions.HelloWorld</code>, so it needs to go in the <code>XMonad/Actions/</code> subdirectory of the <code>xmonad-contrib</code> repository:
   
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ $EDITOR XMonad/Actions/HelloWorld.hs &
+
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ $EDITOR XMonad/Actions/HelloWorld.hs &
   
 
To avoid flame wars, I won't reveal my favorite editor here. =)
 
To avoid flame wars, I won't reveal my favorite editor here. =)
Line 134: Line 106:
 
OK, that's a good start. Now let's go to a prompt and see what we've done so far:
 
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
+
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ git status
No changes!
+
On branch master
  +
Your branch is up-to-date with 'origin/master'.
  +
Untracked files:
  +
(use "git add <file>..." to include in what will be committed)
  +
  +
HelloWorld.hs
  +
  +
nothing added to commit but untracked files present (use "git add" to track)
   
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:
 
  +
<code>HelloWorld.hs</code> is so far not being tracked by <code>git</code>; we can follow the given suggestion to add it:
   
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs add XMonad/Actions/HelloWorld.hs
+
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ git 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 <code>darcs whatsnew</code> command shows any changes to files tracked by darcs that are currently unrecorded. To finish things off for now, we add the <hask>helloWorld</hask> function, and a required import (<hask>XMonad.Core</hask> is where we get the <hask>spawn</hask> function).
+
To finish things off for now, we add the <hask>helloWorld</hask> function, and a required import (<hask>XMonad.Core</hask> is where we get the <hask>spawn</hask> function).
   
 
<haskell>
 
<haskell>
Line 152: Line 124:
 
=== Adding our new module to the xmonad-contrib library ===
 
=== 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.
+
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 <code>xmonad-contrib.cabal</code>.
   
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ $EDITOR xmonad-contrib.cabal
+
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ $EDITOR xmonad-contrib.cabal
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs whatsnew -u xmonad-contrib.cabal
+
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ git diff
What's new in "xmonad-contrib.cabal":
+
index 490cec2..743f6d4 100644
+
--- a/XMonad/Actions/HelloWorld.hs
{
+
+++ b/XMonad/Actions/HelloWorld.hs
hunk ./xmonad-contrib.cabal 74
+
@@ -13,3 +13,8 @@
XMonad.Actions.FlexibleResize
+
-----------------------------------------------------------------------------
XMonad.Actions.FloatKeys
+
  +
module XMonad.Actions.HelloWorld where
  +
+
  +
+import XMonad.Core
  +
+
  +
+helloWorld :: X ()
  +
+helloWorld = spawn "xmessage 'Hello, world!'"
  +
diff --git a/xmonad-contrib.cabal b/xmonad-contrib.cabal
  +
index 12721ce..4dc9fda 100644
  +
--- a/xmonad-contrib.cabal
  +
+++ b/xmonad-contrib.cabal
  +
@@ -109,6 +109,7 @@ library
 
XMonad.Actions.FocusNth
 
XMonad.Actions.FocusNth
  +
XMonad.Actions.GridSelect
  +
XMonad.Actions.GroupNavigation
 
+ XMonad.Actions.HelloWorld
 
+ XMonad.Actions.HelloWorld
XMonad.Actions.MouseGestures
+
XMonad.Actions.Launcher
XMonad.Actions.MouseResize
+
XMonad.Actions.LinkWorkspaces
XMonad.Actions.NoBorders
+
XMonad.Actions.MessageFeedback
}
 
   
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.
+
The + lines indicate additions that we have made. Note in particular the line we added to the .cabal file. (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 ===
 
=== Building and testing ===
Line 174: Line 148:
 
We first rebuild and reinstall the xmonad-contrib library.
 
We first rebuild and reinstall the xmonad-contrib library.
   
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ cabal install
+
[brent@xenophon:~/haskell/xmonad-dev-tut]$ cabal install xmonad-contrib/
 
Configuring xmonad-contrib-0.9.1...
 
Configuring xmonad-contrib-0.9.1...
 
Preprocessing library xmonad-contrib-0.9.1...
 
Preprocessing library xmonad-contrib-0.9.1...
Line 242: Line 216:
 
To generate the documentation, first rebuild the xmonad-contrib library, then use <code>cabal haddock</code>:
 
To generate the documentation, first rebuild the xmonad-contrib library, then use <code>cabal haddock</code>:
   
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ cabal install
+
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ cabal haddock
Configuring xmonad-contrib-0.9.1...
 
...blah blah...
 
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ cabal haddock
 
 
Preprocessing library xmonad-contrib-0.9.1...
 
Preprocessing library xmonad-contrib-0.9.1...
 
Running Haddock for xmonad-contrib-0.9.1...
 
Running Haddock for xmonad-contrib-0.9.1...
Line 271: Line 245:
 
== Submitting your extension ==
 
== Submitting your extension ==
   
=== Recording your patches ===
+
=== Recording your commits ===
   
The first step is to record our changes as one or more darcs patches. First, let's see what we've changed:
+
The first step is to record our changes as one or more git commits. First, let's see what we've changed:
   
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs whatsnew -s
+
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ git status
A ./XMonad/Actions/HelloWorld.hs
+
On branch master
M ./xmonad-contrib.cabal +1
+
Your branch is up-to-date with 'origin/master'.
+
Changes to be committed:
The <code>-s</code> option makes darcs display a summary; if you leave the -s option off it will show more details about each patch.
+
(use "git reset HEAD <file>..." to unstage)
+
To record the patches, type <code>darcs record</code> at a prompt. If this is your first time recording any patches, it will first ask you for your name and e-mail address. Then it will prompt you for each change, asking whether you would like to record it. In general, you will find that darcs has a very nice interface, prompting you about most things and giving you a lot of flexibility in choosing what exactly you would like to do, without having to memorize lots of complicated command-line flags. In this particular case, it's nice that you can hand-pick which changes should be recorded as a patch -- so you could be working on several things at once, and record them separately as you finish, without messing up the other things.
+
new file: HelloWorld.hs
+
Note that for the xmonad-contrib repository, darcs is configured to run some tests before committing a patch. In particular, it will try compiling everything from scratch and generating all the documentation. If there are any errors, you must fix them before you will be allowed to record your patch. It can be a bit tiresome waiting for the tests to complete (especially if you are on a slower computer), but it ensures that no one can accidentally push a patch which breaks the repository. The repository should always be in a compilable state.
+
Changes not staged for commit:
+
(use "git add <file>..." to update what will be committed)
After recording a patch, running <code>darcs changes --last 1</code> should show you that your patch is recorded. At this point <code>darcs whatsnew</code> will once again say 'No changes!' because there are no longer any unrecorded changes.
+
(use "git checkout -- <file>..." to discard changes in working directory)
+
=== Sending your patch ===
+
modified: HelloWorld.hs
+
modified: ../../xmonad-contrib.cabal
And now, the moment of truth: sending your patch to the xmonad mailing list for inclusion in the main repository! First make sure you are subscribed; as an anti-spam measure, only list subscribers are allowed to post.
 
 
Now, if you have a mail agent configured correctly, all you have to do is type <code>darcs send</code> at the prompt, and it will send the patches you select as attachments to an e-mail to the xmonad list (<code>xmonad@haskell.org</code>). Alternatively, you can <code>darcs send</code> to an output file with the <code>-o</code> option:
 
 
[brent@xenophon:~/haskell/xmonad-dev-tut/XMonadContrib]$ darcs send -o ../helloWorld.dpatch
 
Creating patch to "http://code.haskell.org/XMonadContrib"...
 
   
Wed Feb 20 15:12:19 EST 2008 Brent Yorgey <byorgey@gmail.com>
 
  +
To commit everything, type something like <code>git commit -a -m 'new HelloWorld module'</code> at a prompt. Then push your changes up to your forked repository on github with <code>git push -u origin master</code> (the <code>-u</code> is only necessary the very first time you push).
* new contrib module: XMonad.Actions.HelloWorld, for popping up a hello world message
 
Shall I send this patch? (1/1) [ynWvpxqadjk], or ? for help: y
 
   
Then write an e-mail to the [http://www.haskell.org/mailman/listinfo/xmonad xmonad mailing list] and manually attach the generated patch file.
 
  +
=== Making a pull request ===
   
That's it! Happy xmonad hacking!
 
  +
Now open your browser and navigate to your personal fork of the repository on github. Click the "Pull Request" button, fill out the resulting form with some details, and click "submit"! Hopefully someone will be able to take a look at it soon and give you some feedback (though be patient --- xmonad development can be somewhat glacial these days!).
   
 
== Postscript ==
 
== Postscript ==

Revision as of 00:58, 9 May 2016

Xmonad-logo-small.png

XMonad

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:

If you have any questions or run into any problems while following this tutorial, feel free to ask on the xmonad mailing list, or on the #xmonad channel on irc.freenode.net.

Getting started

Getting the latest sources

The first step in creating an xmonad extension is to get the latest development sources from github. If you already have the xmonad sources, you can skip this section.

First, create an account on github if you don't have one already. Then visit the xmonad repository on github and click the "Fork" button to clone it into your personal account. Do the same for the xmonad-contrib repository.

Next, create a local 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 the git command to clone the forked repos locally:

[brent@xenophon:~/haskell/xmonad-dev-tut]$ git clone git@github.com:byorgey/xmonad-contrib.git
Cloning into 'xmonad-contrib'...
remote: Counting objects: 13578, done.
remote: Total 13578 (delta 0), reused 0 (delta 0), pack-reused 13578
Receiving objects: 100% (13578/13578), 9.21 MiB | 2.00 MiB/s, done.
Resolving deltas: 100% (9879/9879), done.
Checking connectivity... done.
[brent@xenophon:~/haskell/xmonad-dev-tut]$ git clone git@github.com:byorgey/xmonad.git
...
[brent@xenophon:~/haskell/xmonad-dev-tut]$ ls
xmonad  xmonad-contrib

(Of course, you should replace byorgey with your own username.) Congratulations, you now have the latest xmonad sources!

Building

To build the latest development versions of xmonad and xmonad-contrib, navigate to the parent directory and issue the command:

cabal install xmonad/ xmonad-contrib/

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
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
Saving old package config file... done.
Writing new package config file... done.

Important note on rebuilding

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 (cabal clean && cabal 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 an extension

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 xmonad-contrib repository:

[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ $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/xmonad-contrib]$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        HelloWorld.hs

nothing added to commit but untracked files present (use "git add" to track)

HelloWorld.hs is so far not being tracked by git; we can follow the given suggestion to add it:

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

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 :: X ()
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/xmonad-contrib]$ $EDITOR xmonad-contrib.cabal
[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ git diff
index 490cec2..743f6d4 100644
--- a/XMonad/Actions/HelloWorld.hs
+++ b/XMonad/Actions/HelloWorld.hs
@@ -13,3 +13,8 @@
 -----------------------------------------------------------------------------
 
 module XMonad.Actions.HelloWorld where
+
+import           XMonad.Core
+
+helloWorld :: X ()
+helloWorld = spawn "xmessage 'Hello, world!'"
diff --git a/xmonad-contrib.cabal b/xmonad-contrib.cabal
index 12721ce..4dc9fda 100644
--- a/xmonad-contrib.cabal
+++ b/xmonad-contrib.cabal
@@ -109,6 +109,7 @@ library
                         XMonad.Actions.FocusNth
                         XMonad.Actions.GridSelect
                         XMonad.Actions.GroupNavigation
+                        XMonad.Actions.HelloWorld
                         XMonad.Actions.Launcher
                         XMonad.Actions.LinkWorkspaces
                         XMonad.Actions.MessageFeedback

The + lines indicate additions that we have made. Note in particular the line we added to the .cabal file. (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.

[brent@xenophon:~/haskell/xmonad-dev-tut]$ cabal install xmonad-contrib/
Configuring xmonad-contrib-0.9.1...
Preprocessing library xmonad-contrib-0.9.1...
Building xmonad-contrib-0.9.1...
[ 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.9.1.a
...

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. It's a good idea to give documentation and explicit type signatures for unexported functions, too, to make it easier for others to understand or modify your code.
  • A "Usage" section should be added in the comments, explaining the purpose of the module and giving examples of its use.
  • It's usual to explicitly list the module's exports, rather than implicitly exporting everything.

There are 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 cabal haddock:

[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ cabal haddock
Preprocessing library xmonad-contrib-0.9.1...
Running Haddock for xmonad-contrib-0.9.1...
...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, then navigate to the documentation for your module. 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 cabal 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.

Source links

On newer versions of Cabal, install the hscolour package, and build your docs using the following command to include links to source from each function, type, etc.

cabal haddock --hyperlink-source

Dialing in documentation for a single module

While it's no huge chore to build all the Haddock documentation, and you should do that before submitting your extension, sometimes you just want to build documentation for one or a few modules that aren't necessarily even finished yet. The following bash alias is a quick and dirty way to build html documentation in a draft document directory for the files specified on the command line.

alias "hddck."='tmp=~/tmp/haddock; [[ -d $tmp/html ]] || mkdir $tmp/html; base=`pwd`; haddock --html --source-module file://'$base'/%F --odir $tmp/html '

Submitting your extension

Recording your commits

The first step is to record our changes as one or more git commits. First, let's see what we've changed:

[brent@xenophon:~/haskell/xmonad-dev-tut/xmonad-contrib]$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   HelloWorld.hs

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   HelloWorld.hs
        modified:   ../../xmonad-contrib.cabal

To commit everything, type something like git commit -a -m 'new HelloWorld module' at a prompt. Then push your changes up to your forked repository on github with git push -u origin master (the -u is only necessary the very first time you push).

Making a pull request

Now open your browser and navigate to your personal fork of the repository on github. Click the "Pull Request" button, fill out the resulting form with some details, and click "submit"! Hopefully someone will be able to take a look at it soon and give you some feedback (though be patient --- xmonad development can be somewhat glacial these days!).

Postscript

Questions? Suggestions? Confusions? Edit this tutorial yourself (it's a wiki, after all!), send an email to the xmonad mailing list, or join the #xmonad channel on irc.freenode.net.

For further reading, check out the guided tour of the xmonad source code, or browse the xmonad and xmonad-contrib documentation.