diff --git a/Graphics/SOE/Gtk.hs b/Graphics/SOE/Gtk.hs
new file mode 100644
--- /dev/null
+++ b/Graphics/SOE/Gtk.hs
@@ -0,0 +1,595 @@
+-- -*-haskell-*-
+--  SOE implementation based on Gtk and cairo.
+--  Some code borrowed from SOE implementation based on OpenGL and GLFW by
+--  Paul Liu, http://www.haskell.org/soe/
+--
+--  Author : Duncan Coutts
+--
+--  Created: 10 October 2005
+--
+--  Copyright (C) 2005-2007 Duncan Coutts
+--  Copyright (C) 2007 Paul Liu
+--
+--  This library is free software; you can redistribute it and/or
+--  modify it under the terms of the GNU Lesser General Public
+--  License as published by the Free Software Foundation; either
+--  version 2.1 of the License, or (at your option) any later version.
+--
+--  This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY; without even the implied warranty of
+--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+--  Lesser General Public License for more details.
+--
+-- |
+-- Maintainer  : gtk2hs-devel@lists.sourceforge.net
+-- Stability   : stable
+-- Portability : portable (depends on GHC)
+-- 
+-- An alternative implementation of the graphics library used in The Haskell
+-- School of Expression, by Paul Hudak, <http://www.haskell.org/soe/>.
+--
+-- It has exaclty the same interface as the original implementation
+-- "Graphics.SOE". See the original for an API reference.
+--
+module Graphics.SOE.Gtk (
+  runGraphics,
+  Title,
+  Size,
+  Window,
+  openWindow,
+  getWindowSize,
+  clearWindow,
+  drawInWindow,
+  drawInWindowNow,
+  setGraphic,
+  closeWindow,
+  openWindowEx,
+  RedrawMode,
+  drawGraphic,
+  drawBufferedGraphic,
+  Graphic,
+  emptyGraphic,
+  overGraphic ,
+  overGraphics,
+  Color (..),
+  withColor,
+  text,
+  Point,
+  ellipse,
+  shearEllipse,
+  line,
+  polygon,
+  polyline,
+  polyBezier,
+  Angle,
+  arc,
+  Region,
+  createRectangle,
+  createEllipse,
+  createPolygon,
+  andRegion,
+  orRegion,
+  xorRegion,
+  diffRegion,
+  drawRegion,
+  getKey,
+  getLBP,
+  getRBP,
+  Event (..),
+  maybeGetWindowEvent,
+  getWindowEvent,
+  Word32,
+  timeGetTime,
+  word32ToInt
+  ) where
+
+import Data.List (foldl')
+import Data.Ix (Ix)
+import Data.Word (Word32)
+import Data.IORef (newIORef, readIORef, writeIORef)
+import Control.Concurrent (forkIO, yield, rtsSupportsBoundThreads)
+import Control.Concurrent.MVar
+import Control.Concurrent.Chan
+
+import qualified System.Time
+import qualified Graphics.UI.Gtk as Gtk
+
+import qualified Graphics.UI.Gtk.Cairo as Gtk.Cairo
+import qualified Graphics.Rendering.Cairo as Cairo
+import qualified Graphics.Rendering.Cairo.Matrix as Matrix
+
+-------------------
+-- Window Functions
+-------------------
+
+runGraphics :: IO () -> IO ()
+runGraphics main
+  | rtsSupportsBoundThreads = do
+      Gtk.unsafeInitGUIForThreadedRTS
+      forkIO (main >> Gtk.postGUIAsync Gtk.mainQuit)
+      Gtk.mainGUI
+  | otherwise = do
+      Gtk.initGUI
+      quitVar <- newIORef False
+      forkIO (main >> writeIORef quitVar True)
+      let loop = do
+            yield
+            Gtk.mainIteration
+            quit <- readIORef quitVar
+            if quit then return ()
+                    else loop
+      loop
+      -- give any windows a chance to close
+      Gtk.flush
+
+type Title = String
+type Size = (Int, Int)
+
+data Window = Window {
+  window :: Gtk.Window,
+  canvas :: Gtk.DrawingArea,
+  graphicVar :: MVar Graphic,
+  eventsChan :: Chan Event
+}
+
+openWindow :: Title -> Size -> IO Window
+openWindow title size =
+  openWindowEx title Nothing (Just size) drawBufferedGraphic
+
+openWindowEx :: Title -> Maybe Point -> Maybe Size -> RedrawMode -> IO Window
+openWindowEx title position size (RedrawMode useDoubleBuffer) =
+  Gtk.postGUISync $ do
+  window <- Gtk.windowNew
+  Gtk.windowSetTitle window title
+
+  canvas <- Gtk.drawingAreaNew
+  Gtk.containerAdd window canvas
+  Gtk.widgetSetCanFocus canvas True
+  Gtk.widgetSetRedrawOnAllocate canvas False
+  Gtk.widgetSetDoubleBuffered canvas useDoubleBuffer
+
+  case position of
+    Nothing     -> return ()
+    Just (x, y) -> Gtk.windowMove window x y
+  case size of
+    Nothing              -> return ()
+    Just (width, height) -> Gtk.windowSetDefaultSize window width height
+
+  Gtk.widgetShowAll window
+
+  graphicVar <- newMVar emptyGraphic
+  eventsChan <- newChan
+  timerVar   <- newMVar []
+
+  -- set up the fonts
+  pc <- Gtk.Cairo.cairoCreateContext Nothing
+  fd <- Gtk.contextGetFontDescription pc
+  Gtk.fontDescriptionSetSize fd 12
+  Gtk.fontDescriptionSetFamily fd "Sans"
+  Gtk.contextSetFontDescription pc fd
+
+  Gtk.onExpose canvas $ \Gtk.Expose { Gtk.eventArea = eventArea,
+                                      Gtk.eventRegion = exposeRegion } -> do
+    Graphic graphic <- readMVar graphicVar
+    win <- Gtk.widgetGetDrawWindow canvas
+    Gtk.Cairo.renderWithDrawable win $ do
+      -- clip to the exposed region
+      Gtk.Cairo.region exposeRegion
+      Cairo.clip
+      Cairo.paint                 --fill backgound with black
+      Cairo.setSourceRGB 1 1 1    --use white default colour
+      Cairo.setLineWidth 1.5
+      -- actually do the drawing
+      graphic pc
+    return True
+
+  Gtk.onDelete window $ \_ -> do writeChan eventsChan Closed
+                                 Gtk.widgetHide window
+                                 return True
+                     
+  Gtk.onMotionNotify canvas True $ \Gtk.Motion { Gtk.eventX=x, Gtk.eventY=y} ->
+    writeChan eventsChan MouseMove {
+      pt = (round x, round y)
+    } >> return True
+  
+  let mouseButtonHandler event@Gtk.Button { Gtk.eventX=x, Gtk.eventY=y } = do
+        writeChan eventsChan Button {
+            pt = (round x,round y),
+            isLeft = Gtk.eventButton event == Gtk.LeftButton,
+            isDown = case Gtk.eventClick event of
+                       Gtk.ReleaseClick -> False
+                       _                -> True
+          }
+        return True
+  Gtk.onButtonPress canvas mouseButtonHandler
+  Gtk.onButtonRelease canvas mouseButtonHandler
+
+  let keyPressHandler Gtk.Key { Gtk.eventKeyChar = Nothing } = return True
+      keyPressHandler Gtk.Key { Gtk.eventKeyChar = Just char, Gtk.eventRelease = release } =
+        writeChan eventsChan Key {
+                     char = char,
+                     isDown = not release
+        } >> return True
+  Gtk.onKeyPress canvas keyPressHandler
+  Gtk.onKeyRelease canvas keyPressHandler
+
+  Gtk.onSizeAllocate canvas $ \_ -> writeChan eventsChan Resize
+
+  return Window {
+    window  = window,
+    canvas  = canvas,
+    graphicVar = graphicVar,
+    eventsChan = eventsChan
+  }
+
+getWindowSize :: Window -> IO Size
+getWindowSize win = Gtk.postGUISync $ Gtk.widgetGetSize (canvas win)
+
+clearWindow :: Window -> IO ()
+clearWindow win = setGraphic win emptyGraphic
+
+drawInWindow :: Window -> Graphic -> IO ()
+drawInWindow win graphic = do
+  modifyMVar_ (graphicVar win) (return . overGraphic graphic)
+  Gtk.postGUIAsync $ Gtk.widgetQueueDraw (canvas win)
+
+drawInWindowNow :: Window -> Graphic -> IO ()
+drawInWindowNow = drawInWindow
+
+setGraphic :: Window -> Graphic -> IO ()
+setGraphic win graphic = do
+  modifyMVar_ (graphicVar win) (\_ -> return graphic)
+  Gtk.postGUIAsync $ Gtk.widgetQueueDraw (canvas win)
+
+closeWindow :: Window -> IO ()
+closeWindow win = Gtk.postGUIAsync $ Gtk.widgetHide (window win)
+
+--------------------
+-- Drawing Functions
+--------------------
+
+newtype RedrawMode = RedrawMode Bool
+
+drawGraphic :: RedrawMode
+drawGraphic = RedrawMode False
+
+drawBufferedGraphic :: RedrawMode
+drawBufferedGraphic = RedrawMode True
+
+data Color = Black
+           | Blue
+           | Green
+           | Cyan
+           | Red
+           | Magenta
+           | Yellow
+           | White
+  deriving (Eq, Ord, Bounded, Enum, Ix, Show, Read)
+
+type Angle = Float
+
+--------------------------------------------------
+-- implementation using the new cairo API
+--
+
+newtype Graphic = Graphic (Gtk.PangoContext -> Cairo.Render ())
+
+emptyGraphic :: Graphic
+emptyGraphic = Graphic (\_ -> return ())
+
+overGraphic :: Graphic -> Graphic -> Graphic
+overGraphic (Graphic over) (Graphic base) = Graphic (\pc -> base pc >> over pc)
+
+overGraphics :: [Graphic] -> Graphic
+overGraphics = foldl1 overGraphic
+
+colorToRGB :: Color -> (Double, Double, Double)
+colorToRGB Black   = (0, 0, 0)
+colorToRGB Blue    = (0, 0, 1)
+colorToRGB Green   = (0, 1, 0)
+colorToRGB Cyan    = (0, 1, 1)
+colorToRGB Red     = (1, 0, 0)
+colorToRGB Magenta = (1, 0, 1)
+colorToRGB Yellow  = (1, 1, 0)
+colorToRGB White   = (1, 1, 1)
+
+withColor :: Color -> Graphic -> Graphic
+withColor color (Graphic graphic) = Graphic $ \pc -> do
+  Cairo.save
+  case colorToRGB color of
+    (r,g,b) -> Cairo.setSourceRGB r g b
+  -- for some reason the SOE withColor uses a line width of 2,
+  -- though the default line width outside of withColor is 1.
+  Cairo.setLineWidth 2
+  graphic pc
+  Cairo.restore
+
+text :: Point -> String -> Graphic
+text (x,y) str = Graphic $ \pc -> do
+  layout <- Cairo.liftIO $ Gtk.layoutEmpty pc
+  Gtk.Cairo.updateLayout layout
+  Cairo.liftIO $ Gtk.layoutSetText layout str
+  Cairo.moveTo (fromIntegral x) (fromIntegral y)
+  Gtk.Cairo.showLayout layout
+
+type Point = (Int, Int)
+
+ellipse :: Point -> Point -> Graphic
+ellipse pt1 pt2 = Graphic $ \pc -> case normaliseBounds pt1 pt2 of
+  Nothing -> return ()
+  Just  (x,y,width,height) -> do
+    Cairo.save
+    Cairo.translate (x + width / 2) (y + height / 2)
+    Cairo.scale (width / 2) (height / 2)
+    Cairo.arc 0 0 1 0 (2*pi)
+    Cairo.fill
+    Cairo.restore
+
+shearEllipse :: Point -> Point -> Point -> Graphic
+shearEllipse (x1,y1) (x2,y2) (x3,y3) = Graphic $ \pc -> do
+  let x = fromIntegral x1
+      y = fromIntegral y1
+      scalex = fromIntegral $ abs $ x1 - x3
+      scaley = fromIntegral $ abs $ y1 - y2
+      shearx = fromIntegral $ abs $ x1 - x2
+      sheary = fromIntegral $ abs $ y1 - y3
+
+  Cairo.save
+  Cairo.transform (Matrix.Matrix scalex sheary shearx scaley x y)
+  Cairo.arc 0.5 0.5 0.5 0 (2 * pi)
+  Cairo.fill
+  Cairo.restore
+
+line :: Point -> Point -> Graphic
+line (x1, y1) (x2, y2) = Graphic $ \pc -> do
+  Cairo.moveTo (fromIntegral x1) (fromIntegral y1)
+  Cairo.lineTo (fromIntegral x2) (fromIntegral y2)
+  Cairo.stroke
+
+polygon :: [Point] -> Graphic
+polygon [] = Graphic (\_ -> return ())
+polygon ((x,y):ps) = Graphic $ \pc -> do
+  Cairo.moveTo (fromIntegral x) (fromIntegral y)
+  sequence_ [ Cairo.lineTo (fromIntegral x) (fromIntegral y)
+            | (x,y) <- ps ]
+  Cairo.fill
+
+polyline :: [Point] -> Graphic
+polyline [] = Graphic (\_ -> return ())
+polyline ((x,y):ps) = Graphic $ \pc -> do
+  Cairo.moveTo (fromIntegral x) (fromIntegral y)
+  sequence_ [ Cairo.lineTo (fromIntegral x) (fromIntegral y)
+            | (x,y) <- ps ]
+  Cairo.stroke
+
+polyBezier :: [Point] -> Graphic
+polyBezier [] = Graphic (\_ -> return ())
+polyBezier ((x,y):ps) = Graphic $ \pc -> do
+  Cairo.moveTo (fromIntegral x) (fromIntegral y)
+  let loop ((x1,y1):(x2,y2):(x3,y3):ps) = do
+        Cairo.curveTo (fromIntegral x1) (fromIntegral y1)
+                      (fromIntegral x2) (fromIntegral y2)
+                      (fromIntegral x3) (fromIntegral y3)
+        loop ps
+      loop _ = return ()
+  loop ps
+  Cairo.stroke
+
+arc :: Point -> Point -> Angle -> Angle -> Graphic
+arc pt1 pt2 start_ extent_ = Graphic $ \pc -> case normaliseBounds pt1 pt2 of
+  Nothing -> return ()
+  Just  (x,y,width,height) -> do
+    Cairo.save
+    Cairo.translate (x + width / 2) (y + height / 2)
+    Cairo.scale (width / 2) (height / 2)
+    Cairo.moveTo 0 0
+    Cairo.arcNegative 0 0 1 (-start * pi / 180) (-(start+extent) * pi / 180)
+    Cairo.fill
+    Cairo.restore
+  where start  = realToFrac start_
+        extent = realToFrac extent_
+
+-------------------
+-- Region Functions
+-------------------
+
+data Region = Region {
+  regionGraphic :: Int -> Int -> Cairo.Render (),
+  regionOriginX :: !Int,
+  regionOriginY :: !Int,
+  regionWidth   :: !Int,
+  regionHeight  :: !Int
+}
+
+createRectangle :: Point -> Point -> Region
+createRectangle pt1 pt2 =
+  let (x,y,width,height) = normaliseBounds' pt1 pt2 
+      drawing x y = do
+        Cairo.rectangle (fromIntegral x) (fromIntegral y)
+                        (fromIntegral width) (fromIntegral height)
+        Cairo.fill
+   in Region drawing x y width height
+
+createEllipse :: Point -> Point -> Region
+createEllipse pt1 pt2 =
+  let (x0,y0,width,height) = normaliseBounds' pt1 pt2
+      drawing x y | width==0 || height==0 = return ()
+                  | otherwise = do
+        Cairo.save
+        Cairo.translate (fromIntegral x + fromIntegral width / 2)
+                        (fromIntegral y + fromIntegral height / 2)
+        Cairo.scale (fromIntegral width / 2) (fromIntegral height / 2)
+        Cairo.arc 0 0 1 0 (2*pi)
+        Cairo.fill
+        Cairo.restore
+  in Region drawing x0 y0 width height
+
+createPolygon :: [Point] -> Region
+createPolygon [] = Region (\_ _ -> return ()) 0 0 0 0
+createPolygon (p@(x0,y0):ps) =
+  let minMax (minx,maxx,miny,maxy) (x,y) =
+        let minx' = min minx x
+            maxx' = max maxx x
+            miny' = min miny y
+            maxy' = max maxy y
+         in seq minx' $ seq maxx' $ seq miny' $ seq maxy' $
+            (minx',maxx',miny',maxy')
+      (minx,maxx,miny,maxy) = foldl' minMax (x0,x0,y0,y0) (p:ps)
+      drawing x y = do
+        Cairo.save
+        Cairo.translate (fromIntegral (x-minx)) (fromIntegral (y-miny))
+        Cairo.moveTo (fromIntegral x0) (fromIntegral y0)
+        sequence_ [ Cairo.lineTo (fromIntegral x) (fromIntegral y)
+                  | (x,y) <- ps ]
+        Cairo.fill
+        Cairo.restore
+   in Region drawing minx miny (maxx - minx) (maxy - miny)
+
+andRegion, orRegion, xorRegion, diffRegion :: Region -> Region -> Region
+andRegion  = combineRegion Cairo.OperatorIn
+orRegion   = combineRegion Cairo.OperatorOver
+xorRegion  = combineRegion Cairo.OperatorXor
+diffRegion = combineRegion Cairo.OperatorDestOut
+
+drawRegion :: Region -> Graphic
+drawRegion Region { regionGraphic = graphic,
+                    regionOriginX = x,
+                    regionOriginY = y
+                  } = Graphic $ \_ -> do
+  graphic x y
+
+combineRegion :: Cairo.Operator -> Region -> Region -> Region
+combineRegion operator a b =
+  let x = min (regionOriginX a) (regionOriginX b)
+      y = min (regionOriginY a) (regionOriginY b)
+      x' = max (regionOriginX a + regionWidth a) (regionOriginX b + regionWidth b)
+      y' = max (regionOriginY a + regionHeight a) (regionOriginY b + regionHeight b)
+      width  = x' - x
+      height = y' - y
+      drawing x'' y'' = do
+        Cairo.renderWithSimilarSurface Cairo.ContentAlpha width height $
+          \surface -> do
+          Cairo.renderWith surface $ do
+            Cairo.setSourceRGBA 0 0 0 1
+            regionGraphic a (regionOriginX a - x)
+                            (regionOriginY a - y)
+            Cairo.setOperator operator
+            regionGraphic b (regionOriginX b - x)
+                            (regionOriginY b - y)
+          Cairo.maskSurface surface (fromIntegral x'') (fromIntegral y'')
+   in Region drawing x y width height
+
+normaliseBounds :: Point -> Point -> Maybe (Double,Double,Double,Double)
+normaliseBounds (x1,y1) (x2,y2) = 
+  if x1==x2 || y1==y2 then Nothing else Just (x, y, width, height)
+  where x = fromIntegral $ min x1 x2
+        y = fromIntegral $ min y1 y2
+        width  = fromIntegral $ abs $ x1 - x2
+        height = fromIntegral $ abs $ y1 - y2
+
+normaliseBounds' :: Point -> Point -> (Int,Int,Int,Int)
+normaliseBounds' (x1,y1) (x2,y2) = (x, y, width, height)
+  where x = min x1 x2
+        y = min y1 y2
+        width  = abs $ x1 - x2
+        height = abs $ y1 - y2
+
+---------------------------
+-- Event Handling Functions
+---------------------------
+
+data Event = Key {
+               char :: Char,
+               isDown :: Bool
+             }
+           | Button {
+              pt :: Point,
+              isLeft :: Bool,
+              isDown :: Bool
+             }
+           | MouseMove {
+               pt :: Point
+             }
+           | Resize
+           | Closed
+  deriving Show
+
+getWindowEvent_ :: Window -> IO Event
+getWindowEvent_ win = readChan (eventsChan win)
+
+getWindowEvent :: Window -> IO Event
+getWindowEvent win = do
+  event <- getWindowEvent_ win
+  -- this says we are ready for another mouse move event
+  -- (this is part of the pointer move event flood prevention system)
+  case event of
+    MouseMove _ -> Gtk.postGUIAsync $
+                   Gtk.widgetGetDrawWindow (canvas win)
+                   >>= Gtk.drawWindowGetPointer
+                   >> return ()
+    _ -> return ()
+  return event
+
+maybeGetWindowEvent :: Window -> IO (Maybe Event)
+maybeGetWindowEvent win = do
+  noEvents <- isEmptyChan (eventsChan win)
+  if noEvents then -- Sync with the main GUI loop or we can end up spinning on
+                   -- maybeGetWindowEvent and prevent the screen redrawing.
+                   -- We also introduce a very short delay here of 5ms. This
+                   -- prevents the program from using 100% cpu and redrawing
+                   -- constantly. This actually makes animation much smoother
+                   -- since the process gets treated as interactive rather than
+                   -- as a CPU hog so the scheduler gives us the benefit on
+                   -- latency. Even with a 5ms delay here we can animate at up
+                   -- to 200fps.
+                   do syncVar <- newEmptyMVar
+                      Gtk.timeoutAddFull (putMVar syncVar () >> return False)
+                                         Gtk.priorityDefaultIdle 10
+                      takeMVar syncVar
+                      return Nothing
+              else do event <- readChan (eventsChan win)
+                      case event of
+                        MouseMove _ -> Gtk.postGUIAsync $
+                                       Gtk.widgetGetDrawWindow (canvas win)
+                                       >>= Gtk.drawWindowGetPointer
+                                       >> return ()
+                        _ -> return ()
+                      return (Just event)
+
+
+getKeyEx :: Window -> Bool -> IO Char
+getKeyEx win down = loop
+  where loop = do e <- getWindowEvent_ win
+                  case e of
+                    (Key { char = ch, isDown = d })
+                      | d == down -> return ch
+                    Closed -> return '\x0'
+                    _ -> loop
+
+getKey :: Window -> IO Char
+getKey win = do
+  ch <- getKeyEx win True
+  if ch == '\x0' then return ch
+    else getKeyEx win False
+
+getButton :: Window -> Int -> Bool -> IO Point
+getButton win but down = loop
+  where loop = do e <- getWindowEvent_ win
+                  case e of
+                    (Button { pt = pt, isDown = id })
+                      | id == down -> return pt
+                    _ -> loop
+
+getLBP :: Window -> IO Point
+getLBP w = getButton w 1 True
+
+getRBP :: Window -> IO Point
+getRBP w = getButton w 2 True
+
+timeGetTime :: IO Word32
+timeGetTime = do
+  System.Time.TOD sec psec <- System.Time.getClockTime
+  return (fromIntegral $ sec * 1000 + psec `div` 1000000000)
+
+word32ToInt :: Word32 -> Int
+word32ToInt = fromIntegral
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/soegtk.cabal b/soegtk.cabal
new file mode 100644
--- /dev/null
+++ b/soegtk.cabal
@@ -0,0 +1,10 @@
+name: soegtk
+version: 0.9.12.2
+license: LGPL
+author: Duncan Coutts <duncan@haskell.org>
+maintainer: gtk2hs-devel@lists.sourceforge.net
+stability: provisional
+homepage: http://haskell.org/gtk2hs/
+category: Graphics
+build-depends: base, gtk >=0.9.12, cairo >=0.9.12
+exposed-modules: Graphics.SOE.Gtk
