diff --git a/App/Behaviours/PrintEvents.hs b/App/Behaviours/PrintEvents.hs
new file mode 100644
--- /dev/null
+++ b/App/Behaviours/PrintEvents.hs
@@ -0,0 +1,30 @@
+module App.Behaviours.PrintEvents where
+
+import qualified Data.ByteString as B
+import Text.PrettyPrint
+import Data.Time
+import System.Locale
+import App.EventBus
+
+printEventsBehaviour b = pollAllEventsWith b $ (\(Event n g lifetime edata source t) -> do
+    putStrLn.render $ text "name:    " <+> text n $+$
+	                  text "source:  " <+> text source $+$
+	                  text "group:   " <+> text g $+$
+	                  text "ttl:     " <+> text (show lifetime) $+$
+	                  text "emitTime:" <+> text (formatTime defaultTimeLocale "%T" t) $+$
+	                  (vcat.map showIfPossible $ edata)
+    return [])
+    where showIfPossible (EString x) = text x
+          showIfPossible (EByteString x) = text "ByteString"
+          showIfPossible (EByteStringL x) = text "[ByteString]"
+          showIfPossible (EInt x) = text (show x)
+          showIfPossible (EDouble x) = text (show x)
+          showIfPossible (EBool x) = text (show x)
+          showIfPossible (EStringL x) = text (show x)
+          showIfPossible (EIntL x) = text (show x)
+          showIfPossible (EDoubleL x) = text (show x)
+          showIfPossible (EBoolL x) = text (show x)
+          showIfPossible (EChar x) = char x
+          showIfPossible (EOther x) = text "Custom Data"
+          showIfPossible (EOtherL x) = text "Custom Data List"
+	
diff --git a/App/EventBus.hs b/App/EventBus.hs
new file mode 100644
--- /dev/null
+++ b/App/EventBus.hs
@@ -0,0 +1,345 @@
+{-# LANGUAGE TypeSynonymInstances #-}
+-- | Not exactly the FRP model, but rather a model of a large application with
+--   heterogenous data and many inputs and outputs.  An application is in its
+--   essence a collection of widgets and behaviours and events with a bus.
+--   The bus holds events and manages the event timeline.  Behaviours and
+--   widgets are continuous. Widgets applied to the bus make insertions and
+--   never deletions. Behaviours applied to the bus make insertions and deletions.
+--
+--   Behaviours are composable using combinators that set one Behaviour as either
+--   behind, in front, or beside another behaviour on the bus.  The in front and
+--   behind combinators  establish that the behaviour "behind" the others
+--   sees the results of the other behaviours' application to the bus. The beside
+--   combinator says that the combinators see the same bus.
+--
+module App.EventBus where
+
+import Control.Applicative
+import Control.Concurrent
+import Control.Monad
+import Data.Maybe
+import Data.List (foldl')
+import Data.Monoid
+import qualified Data.Set as Set
+import Data.Time.Clock
+import qualified Data.Map as Map
+import qualified Data.ByteString as B
+import System.IO.Unsafe
+
+-- generic functions for key ordering.  move somewhere else later
+EQ />/ b = b
+a />/ _ = a
+
+a /</ EQ = a
+_ /</ b = b
+
+g %=> f = f `on` g
+
+on f g a b = f (g a) (g b)
+
+-- IO version of the <* Applicative operator
+a =<<^ b = \m -> b >> a m
+
+-- | Defines the amount of time that an event exists.
+data TimeSpan =
+      Persistent            -- ^ The event exists forever
+    | Time DiffTime         -- ^ The event exists for a specified amount of real time
+    | Iterations Int        -- ^ The event exists for a certain number of samples of time from its inception.
+    deriving (Eq,Ord,Show)
+
+seconds :: Integer -> TimeSpan
+seconds = Time . secondsToDiffTime
+
+minutes :: Integer -> TimeSpan
+minutes = Time . secondsToDiffTime . (60*)
+
+hours :: Integer -> TimeSpan
+hours = Time . secondsToDiffTime . (3600*)
+
+days :: Integer -> TimeSpan
+days = Time . secondsToDiffTime . (86400*)
+
+once :: TimeSpan
+once = Iterations 1
+
+-- | Defines time in terms of the differences from time t0 to the next instant. This is the type
+--   returned by Behaviours to describe time directly after the Behaviour.
+data Diff a =
+      Insertion (Event a)   -- ^ Time t1 contains all events at time t0 plus this event.
+    | Deletion (Event a)    -- ^ Time t1 contains all events at time t0 minus this event.
+
+
+-- | Defines the data attachable to events.
+data EData a =
+      EString String
+    | EByteString B.ByteString
+    | EByteStringL [B.ByteString]
+    | EChar Char
+    | EDouble Double
+    | EInt Int
+    | EBool Bool
+    | EStringL [String]
+    | EDoubleL [Double]
+    | EIntL [Int]
+    | EBoolL [Bool]
+    | EOther a
+    | EOtherL [a]
+    deriving (Eq, Show, Read)
+
+-- | An discrete event in time
+data Event a = Event
+    { name :: String            -- ^ The unique name of an event.  Group + src + name = the fully qualified name FQN of the event.
+    , group :: String           -- ^ The group of an event.
+    , timespan :: TimeSpan      -- ^ The timespan from "time" that an event exists.
+    , eventdata :: [EData a]    -- ^ The data attached to the event.
+    , src :: String             -- ^ The behaviour or widget that assigned the event to time.
+    , time :: UTCTime }         -- ^ The time of the event's inception.
+
+
+instance Ord (Event a) where
+    compare l r = ((src %=> compare) l r) />/
+                  ((time %=> compare) l r) />/
+                  ((group %=> compare) l r) />/
+                  ((name %=> compare) l r)
+
+instance Eq (Event a) where
+    x == y = (name %=> (==)) x y &&
+             (group %=> (==)) x y &&
+             (src %=> (==)) x y
+
+-- | The type of a discrete sample of continuous time.
+data Bus a = Bus
+    { nameMap :: Map.Map String (Set.Set (Event a))                     -- ^ The map of just Event.name to events.
+    , srcMap :: Map.Map String (Set.Set (Event a))                      -- ^ The map of just Event.src to events.
+    , groupMap :: Map.Map String (Set.Set (Event a))                    -- ^ The map of just Event.group to events.
+    , fullyQualifiedMap :: Map.Map (String,String,String) (Event a) }   -- ^ The map of FQNs to events.
+
+instance Monoid (Bus a) where
+    mempty = emptyBus
+    mappend (Bus n0 s0 g0 f0) (Bus n1 s1 g1 f1) = Bus (Map.union n0 n1) (Map.union s0 s1) (Map.union g0 g1) (Map.union f0 f1)
+
+-- | The empty bus
+emptyBus :: Bus a
+emptyBus = Bus Map.empty Map.empty Map.empty Map.empty
+
+-- | Add an event to time within the bus
+addEvent :: Event a -> Bus a -> Bus a
+addEvent edata b = b{ nameMap = Map.insertWith (Set.union) (name edata) (singleton edata) (nameMap b)
+                    , srcMap = Map.insertWith (Set.union) (src edata) (singleton edata) (srcMap b)
+                    , groupMap = Map.insertWith (Set.union) (group edata) (singleton edata) (groupMap b)
+                    , fullyQualifiedMap = Map.insert (group edata, src edata, name edata) edata (fullyQualifiedMap b) }
+
+-- | The type of widgets.
+--   A widget is an input-only way to assign Events to time.  A mouse is a widget.  A keyboard is a
+--   widget.  A webcam is a widget, and so on.
+type Widget a = MVar (Bus a) -> IO ()
+
+-- | The type of future events..
+--   A behaviour doesn't know about the time that it assigns events, only that they exist
+--   at some point after the time that the Behaviour sampled.
+type Future a = IO (MVar a)
+
+-- | An IO action sometime in the future.
+future :: IO a -> Future a
+future thunk = do
+    ref <- newEmptyMVar
+    forkIO $ thunk >>= putMVar ref
+    return ref
+
+-- | Obtain the final value of a Future.  Blocks until the value is available
+immediate = takeMVar
+
+-- | The type of a Behaviour.  A behaviour maps the bus to a list of differences to apply to the bus
+--   before the next Behaviour's sample of time.
+type Behaviour a = Bus a -> Future [Diff a]
+
+instance Monoid (Behaviour a) where
+    mempty = passthrough
+    mappend = (>~>) -- x behind y
+
+-- | The null Behaviour.  Samples the bus and adds and deletes nothing.
+passthrough :: Behaviour a
+passthrough a = future (return [])
+
+-- | the in front of behaviour combinator. behaviour 1 is in front of behaviour 0, so behavour 0 will see the bus filtered through behaviour 1
+(<~<) :: Behaviour a -> Behaviour a -> Behaviour a
+behaviour1 <~< behaviour0 = \m -> behaviour0 m >>= applyDiff m >>= behaviour1
+
+-- | the behind behaviour combinator. behaviour 0 is behind behaviour 1, so behaviour 0 will see the bus filtered through behaviour 1
+(>~>) :: Behaviour a -> Behaviour a -> Behaviour a
+behaviour0 >~> behaviour1 = \m -> behaviour0 m >>= applyDiff m >>= behaviour1
+
+-- | the beside behaviour combinator. All behaviours that are side-by-side see the same bus.
+(|~|) :: Behaviour a -> Behaviour a -> Behaviour a
+behaviour0 |~| behaviour1 = \m -> do
+    future0 <- behaviour0 m
+    future1 <- behaviour1 m
+    future $ concat <$> mapM immediate [future0, future1]
+
+behind = (>~>)
+beside = (|~|)
+infrontof = (<~<)
+
+-- | An infinite loop of behaviours and widgets over time, sampled forward.
+bus :: [Widget a] -> IO b -> Behaviour a -> IO ()
+bus widgets widgetThunk behaviour = do
+    evBus <- newMVar emptyBus
+    forM_ widgets ($evBus)
+
+    let loop = do
+        widgetThunk
+        busIteration evBus behaviour
+        loop
+
+    loop
+
+-- | Sample time and apply the behaviour to that sample.
+busIteration :: MVar (Bus a) -> Behaviour a -> IO ()
+busIteration b behaviour = do
+    v <- tryTakeMVar b
+    case v of
+        Nothing -> return ()
+        Just m -> putMVar b . expire =<< decrementTimeSpan =<< applyDiff m =<< behaviour m
+
+-- | Assign an event to time given some event data and a TimeSpan.
+produce :: String -> String -> String -> TimeSpan -> [EData a] -> IO (Diff a)
+produce group source nm timetolive edata =
+    (return . Insertion . Event nm group timetolive edata source) =<< getCurrentTime
+
+-- | Assign an event to time from a widget.
+produce' :: String -> String -> String -> TimeSpan -> [EData a] -> MVar (Bus a) -> IO ()
+produce' group source nm timetolive edata b = getCurrentTime >>= \t -> modifyMVar_ b (return . addEvent (Event nm group timetolive edata source t))
+
+-- | Sample all events with a given name at the current time and output their deletions as Diffs as
+--   well as any additional Diffs returned by the behaviour.
+consumeNamedEventsWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+consumeNamedEventsWith em nm f =
+    maybe (future . return $ [])
+          (\ev -> future $ (map Deletion (Set.toList ev) ++) <$> f ev)
+          (Map.lookup nm (nameMap em))
+
+-- | Sample all events with a given group at the current time and output their deletions as Diffs as
+--   well as any additional Diffs returned by the behaviour.
+consumeEventGroupWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+consumeEventGroupWith em gp f =
+    maybe (future . return $ [])
+          (\ev -> future $ (map Deletion (Set.toList ev) ++) <$> f ev)
+          (Map.lookup gp (groupMap em))
+
+-- | Sample all events with a given source at the current time and output their deletions as Diffs as
+--   well as any additional Diffs returned by the behaviour.
+consumeEventsFromSourceWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+consumeEventsFromSourceWith em source f =
+    maybe (future . return $ [])
+          (\ev -> future $ (map Deletion (Set.toList ev) ++) <$> f ev)
+          (Map.lookup source (srcMap em))
+
+-- | Sample a single fully qualified event at the current time and output their deletions as Diffs as
+--   well as any additional Diffs returned by the behaviour.
+consumeFullyQualifiedEventWith :: Bus a -> String -> String -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]
+consumeFullyQualifiedEventWith em group source name f =
+    maybe (future . return $ [])
+          (\ev -> future $ (Deletion ev :) <$> f ev)
+          (Map.lookup (group,source,name) (fullyQualifiedMap em))
+
+-- | Sample all events with a given name and apply a Behaviour
+pollNamedEventsCollectivelyWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+pollNamedEventsCollectivelyWith b nm f = maybe (future . return $[]) (future . f) (Map.lookup nm (nameMap b))
+
+-- | Sample all events with a given name and apply a Behaviour to each
+pollNamedEventsWith :: Bus a -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]
+pollNamedEventsWith b nm f = future $ concat <$> (mapM f . Set.toList $ fromMaybe Set.empty (Map.lookup nm (nameMap b)))
+
+-- | Sample all events with a given group and apply a Behaviour
+pollEventGroupCollectivelyWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+pollEventGroupCollectivelyWith b nm f = maybe (future . return $[]) (future . f) (Map.lookup nm (groupMap b))
+
+-- | Sample all events with a gien group and apply a Behaviour to each.
+pollEventGroupWith :: Bus a -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]
+pollEventGroupWith b nm f = future $ concat <$> (mapM f . Set.toList $ fromMaybe Set.empty (Map.lookup nm (groupMap b)))
+
+-- | Sample all events with a given source and apply a Behaviour
+pollEventsFromSourceCollectivelyWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+pollEventsFromSourceCollectivelyWith b nm f = maybe (future . return $[]) (future.f) (Map.lookup nm (srcMap b))
+
+-- | Sample all events with a given source and apply a Behaviour to each.
+pollEventsFromSourceWith :: Bus a -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]
+pollEventsFromSourceWith b nm f = future $ concat <$> (mapM f . Set.toList $ fromMaybe Set.empty (Map.lookup nm (srcMap b)))
+
+-- | Sample a single fully qualified event and output some Diffs.
+pollFullyQualifiedEventWith :: Bus a -> String -> String -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]
+pollFullyQualifiedEventWith b gp source nm f = maybe (future . return $ []) (future . f) (Map.lookup (gp,source,nm) (fullyQualifiedMap b))
+
+-- | Apply a behaviour to all events in the bus, one event at a time.
+pollAllEventsWith :: Bus a -> (Event a -> IO [Diff a]) -> Future [Diff a]
+pollAllEventsWith b f = future $ concat <$> (mapM f . Map.elems . fullyQualifiedMap $ b)
+
+-- | Apply a behaviour to the collection of all events on the bus at once
+pollAllEventsCollectivelyWith :: Bus a -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]
+pollAllEventsCollectivelyWith b f = future $ f . Set.fromList . Map.elems . fullyQualifiedMap $ b
+
+applyDiff m ds = immediate ds >>= (return . foldl' busDiff m)
+    where busDiff b (Insertion ev) = b{ nameMap = Map.insertWith (Set.union) (name ev) (singleton ev) (nameMap b)
+                                      , srcMap = Map.insertWith (Set.union) (src ev) (singleton ev) (srcMap b)
+                                      , groupMap = Map.insertWith (Set.union) (group ev) (singleton ev) (groupMap b)
+                                      , fullyQualifiedMap = Map.insert (group ev, src ev, name ev) ev (fullyQualifiedMap b) }
+          busDiff b (Deletion ev) = b { nameMap = deleteOneFrom ev (name ev) (nameMap b) -- should change this to alter instead of delete, check for empty lists
+                                      , srcMap = deleteOneFrom ev (src ev) (srcMap b)
+                                      , groupMap = deleteOneFrom ev (group ev) (groupMap b)
+                                      , fullyQualifiedMap = Map.delete (group ev, src ev, name ev) (fullyQualifiedMap b) }
+          deleteOneFrom ev key mp = case Map.lookup key mp of
+					Just eset -> let eset' = Set.delete ev eset in if eset' == Set.empty then Map.delete key mp else Map.insert key eset' mp
+					Nothing -> mp
+					
+singleton a = Set.fromList [a]					
+	
+decrementTimeSpan b = return $ b{ nameMap = Map.map decrements (nameMap b)
+                           , srcMap = Map.map decrements (srcMap b)
+                           , groupMap = Map.map decrements (groupMap b)
+                           , fullyQualifiedMap = Map.map decrement (fullyQualifiedMap b) }
+    where decrement e = e{ timespan = decTimeSpan e (timespan e) }
+          decrements = Set.map (\e -> e{timespan = decTimeSpan e (timespan e)} )
+          decTimeSpan _ Persistent = Persistent
+          decTimeSpan e (Time x) = Time . realToFrac $ diffUTCTime (addUTCTime (realToFrac x) (time e)) (unsafePerformIO getCurrentTime)
+          decTimeSpan _ (Iterations x) = (Iterations (x-1))
+
+expire b = Bus (Map.filter (/=Set.empty) . Map.map (Set.filter (current . timespan)) . nameMap $ b)
+               (Map.filter (/=Set.empty) . Map.map (Set.filter (current . timespan)) . srcMap $ b)
+               (Map.filter (/=Set.empty) . Map.map (Set.filter (current . timespan)) . groupMap $ b)
+               (Map.filter (current . timespan) . fullyQualifiedMap $ b)
+	where current (Time x) = x > 0
+	      current Persistent = True
+	      current (Iterations x) = x > 0
+	
+{- example usage...
+ -
+ - handleDataLoad :: Behaviour
+ - ...
+ -
+ - handleZoom :: Behaviour
+ - ...
+ -
+ - handlePan :: Behaviour
+ - ...
+ -
+ - handleRot :: Behaviour
+ - ...
+ -
+ - handleWriteData :: Behaviour
+ - ...
+ -
+ - main = do
+ -  ui <- getUIFromFile "something.glade"
+ -  mapM_ makeGtkProducers ui
+ -  multitouch <- getMultitouchProducer "localhost" 8080
+ -  bus (multitouch:ui) $ handleDataLoad >~> handleZoom |~| handlePan |~| handleRot >~> handleWriteData
+ -
+ -}
+
+{- example of generically wrapping a Gtk widget into a EventBus.Widget
+ - buttonWidget :: Gtk.Widget -> Bus a -> IO (Behaviour a)
+ - buttonWidget button em = Gtk.onClick button $ do
+ -      name <- Gtk.getWidgetName button
+ -      value <- EString <$> Gtk.getText button
+ -      produce' "ui" name "Click" once em
+ -}
diff --git a/App/Widgets/GtkMouseKeyboard.hs b/App/Widgets/GtkMouseKeyboard.hs
new file mode 100644
--- /dev/null
+++ b/App/Widgets/GtkMouseKeyboard.hs
@@ -0,0 +1,54 @@
+-- | Gtk mouse keyboard widget.
+--
+--   For a mouse button press or release, add events named SingleClick or ClickRelease respectively to the bus.
+--   For this widget, all events have source ''KeyboardMouseWidget'', and group ''Mouse''
+--   Additionally, the data attached to the event follows the form [EString SingleClick|ClickRelease, EDouble x, EDouble y, EStringL [Gtk modifier names]]
+--
+--   For a keyboard press or release, add events named KeyDown or KeyUp respectively to the bus.
+--   All keyboard events have group ''Keyboard'' and source ''WidgetName.KeyboardMouseWidget''
+--   Additionally, the data attached to a keyboard event follows the form [EString keyName | EChar keyChar, EStringL [Gtk modifier names]]
+module App.Widgets.GtkMouseKeyboard where
+
+import Control.Applicative
+import Control.Concurrent
+import Data.Maybe
+import qualified Graphics.UI.Gtk as Gtk
+import qualified Graphics.UI.Gtk.Gdk.Events as Gtk
+import App.EventBus
+
+-- Gtk's button click event system is annoying, so we're ignoring it and only bothering with the single clicks.
+-- when we receive a click, fire off a thread (once) that waits for 100ms to see how many clicks we get total in that time.  Then fire off that number.
+buttonHandler _ _ (Gtk.Button _ Gtk.DoubleClick _ _ _ _ _ _ _) = return True
+buttonHandler _ _ (Gtk.Button _ Gtk.TripleClick _ _ _ _ _ _ _) = return True
+buttonHandler wname b (Gtk.Button sent click time x y modifiers button _ _) = do
+    produce' "Mouse" (wname ++ ".KeyboardMouseWidget") (show click) once [EString . show $ button, EDouble x, EDouble y, EStringL . map show $ modifiers] b
+    return True
+
+scrollWheelHandler wname b (Gtk.Scroll _ _ x y direction _ _) = do
+    produce' "Mouse" (wname ++ ".KeyboardMouseWidget") (show direction) once [EDouble x, EDouble y] b
+    return True
+
+keyboardHandler wname b (Gtk.Key released sent time modifiers withCapsLock withNumLock withScrollLock keyVal keyName keyChar) = do
+    produce' "Keyboard" (wname ++ "KeyboardMouseWidget") (if released then "KeyUp" else "KeyDown") once
+            [ fromMaybe (EString . show $ keyName) (EChar <$> keyChar)
+            , EStringL . map show $ modifiers ] b
+    return False
+
+motionHandler wname w b evt = do
+    produce' "Mouse" (wname ++ ".KeyboardMouseWidget") "Position" once [EDouble . Gtk.eventX $ evt, EDouble . Gtk.eventY $ evt] b
+    dwin <- Gtk.widgetGetDrawWindow w
+    Gtk.drawWindowGetPointer dwin
+    return False
+
+-- | Bind a keyboard mouse widget to the given Gtk widget. Se module documentation for description of events.
+bindMouseKeyboardWidget :: Gtk.Widget -> Widget a
+bindMouseKeyboardWidget w b = do
+    ref <- newEmptyMVar
+    wname <- Gtk.widgetGetName w
+    Gtk.onButtonPress w (buttonHandler wname b)
+    Gtk.onButtonRelease w (buttonHandler wname b)
+    Gtk.onScroll w (scrollWheelHandler wname b)
+    Gtk.onKeyPress w (keyboardHandler wname b)
+    Gtk.onKeyRelease w (keyboardHandler wname b)
+    Gtk.onMotionNotify w True (motionHandler wname w b)
+    return ()
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,6 @@
+#!/usr/bin/runhaskell 
+> module Main where
+> import Distribution.Simple
+> main :: IO ()
+> main = defaultMain
+
diff --git a/buster.cabal b/buster.cabal
new file mode 100644
--- /dev/null
+++ b/buster.cabal
@@ -0,0 +1,59 @@
+name: buster
+version: 0.99.1
+cabal-version: -any
+build-type: Simple
+license: BSD3
+license-file: ""
+copyright: 2009 Renaissance Computing Institute
+maintainer: Jeff Heard <jeff@renci.org>
+build-depends: old-locale -any, time -any, containers -any,
+               base -any, bytestring -any, gtk -any, mtl -any,
+               pretty -any
+stability: Experimental
+homepage: http://vis.renci.org/jeff/buster
+package-url:
+bug-reports:
+synopsis: Almost but not quite entirely unlike FRP
+description: Buster is best described by the following blog post: http://vis.renci.org/jeff/2009/03/31/almost-but-not-quite-entirely-like-frp/
+             .
+             It is an engine for orchestrating large, complex, and multifaceted applications by couching them in terms of time, events, a bus,
+             behaviours, and widgets.  Time is continuous and infininte.  Events are discrete and exist for a particular time.  The bus is a
+             discrete sample of time made available to behaviours. Behaviours are continuous and exist for all time, but sample time via
+             the bus.  They filter Events to determine what is on the bus at future times.  Widgets are input-only objects that sample the
+             outside world and assign events to discrete portions of time.
+             .
+             Buster is designed to be flexible, with a flexible event model and the ability to add custom data to events, and designed to be
+             high performance.  It is simple to integrate with Gtk while at the same time able to handle other kinds of resources, like files
+             and sockets.
+category: FRP
+author: Jeff Heard
+tested-with:
+data-files:
+data-dir: ""
+extra-source-files:
+extra-tmp-files:
+exposed-modules: App.EventBus App.Behaviours.PrintEvents
+                 App.Widgets.GtkMouseKeyboard 
+exposed: True
+buildable: True
+build-tools:
+cpp-options:
+cc-options:
+ld-options:
+pkgconfig-depends:
+frameworks:
+c-sources:
+extensions:
+extra-libraries:
+extra-lib-dirs:
+includes:
+install-includes:
+include-dirs:
+hs-source-dirs: .
+other-modules:
+ghc-prof-options:
+ghc-shared-options:
+ghc-options:
+hugs-options:
+nhc98-options:
+jhc-options:
