diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Patrick Chilton
+
+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/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+#! /usr/bin/env runhaskell
+
+import Distribution.Simple
+main = defaultMain
diff --git a/System/HIDAPI.hsc b/System/HIDAPI.hsc
new file mode 100644
--- /dev/null
+++ b/System/HIDAPI.hsc
@@ -0,0 +1,244 @@
+{-# LANGUAGE ForeignFunctionInterface, DeriveDataTypeable #-}
+
+#include "hidapi/hidapi.h"
+
+module System.HIDAPI
+  ( System.HIDAPI.init, exit, withHIDAPI
+  , enumerate, enumerateAll
+  , open, openPath, openDeviceInfo
+  , close
+  , System.HIDAPI.read
+  , getSerialNumberString
+  , System.HIDAPI.error
+  , HIDAPIException(HIDAPIException)
+  , Device()
+  , DeviceInfo (..)
+  , DevicePath
+  , VendorID
+  , ProductID
+  , ReleaseNumber
+  , SerialNumber
+  , InterfaceNumber
+  ) where
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad
+import Data.ByteString
+import Data.Maybe
+import Data.Typeable
+import Data.Data
+import Foreign
+import Foreign.C.Types
+import Foreign.C.String
+
+type Hid_Device_Ptr = Ptr () -- used where hid_device* is used on C side
+newtype Device = Device Hid_Device_Ptr
+
+data DeviceInfoInternal = DeviceInfoInternal
+  { _path :: CString
+  , _vendorId :: CUShort
+  , _productId :: CUShort
+  , _serialNumber :: CWString
+  , _releaseNumber :: CUShort
+  , _manufacturerString :: CWString
+  , _productString :: CWString
+  , _usagePage :: CUShort
+  , _usage :: CUShort
+  , _interfaceNumber :: CInt
+  , _next :: Ptr DeviceInfoInternal
+  } deriving (Show)
+
+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
+
+instance Storable DeviceInfoInternal where
+  alignment _ = #{alignment struct hid_device_info}
+  sizeOf _ = #{size struct hid_device_info}
+  peek p = DeviceInfoInternal <$>
+    #{peek struct hid_device_info, path} p <*>
+    #{peek struct hid_device_info, vendor_id} p <*>
+    #{peek struct hid_device_info, product_id} p <*>
+    #{peek struct hid_device_info, serial_number} p <*>
+    #{peek struct hid_device_info, release_number} p <*>
+    #{peek struct hid_device_info, manufacturer_string} p <*>
+    #{peek struct hid_device_info, product_string} p <*>
+    #{peek struct hid_device_info, usage_page} p <*>
+    #{peek struct hid_device_info, usage} p <*>
+    #{peek struct hid_device_info, interface_number} p <*>
+    #{peek struct hid_device_info, next} p
+
+-- Aliases for writing better type signatures and haddocks.
+type DevicePath = String
+type VendorID = Word16
+type ProductID = Word16
+type ReleaseNumber = Word16
+type SerialNumber = String
+type InterfaceNumber = Int
+
+data DeviceInfo = DeviceInfo
+  { path :: DevicePath
+  , vendorId :: VendorID
+  , productId :: ProductID
+  , serialNumber :: Maybe SerialNumber
+  , releaseNumber :: ReleaseNumber
+  , manufacturerString :: Maybe String
+  , productString :: Maybe String
+  , usagePage :: Word16
+  , usage :: Word16
+  , interfaceNumber :: InterfaceNumber
+  } deriving (Show)
+
+peekOptString :: Ptr CWchar -> IO (Maybe String)
+peekOptString p
+  | p == nullPtr = return Nothing
+  | otherwise = Just <$> peekCWString p
+
+fromInternalDeviceInfo :: DeviceInfoInternal -> IO DeviceInfo
+fromInternalDeviceInfo di = DeviceInfo <$>
+  peekCString (_path di) <*>
+  pure (fromIntegral $ _vendorId di) <*>
+  pure (fromIntegral $ _productId di) <*>
+  peekOptString (_serialNumber di) <*>
+  pure (fromIntegral $ _releaseNumber di) <*>
+  peekOptString (_manufacturerString di) <*>
+  peekOptString (_productString di) <*>
+  pure (fromIntegral $ _usagePage di) <*>
+  pure (fromIntegral $ _usage di) <*>
+  pure (fromIntegral $ _interfaceNumber di)
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_error"
+  hid_error :: Hid_Device_Ptr -> IO CWString
+
+data HIDAPIException = HIDAPIException String String
+  deriving (Data, Typeable)
+
+instance Show HIDAPIException where
+  showsPrec _ (HIDAPIException a c) = showString a . showString ": " . showString c
+
+instance Exception HIDAPIException
+
+-- TODO As of https://github.com/signal11/hidapi/issues/123 it is unclear
+--      whether the passed in pointer may be NULL, and if hid_error ever
+--      returns something useful.
+error :: Device -> IO (Maybe String)
+error (Device devicePtr) = do
+  e <- hid_error devicePtr
+  if e == nullPtr
+    then return Nothing
+    else do
+      es <- peekCWString e
+      free e
+      return (Just es)
+
+check :: Bool -> String -> String -> IO ()
+check c msg reason = unless c $ throwIO $ HIDAPIException msg reason
+
+-- Device is only used to obtain a more detailed error if the condition is
+-- false and device is not a NULL pointer.
+checkWithHidError :: Bool -> Device -> String -> String -> IO ()
+checkWithHidError c dev@(Device devPtr) msg defaultReason = unless c $ do
+  reason <- if devPtr /= nullPtr
+              then fromMaybe defaultReason <$> System.HIDAPI.error dev
+              else return defaultReason
+  throwIO $ HIDAPIException msg reason
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_init"
+  hid_init :: IO CInt
+
+init :: IO ()
+init = do
+  r <- hid_init
+  check (r == 0) "HIDAPI initialization failed" "hid_init /= 0"
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_exit"
+  hid_exit :: IO CInt
+
+exit :: IO ()
+exit = do
+	r <- hid_exit
+	check (r == 0) "HIDAPI shutdown failed" "hid_exit /= 0"
+
+withHIDAPI :: IO a -> IO a
+withHIDAPI = bracket_ System.HIDAPI.init exit
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_enumerate"
+  hid_enumerate :: CUShort -> CUShort -> IO (Ptr DeviceInfoInternal)
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_free_enumeration"
+  hid_free_enumeration :: Ptr DeviceInfoInternal -> IO ()
+
+parseEnumeration :: Ptr DeviceInfoInternal -> IO [ DeviceInfo ]
+parseEnumeration dip
+  | dip == nullPtr = return []
+  | otherwise = do
+    idi <- peek dip
+    di <- fromInternalDeviceInfo idi
+    dis <- parseEnumeration (_next idi)
+    return (di : dis)
+
+enumerate :: Maybe VendorID -> Maybe ProductID -> IO [ DeviceInfo ]
+enumerate m'vendorId m'productId = do
+  dip <- hid_enumerate (maybe 0 fromIntegral m'vendorId) (maybe 0 fromIntegral m'productId)
+  -- Docs say hid_enumerate also returns "NULL in the case of failure", but
+  -- this is indistinguishable from "no devices". :/
+  if dip == nullPtr
+    then return []
+    else do
+      dis <- parseEnumeration dip
+      hid_free_enumeration dip
+      return dis
+
+enumerateAll :: IO [ DeviceInfo ]
+enumerateAll = enumerate Nothing Nothing
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_open"
+  hid_open :: CUShort -> CUShort -> CWString -> IO Hid_Device_Ptr
+
+open :: VendorID -> ProductID -> Maybe SerialNumber -> IO Device
+open vendor_id product_id serial = do
+  let vid = fromIntegral vendor_id
+  let pid = fromIntegral product_id
+  dev@(Device dp) <- Device <$> case serial of
+    Nothing -> hid_open vid pid nullPtr
+    Just sn -> withCWString sn (hid_open vid pid)
+  checkWithHidError (dp /= nullPtr) dev "Device open (by vendor/product id) failed" "hid_open returned NULL"
+  return dev
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_open_path"
+  hid_open_path :: CString -> IO Hid_Device_Ptr
+
+openPath :: DevicePath -> IO Device
+openPath p = do
+  dev@(Device dp) <- Device <$> withCString p hid_open_path
+  checkWithHidError (dp /= nullPtr) dev "Device open (by path) failed" "hid_open returned NULL"
+  return dev
+
+openDeviceInfo :: DeviceInfo -> IO Device
+openDeviceInfo
+  = openPath . path
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_close"
+  close :: Device -> IO ()
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_read"
+  hid_read :: Device -> Ptr CChar -> CSize -> IO CInt
+
+read :: Device -> Int -> IO ByteString
+read dev n = allocaBytes n $ \b -> do
+  n' <- hid_read dev b (fromIntegral n)
+  checkWithHidError (n' /= -1) dev "Read failed" "hid_read returned -1"
+  packCStringLen ( b, fromIntegral n' )
+
+foreign import ccall unsafe "hidapi/hidapi.h hid_get_serial_number_string"
+  hid_get_serial_number_string :: Device -> CWString -> CSize -> IO CInt
+
+_SERIAL_NUMBER_MAX_LENGTH :: Int
+_SERIAL_NUMBER_MAX_LENGTH = 32768
+
+getSerialNumberString :: Device -> IO SerialNumber
+getSerialNumberString dev = do
+  let bs = _SERIAL_NUMBER_MAX_LENGTH * sizeOf (undefined :: CWchar)
+  bracket (mallocBytes bs) free $ \b -> do
+    n' <- hid_get_serial_number_string dev b (fromIntegral _SERIAL_NUMBER_MAX_LENGTH)
+    checkWithHidError (n' /= -1) dev "Getting serial number failed" "hid_get_serial_number_string returned -1"
+    peekCWString b
diff --git a/hidapi.cabal b/hidapi.cabal
new file mode 100644
--- /dev/null
+++ b/hidapi.cabal
@@ -0,0 +1,24 @@
+name:              hidapi
+version:           0.1
+build-type:        Simple
+cabal-version:     >= 1.6
+category:          Hardware
+author:            Patrick Chilton <chpatrick@gmail.com>, Niklas Hambüchen <mail@nh2.me>
+maintainer:        Patrick Chilton <chpatrick@gmail.com>
+homepage:          https://github.com/vahokif/haskell-hidapi
+bug-reports:       https://github.com/vahokif/haskell-hidapi/issues
+synopsis:          Haskell bindings to HIDAPI
+description:       Haskell bindings to the HIDAPI library (<http://www.signal11.us/oss/hidapi/>).
+license:           MIT
+license-file:      LICENSE
+
+source-repository head
+  type:      git
+  location:  git://github.com/vahokif/haskell-hidapi.git
+
+library
+  exposed-modules: System.HIDAPI
+  build-depends:
+      base < 5
+    , bytestring >= 0.10
+  extra-libraries: hidapi-hidraw
