diff --git a/SnowGlobe.hs b/SnowGlobe.hs
--- a/SnowGlobe.hs
+++ b/SnowGlobe.hs
@@ -1,8 +1,8 @@
 -- SnowGlobe -- randomized fractal snowflakes demo
--- (GPL3+) 2012 Claude Heiland-Allen <claudiusmaximus@goto10.org>
+-- (GPL3+) 2012 Claude Heiland-Allen <claude@mathr.co.uk>
 -- usage: snowglobe
--- 'f' toggles full screen, any other key to quit
--- tested with: ghc-7.0.4, ghc-7.4-rc1
+-- 'f' toggles full screen, 'shift-R' toggles record, any other key to quit
+-- tested with: ghc-7.0.4, ghc-7.4-rc1, ghc-7.4.2
 
 import Numeric.LinearAlgebra hiding (i, reshape)
 import qualified Numeric.LinearAlgebra as L
@@ -24,10 +24,12 @@
 import qualified Data.Map as M
 import qualified Data.Set as S
 import System.Exit (exitSuccess)
-import System.IO (hPutStrLn, stderr)
+import System.IO (hPutStrLn, stderr, stdout)
 import System.Random (randomRIO)
 import Unsafe.Coerce (unsafeCoerce) -- for OpenGL newtypes
 import Foreign (alloca, peek, nullPtr, withArray)
+import Graphics.Rendering.OpenGL.Capture (capturePPM)
+import Data.ByteString (hPut)
 
 shader :: Maybe String -> Maybe String -> IO Program
 shader mV mF = do
@@ -95,6 +97,7 @@
   , sFlakes :: [Flake], sTextures :: Map N TextureObject
   , wSize :: Size, wFullScreen :: Maybe Size
   , sRenderFlake :: IO Render, sNextName :: N
+  , sRecord :: Bool
   }
 
 data Flake = Flake
@@ -338,6 +341,7 @@
     , tPing = tPing', tPong = tPong', fBuffer = fBuffer'
     , sFlakes = [], sTextures = M.empty, wSize = wSize', wFullScreen = Nothing
     , sNextName = 0, sRenderFlake = return undefined
+    , sRecord = False
     }
   modifyIORef sR $ \s'->s'{ sRenderFlake = flakeRenderStart sR }
   addTimerCallback 40 timer
@@ -349,6 +353,7 @@
 keyboard :: IORef SnowGlobe -> Key -> KeyState -> Modifiers -> Position -> IO ()
 keyboard sR (SpecialKey KeyF11) Down _ _ = toggleFullScreen sR
 keyboard sR (Char 'f') Down _ _ = toggleFullScreen sR
+keyboard sR (Char 'R') Down _ _ = modifyIORef sR $ \s -> s{ sRecord = not (sRecord s) }
 keyboard _ (Char _) Down _ _ = exitSuccess
 keyboard _ _ _ _ _ = return ()
 
@@ -424,6 +429,7 @@
   blend $= Disabled
   texture Texture2D $= Disabled
   swapBuffers
+  when (sRecord s) $ hPut stdout =<< capturePPM
   reportErrors
   display' sR
 
diff --git a/snowglobe.cabal b/snowglobe.cabal
--- a/snowglobe.cabal
+++ b/snowglobe.cabal
@@ -1,16 +1,44 @@
 Name:                snowglobe
-Version:             1
+Version:             2
 Synopsis:            randomized fractal snowflakes demo
 Description:         @snowglobe@ generates random snowflakes using iterated
                      function systems via OpenGL texture feedback.  The
                      snowflakes interact in a particle system.
+                     .
+                     Keyboard controls:
+                     .
+                       * f, F11: toggle full screen
+                     .
+                       * shift-R: toggle recording PPM images to stdout
+                     .
+                       * any other key: quit
+
 License:             GPL-3
 License-file:        LICENSE
 Author:              Claude Heiland-Allen
-Maintainer:          claudiusmaximus@goto10.org
+Maintainer:          claude@mathr.co.uk
 Category:            Demo
 Build-type:          Simple
 Cabal-version:       >=1.6
+
 Executable snowglobe
   Main-is:             SnowGlobe.hs
-  Build-depends:       base < 5, containers == 0.4.*, random == 1.0.*, GLUT == 2.2.*, OpenGLRaw == 1.1.*, hmatrix == 0.13.*
+  GHC-options:         -Wall
+  Build-depends:
+    base < 5,
+    bytestring,
+    containers,
+    gl-capture,
+    GLUT,
+    hmatrix,
+    OpenGLRaw,
+    random
+
+Source-repository head
+  type:                git
+  location:            git://gitorious.org/maximus/snowglobe.git
+
+Source-repository this
+  type:                git
+  location:            git://gitorious.org/maximus/snowglobe.git
+  tag:                 v2
