diff --git a/GLFW-b-demo.cabal b/GLFW-b-demo.cabal
new file mode 100644
--- /dev/null
+++ b/GLFW-b-demo.cabal
@@ -0,0 +1,45 @@
+name:         GLFW-b-demo
+version:      0.0.1
+
+category:     Graphics
+
+synopsis:     GLFW-b test\/example\/demo
+description:  GLFW-b test\/example\/demo.
+              .
+              Run 'GLFW-b-demo' and use a joystick or cursor keys to rotate an
+              RGB cube. /Please help me think of more interesting stuff to do/
+              /in here./
+
+author:       Brian Lewis <brian@lorf.org>
+maintainer:   Brian Lewis <brian@lorf.org>
+
+license:      BSD3
+license-file: LICENSE
+
+-- -- -- -- -- -- -- -- -- --
+
+cabal-version: >= 1.6
+build-type:    Simple
+
+-- -- -- -- -- -- -- -- -- --
+
+executable GLFW-b-demo
+  main-is: Main.hs
+
+  hs-source-dirs:
+    src
+
+  build-depends:
+    GLFW-b == 0.*,
+    OpenGL == 2.4.*,
+    base   == 4.*
+
+  ghc-options: -Wall -O2
+  if impl(ghc >= 6.8)
+    ghc-options: -fwarn-tabs
+
+-- -- -- -- -- -- -- -- -- --
+
+source-repository head
+  type:     git
+  location: git://github.com/bsl/GLFW-b-demo.git
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright Brian Lewis <brian@lorf.org> 2010
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import Distribution.Simple (defaultMain)
+
+main :: IO ()
+main = defaultMain
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,98 @@
+module Main (main) where
+
+import Control.Monad (liftM, unless, when)
+
+import qualified Graphics.Rendering.OpenGL as GL
+
+import qualified Graphics.UI.GLFW as GLFW
+
+import qualified Display as Display
+
+main :: IO ()
+main = do
+    configureDisplay
+    start
+    stop
+
+configureDisplay :: IO ()
+configureDisplay = do
+    GLFW.initialize
+
+    GLFW.openWindow GLFW.defaultDisplayOptions
+      { GLFW.displayOptions_numRedBits   = 5
+      , GLFW.displayOptions_numGreenBits = 6
+      , GLFW.displayOptions_numBlueBits  = 5
+      , GLFW.displayOptions_numDepthBits = 1
+      }
+
+    GLFW.setWindowSizeCallback windowSizeCallback
+
+    GL.clearColor    GL.$= GL.Color4 0.05 0.05 0.05 1
+    GL.depthFunc     GL.$= Just GL.Less
+    GL.colorMaterial GL.$= Just (GL.FrontAndBack, GL.AmbientAndDiffuse)
+    GL.shadeModel    GL.$= GL.Smooth
+
+    GL.lighting              GL.$= GL.Enabled
+    GL.lightModelAmbient     GL.$= GL.Color4 0.2 0.2 0.2 1
+    GL.position (GL.Light 0) GL.$= GL.Vertex4 (-10) 10 (-10) 0
+    GL.ambient  (GL.Light 0) GL.$= GL.Color4 0.4 0.4 0.4 1
+    GL.diffuse  (GL.Light 0) GL.$= GL.Color4 0.8 0.8 0.8 1
+    GL.light    (GL.Light 0) GL.$= GL.Enabled
+
+windowSizeCallback :: Int -> Int -> IO ()
+windowSizeCallback w h =
+    GL.viewport GL.$= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h))
+
+start :: IO ()
+start =
+    loop 0 0
+  where
+    loop xa ya = do
+        Display.draw xa ya
+        GLFW.resetTime
+
+        q0 <- GLFW.keyIsPressed GLFW.KeyEsc
+        q1 <- GLFW.keyIsPressed (GLFW.CharKey 'Q')
+        unless (q0 || q1) $ do
+            (jlr, jud) <- getJoystickDirections
+            (klr, kud) <- getCursorKeyDirections
+
+            let xa' = (xa +        jud * maxAngle) - kud
+            let ya' = (ya + negate jlr * maxAngle) - klr
+
+            t <- liftM (numSecondsBetweenFrames -) GLFW.getTime
+            when (t > 0) (GLFW.sleep t)
+
+            loop xa' ya'
+      where
+        maxAngle :: Float
+        maxAngle = 1
+
+        numSecondsBetweenFrames :: Double
+        numSecondsBetweenFrames = recip (fromIntegral framesPerSecond)
+
+        framesPerSecond :: Int
+        framesPerSecond = 200
+
+stop :: IO ()
+stop = do
+    GLFW.closeWindow
+    GLFW.terminate
+
+getJoystickDirections :: IO (Float, Float)
+getJoystickDirections = do
+    r <- take 2 `fmap` GLFW.getJoystickPosition GLFW.Joystick0 2
+    return $
+      case r of
+        [x, y] -> (x, y)
+        _      -> (0, 0)
+
+getCursorKeyDirections :: IO (Float, Float)
+getCursorKeyDirections = do
+    l <- toFloat `fmap` GLFW.keyIsPressed GLFW.KeyLeft
+    r <- toFloat `fmap` GLFW.keyIsPressed GLFW.KeyRight
+    u <- toFloat `fmap` GLFW.keyIsPressed GLFW.KeyUp
+    d <- toFloat `fmap` GLFW.keyIsPressed GLFW.KeyDown
+    return (-l + r, -u + d)
+  where
+    toFloat b = if b then 1 else 0
