diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2016 Brian W Bush
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/ReadMe.md b/ReadMe.md
new file mode 100644
--- /dev/null
+++ b/ReadMe.md
@@ -0,0 +1,14 @@
+Leap Motion events via a Kafka message broker
+=============================================
+
+This package contains functions for passing [Leap Motion](https://www.leapmotion.com/product/desktop) events to topics on a [Kafka message broker](https://kafka.apache.org/).
+
+
+Clients
+-------
+
+The simple Kafka client that produces events from Leap Motion can be run, for example, as follows:
+
+	cabal run kafka-device-leap -- localhost 6437 leap-client localhost 9092 events leap
+
+Also see https://hackage.haskell.org/package/kafka-device/.
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/kafka-device-leap.cabal b/kafka-device-leap.cabal
new file mode 100644
--- /dev/null
+++ b/kafka-device-leap.cabal
@@ -0,0 +1,46 @@
+name:                kafka-device-leap
+version:             0.1.3.0
+synopsis:            Leap Motion events via a Kafka message broker
+description:         This package contains functions for passing Leap Motion \<<https://www.leapmotion.com/product/desktop>\> events to topics on a Kafka message broker \<<https://kafka.apache.org/>\>.  Also see \<https://hackage.haskell.org/package/kafka-device/>\>.
+license:             MIT
+license-file:        LICENSE
+author:              Brian W Bush <consult@brianwbush.info>
+maintainer:          Brian W Bush <consult@brianwbush.info>
+copyright:           (c) 2016 Brian W Bush
+category:            Hardware
+build-type:          Simple
+cabal-version:       >= 1.10
+stability:           Experimental
+homepage:            https://bitbucket.org/functionally/kafka-device-leap
+bug-reports:         https://bwbush.atlassian.net/projects/HKAFDEV/issues/
+package-url:         https://bitbucket.org/functionally/kafka-device-leap/downloads/kafka-device-leap-0.1.3.0.tar.gz
+
+extra-source-files:  ReadMe.md
+
+source-repository head
+  type: git
+  location: https://bitbucket.org/functionally/kafka-device-leap
+ 
+library
+  exposed-modules:  Network.UI.Kafka.Leap
+  build-depends:    base         >= 4.8 && < 5
+               ,    aeson
+               ,    hleap
+               ,    kafka-device
+               ,    milena
+               ,    websockets
+  hs-source-dirs:   src
+  ghc-options:      -Wall
+  default-language: Haskell2010
+
+executable kafka-device-leap
+  main-is:          Main.hs
+  build-depends:    base
+               ,    aeson
+               ,    hleap
+               ,    kafka-device
+               ,    milena
+               ,    websockets
+  hs-source-dirs:   src
+  ghc-options:      -Wall -threaded
+  default-language: Haskell2010
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,44 @@
+{-|
+Module      :  Main
+Copyright   :  (c) 2016 Brian W Bush
+License     :  MIT
+Maintainer  :  Brian W Bush <consult@brianwbush.info>
+Stability   :  Experimental
+Portability :  Stable
+
+Simple producer of events on a Kafka topic from a Leap Motion \<<https://www.leapmotion.com/product/desktop>\> controller.
+-}
+
+
+module Main (
+-- * Main entry
+  main
+) where
+
+
+import Data.String (IsString(fromString))
+import Network.UI.Kafka.Leap (leapApp)
+import System.Environment (getArgs)
+import System.Hardware.Leap (Configuration(..), run)
+
+
+-- | The main action.
+main :: IO ()
+main =
+  do
+    args <- getArgs
+    case args of
+      [leapHost, leapPort, client, kafkaHost, kafkaPort, topic, sensor] ->
+        do
+          putStrLn $ "Leap address:  (" ++ leapHost ++ "," ++ leapPort ++ ")"
+          putStrLn $ "Kafka client:  " ++ client
+          putStrLn $ "Kafka address: (" ++ kafkaHost ++ "," ++ kafkaPort ++ ")"
+          putStrLn $ "Kafka topic:   " ++ topic
+          putStrLn $ "Sensor name:   " ++ sensor
+          run (Configuration leapHost $ read leapPort)
+            $ leapApp
+              (fromString client)
+              (fromString kafkaHost, toEnum $ read kafkaPort)
+              (fromString topic)
+              sensor
+      _ -> putStrLn "USAGE: kafka-device-leap leap-host leap-port client kafka-host kafka-port topic senosr"
diff --git a/src/Network/UI/Kafka/Leap.hs b/src/Network/UI/Kafka/Leap.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/UI/Kafka/Leap.hs
@@ -0,0 +1,86 @@
+{-|
+Module      :  Network.UI.Kafka.Leap
+Copyright   :  (c) 2016 Brian W Bush
+License     :  MIT
+Maintainer  :  Brian W Bush <consult@brianwbush.info>
+Stability   :  Experimental
+Portability :  Stable
+
+Produce events on a Kafka topic from a Leap Motion \<<https://www.leapmotion.com/product/desktop>\> controller.
+-}
+
+
+{-# LANGUAGE RecordWildCards #-}
+
+
+module Network.UI.Kafka.Leap (
+-- * Event handling
+  leapApp
+) where
+
+
+import Control.Monad (void)
+import Data.Aeson (eitherDecode)
+import Network.Kafka (KafkaAddress, KafkaClientId)
+import Network.Kafka.Protocol (TopicName)
+import Network.UI.Kafka (Sensor, producerLoop)
+import Network.UI.Kafka.Types as K (Event(..), Finger(..), Hand(..))
+import Network.WebSockets (receiveData)
+import System.Hardware.Leap (ClientApp, setFocused, setGestures)
+import System.Hardware.Leap.Event as L (Event(..))
+import System.Hardware.Leap.Event.Hand as L (Hand(..), Side(..))
+import System.Hardware.Leap.Event.Pointable as L (Finger(..), Pointable(..))
+
+
+-- | WebSocket application for producing Leap Motion events.
+leapApp :: KafkaClientId -- ^ A Kafka client identifier for the producer.
+        -> KafkaAddress  -- ^ The address of the Kafka broker.
+        -> TopicName     -- ^ The Kafka topic name.
+        -> Sensor        -- ^ The name of the sensor producing events.
+        -> ClientApp ()  -- ^ The WebSocket client application.
+leapApp clientId address topic sensor connection =
+  do
+    setFocused  True  connection
+    setGestures False connection
+    (_, loop) <-
+      producerLoop clientId address topic sensor
+        $ do
+            event <- eitherDecode <$> receiveData connection
+            case event of
+              Right e -> return $ interpret e
+              Left  s -> return [EventError s]
+    void loop
+
+
+-- | Interpret a Leap Motion event as events for Kafka.
+interpret :: L.Event Double -> [K.Event]
+interpret Tracking{..} = concatMap interpretPointable pointables
+interpret _            = [EventError "Unable to process event."]
+
+
+-- | Interpret a Leap Motion pointing event as events for Kafka.
+interpretPointable :: Pointable Double -> [K.Event]
+interpretPointable Finger{..} =
+  [
+    FingerEvent
+    {
+      hand            = case side hand of
+                          L.LeftHand     -> K.LeftHand
+                          L.RightHand    -> K.RightHand
+    , finger          = case finger of
+                          L.Thumb        -> K.Thumb
+                          L.IndexFinger  -> K.IndexFinger
+                          L.MiddleFinger -> K.MiddleFinger
+                          L.RingFinger   -> K.RingFinger
+                          L.Pinky        -> K.Pinky
+    , pointerPosition = stabilizedTipPosition
+    }
+  ]
+interpretPointable Tool{..}   =
+  [
+    PointerEvent
+    {
+      pointerPosition = stabilizedTipPosition
+    }
+  ]
+interpretPointable _          = []
