diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Mitchell Vitez (c) 2020
+
+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.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,120 @@
+# hsautogui
+
+Haskell bindings for [PyAutoGUI](https://pyautogui.readthedocs.io)
+
+## About
+
+These are straightforward Haskell bindings for PyAutoGUI, a library for automating user interaction tasks, using haskell-cpython. 
+
+This is just about the simplest possible example:
+
+```haskell
+import AutoGUI
+main = runAutoGUI $ write "Hello, world!"
+```
+
+This doesn't just print `Hello, world!` to the screen, but instead simulates a user typing it in.
+
+## Language Comparison
+
+When using this library, the following Haskell and Python examples do the same thing:
+
+```haskell
+import AutoGUI
+import Control.Monad.IO.Class (liftIO)
+
+main :: IO ()
+main = do
+  putStrLn "3 seconds until beeping begins"
+  sleep 3
+  runAutoGUI $
+    forM_ [1..100] $ \i -> do
+      write "beep"
+      press [key|enter|]
+      liftIO $ sleep 0.5
+```
+
+```python
+import pyautogui
+import time
+
+print('3 seconds until beeping begins')
+time.sleep(3)
+
+for i in range(1, 101):
+    pyautogui.write('beep')
+    pyautogui.press('enter')
+    time.sleep(0.5)
+```
+
+## Constructing a `Key`
+
+Because not all valid `Text`s are valid `Key`s, we need a way to check that `Key`s are valid when creating them. This leads to `mkKey :: Text -> Maybe Key`. However, using the `key` quasiquoter, we can sidestep having to use `Maybe` by catching invalid keys at compile time. For example, `[key|backspace|]` is a valid `Key` which we can construct and check at compile time.
+
+This is especially useful for some data that looks like this, where there are way too many values (and values with strange characters) for a sum type to be especially handy, but we want to check validity in some way. We generally know which keys we want to use at compile time.
+
+## Overview
+
+### General
+
+- `runAutoGUI :: AutoGUI a -> IO a` runs `AutoGUI` actions in `IO`
+
+### Debug
+- `pause :: Double -> AutoGUI ()` - Set a number of seconds to wait in between autogui actions
+- `failsafe :: Bool -> AutoGUI ()` - When set to true, move the mouse to the upper-left corner of the screen to throw a Python exception, and quit the program
+- `sleep :: Double -> IO ()` - Sleep for a given fractional number of seconds
+
+### Info
+- `size :: AutoGUI (Integer, Integer)` - (screenWidth, screenHeight) of the primary monitor in pixels
+- `position :: AutoGUI (Integer, Integer)` - (x, y) position of the mouse
+- `onScreen :: Integer -> Integer -> AutoGUI Bool` - Test whether (x, y) is within the screen size
+
+### Keyboard
+- `write :: Text -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard
+- `typewrite :: Text -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard, newline is enter
+- `typewriteKeys :: [Key] -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard, newline is enter
+- `writeWithInterval :: Text -> Double -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard, with a specified number of seconds between keypresses
+- `press :: Key -> AutoGUI ()` - Simulate a keypress
+- `keyDown :: Key -> AutoGUI ()` - Simulate holding a key down
+- `keyUp :: Key -> AutoGUI ()` - Simulate releasing a key
+- `hotkey :: [Key] -> AutoGUI ()` - Press a key combination
+
+### Keys
+- `key :: QuasiQuoter` - This quasiquoter lets you use [key|enter|] at compile time, so you don't get a Maybe as you would from mkKey
+- `mkKey :: Text -> Maybe Key`
+- `keyToText :: Key -> Text`
+- `isValidKey :: Text -> Bool`
+- `keys :: Set Key`
+
+### MessageBoxes
+- `alert :: Text -> AutoGUI ()` - Show a box onscreen until dismissed
+- `confirm :: Text -> AutoGUI Bool` - Show a box onscreen until a user hits OK or Cancel. Return True on OK, False on Cancel, and False if user closes the box
+- `password :: Text -> AutoGUI Text` - Show a box onscreen, allowing user to enter some screened text. Return empty string if user closes the box
+- `prompt :: Text -> AutoGUI Text` - Show a box onscreen, allowing user to enter some text. Return empty string if user closes the box
+
+### Mouse
+- `moveTo :: Integer -> Integer -> AutoGUI ()` - Move the mouse to an (x, y) position
+- `moveToDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Move the mouse to an (x, y) position, over a number of seconds
+- `moveRel :: Integer -> Integer -> AutoGUI ()` - Move the mouse relative to where it is now
+- `moveRelDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Move the mouse relative to where it is now, over a number of seconds
+- `click :: AutoGUI ()` - Click the mouse
+- `leftClick :: AutoGUI ()` - Left click the mouse
+- `doubleClick :: AutoGUI ()` - Double click the mouse
+- `tripleClick :: AutoGUI ()` - Triple click the mouse
+- `rightClick :: AutoGUI ()` - Right click the mouse
+- `middleClick :: AutoGUI ()` - Middle click the mouse
+- `moveAndClick :: Integer -> Integer -> AutoGUI ()` - Move the mouse to some (x, y) position and click there
+- `drag :: Integer -> Integer -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y)
+- `dragDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y), over a number of seconds
+- `dragTo :: Integer -> Integer -> AutoGUI ()` - Clicks and drags the mouse to the position (x, y)
+- `dragToDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Clicks and drags the mouse to the position (x, y), over a number of seconds
+- `dragRel :: Integer -> Integer -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y)
+- `dragRelDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y)
+- `scroll :: Integer -> AutoGUI ()` - Scroll up (positive) or down (negative)
+- `mouseDown :: AutoGUI ()` - Press the mouse button down
+- `mouseUp :: AutoGUI ()` - Release the mouse button
+
+### Screen
+- `locateOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer, Integer, Integer))` - Return (left, top, width, height) of first place the image is found
+- `locateCenterOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer))` - Return (x, y) of center of an image, if the image is found
+
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/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,28 @@
+module Main where
+
+import AutoGUI
+import Control.Monad.IO.Class (liftIO)
+
+main :: IO ()
+main = do
+  putStrLn "3 seconds until beeping begins"
+  sleep 3
+  runAutoGUI $
+    forM_ [1..100] $ \i -> do
+      write "beep"
+      press [key|enter|]
+      liftIO $ sleep 0.5
+
+{- # The above is equivalent to this Python code:
+
+import pyautogui
+import time
+
+print('3 seconds until beeping begins')
+time.sleep(3)
+
+for i in range(1, 101):
+    pyautogui.write('beep')
+    pyautogui.press('enter')
+    time.sleep(0.5)
+-}
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,5 @@
+# ChangeLog
+
+## 0.1.0
+
+Initial bindings to (most of) PyAutoGUI
diff --git a/hsautogui.cabal b/hsautogui.cabal
new file mode 100644
--- /dev/null
+++ b/hsautogui.cabal
@@ -0,0 +1,71 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: c8201609cd2ca3891909376630a9e2499c3a2799b03d395571f23dd8155f48a1
+
+name:           hsautogui
+version:        0.1.0
+synopsis:       Haskell bindings for PyAutoGUI, a library for automating user interaction
+description:    Please see the README on GitHub at <https://github.com/mitchellvitez/hsautogui#readme>
+category:       Automation
+homepage:       https://github.com/mitchellvitez/hsautogui#readme
+bug-reports:    https://github.com/mitchellvitez/hsautogui/issues
+author:         Mitchell Vitez
+maintainer:     mitchell@vitez.me
+copyright:      2020 Mitchell Vitez
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    changelog.md
+
+source-repository head
+  type: git
+  location: https://github.com/mitchellvitez/hsautogui
+
+library
+  exposed-modules:
+      AutoGUI
+      AutoGUI.Call
+      AutoGUI.Debug
+      AutoGUI.Info
+      AutoGUI.Keyboard
+      AutoGUI.Keys
+      AutoGUI.MessageBoxes
+      AutoGUI.Mouse
+      AutoGUI.Run
+      AutoGUI.Screen
+  other-modules:
+      Paths_hsautogui
+  hs-source-dirs:
+      src
+  default-extensions: DeriveLift GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses OverloadedStrings RecordWildCards QuasiQuotes ScopedTypeVariables TemplateHaskell
+  extra-libraries:
+      python3
+  build-depends:
+      base
+    , containers
+    , cpython
+    , mtl
+    , template-haskell
+    , text
+  default-language: Haskell2010
+
+executable hsautogui-sample-exe
+  main-is: Main.hs
+  other-modules:
+      Paths_hsautogui
+  hs-source-dirs:
+      app
+  default-extensions: OverloadedStrings QuasiQuotes
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -W
+  extra-libraries:
+      python3
+  build-depends:
+      base >=4.7 && <5
+    , hsautogui
+  default-language: Haskell2010
diff --git a/src/AutoGUI.hs b/src/AutoGUI.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI.hs
@@ -0,0 +1,61 @@
+module AutoGUI
+  ( AutoGUI(..)
+  , AutoGUIT(..)
+  , Key
+  , alert
+  , click
+  , confirm
+  , doubleClick
+  , drag
+  , dragDuration
+  , dragRel
+  , dragRelDuration
+  , dragTo
+  , dragToDuration
+  , failsafe
+  , hotkey
+  , isValidKey
+  , key
+  , keyDown
+  , keyToText
+  , keyUp
+  , keys
+  , leftClick
+  , locateCenterOnScreen
+  , locateOnScreen
+  , middleClick
+  , mkKey
+  , mouseDown
+  , mouseUp
+  , moveAndClick
+  , moveRel
+  , moveRelDuration
+  , moveTo
+  , moveToDuration
+  , onScreen
+  , password
+  , pause
+  , position
+  , press
+  , prompt
+  , rightClick
+  , runAutoGUI
+  , scroll
+  , size
+  , sleep
+  , tripleClick
+  , typewrite
+  , typewriteKeys
+  , write
+  , writeWithInterval
+  )
+where
+
+import AutoGUI.Debug
+import AutoGUI.Info
+import AutoGUI.Keyboard
+import AutoGUI.Keys
+import AutoGUI.MessageBoxes
+import AutoGUI.Mouse
+import AutoGUI.Run
+import AutoGUI.Screen
diff --git a/src/AutoGUI/Call.hs b/src/AutoGUI/Call.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Call.hs
@@ -0,0 +1,41 @@
+module AutoGUI.Call where
+
+import AutoGUI.Run
+import AutoGUI.Keys
+
+import Control.Monad.Reader
+import Data.Text (Text)
+import qualified Data.Text as T
+
+import qualified CPython                  as Py
+import qualified CPython.Constants        as Py
+import qualified CPython.Protocols.Object as Py
+import qualified CPython.Types            as Py
+import qualified CPython.Types.Module     as Py
+
+data Arg
+  = IntArg Integer
+  | TextArg Text
+  | KeyArg Key
+  | DoubleArg Double
+  | BoolArg Bool
+
+argToPy :: Arg -> IO Py.SomeObject
+argToPy = \case
+  IntArg    i -> Py.toObject <$> Py.toInteger i
+  TextArg   t -> Py.toObject <$> Py.toUnicode t
+  KeyArg    k -> Py.toObject <$> Py.toUnicode (keyToText k)
+  DoubleArg d -> Py.toObject <$> Py.toFloat d
+  -- there isn't a bool equivalent to the above in haskell-cpython, just convert to int
+  BoolArg   b -> Py.toObject <$> Py.toInteger (if b then 1 else 0)
+
+call' :: Text -> [Arg] -> AutoGUI Py.SomeObject
+call' func args = do
+  autoguiModule <- ask
+  liftIO $ do
+    pyFunc <- Py.getAttribute autoguiModule =<< Py.toUnicode func
+    pyArgs <- mapM argToPy args
+    Py.callArgs pyFunc pyArgs
+
+call :: Text -> [Arg] -> AutoGUI ()
+call func args = call' func args >> pure ()
diff --git a/src/AutoGUI/Debug.hs b/src/AutoGUI/Debug.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Debug.hs
@@ -0,0 +1,43 @@
+module AutoGUI.Debug
+  ( pause
+  , failsafe
+  , sleep
+  )
+where
+
+import AutoGUI.Call
+import AutoGUI.Run
+
+import Control.Concurrent
+import Control.Monad.IO.Class
+import Control.Monad.Reader
+import Data.Text (Text)
+import qualified Data.Text as T
+import GHC.Float.RealFracMethods
+
+import qualified CPython                  as Py
+import qualified CPython.Constants        as Py
+import qualified CPython.Protocols.Object as Py
+import qualified CPython.Types            as Py
+import qualified CPython.Types.Module     as Py
+
+-- | Set a number of seconds to wait in between autogui actions
+pause :: Double -> AutoGUI ()
+pause seconds = setAttribute "PAUSE" $ DoubleArg seconds
+
+-- | When set to true, move the mouse to the upper-left corner of the screen to throw
+--   a Python exception, and quit the program
+failsafe :: Bool -> AutoGUI ()
+failsafe value = setAttribute "FAILSAFE" $ BoolArg value
+
+-- | Sleep for a given fractional number of seconds
+sleep :: Double -> IO ()
+sleep n = threadDelay . fromInteger . roundDoubleInteger $ n * 1000000
+
+setAttribute :: Text -> Arg -> AutoGUI ()
+setAttribute name value = do
+  autoguiModule <- ask
+  liftIO $ do
+    pyName <- Py.toUnicode name
+    pyValue <- argToPy value
+    Py.setAttribute autoguiModule pyName pyValue
diff --git a/src/AutoGUI/Info.hs b/src/AutoGUI/Info.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Info.hs
@@ -0,0 +1,46 @@
+module AutoGUI.Info
+  ( size
+  , onScreen
+  , position
+  )
+
+where
+
+import AutoGUI.Call
+import AutoGUI.Run
+
+import Control.Monad.IO.Class
+import Data.Text (Text)
+import qualified Data.Text as T
+
+import qualified CPython                  as Py
+import qualified CPython.Constants        as Py
+import qualified CPython.Protocols.Object as Py
+import qualified CPython.Types            as Py
+import qualified CPython.Types.Module     as Py
+
+-- | (screenWidth, screenHeight) of the primary monitor in pixels
+size :: AutoGUI (Integer, Integer)
+size = getXY "size"
+
+-- | (x, y) position of the mouse
+position :: AutoGUI (Integer, Integer)
+position = getXY "position"
+
+-- | Test whether (x, y) is within the screen size
+onScreen :: Integer -> Integer -> AutoGUI Bool
+onScreen x y = do
+  pyObjBool <- call' "onScreen" [IntArg x, IntArg y]
+  liftIO $ Py.toBool pyObjBool
+
+getXY :: Text -> AutoGUI (Integer, Integer)
+getXY func = do
+  pyObjTuple <- call' func []
+  liftIO $ do
+    Just tuple <- Py.cast pyObjTuple
+    [pyObjWidth, pyObjHeight] <- Py.fromTuple tuple
+    Just pyWidth <- Py.cast pyObjWidth
+    Just pyHeight <- Py.cast pyObjHeight
+    width <- Py.fromInteger pyWidth
+    height <- Py.fromInteger pyHeight
+    pure (width, height)
diff --git a/src/AutoGUI/Keyboard.hs b/src/AutoGUI/Keyboard.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Keyboard.hs
@@ -0,0 +1,51 @@
+module AutoGUI.Keyboard
+  ( write
+  , typewrite
+  , typewriteKeys
+  , writeWithInterval
+  , press
+  , keyDown
+  , keyUp
+  , hotkey
+  )
+where
+
+import AutoGUI.Call
+import AutoGUI.Keys
+import AutoGUI.Run
+
+import Data.Text (Text)
+import qualified Data.Text as T
+
+-- | Write out some Text as though it were entered with the keyboard
+write :: Text -> AutoGUI ()
+write msg = call "write" [TextArg msg]
+
+-- | Write out some Text as though it were entered with the keyboard, newline is enter
+typewrite :: Text -> AutoGUI ()
+typewrite msg = call "typewrite" [TextArg msg]
+
+-- | Write out some Text as though it were entered with the keyboard, newline is enter
+typewriteKeys :: [Key] -> AutoGUI ()
+typewriteKeys keys = call "typewrite" $ KeyArg <$> keys
+
+-- | Write out some Text as though it were entered with the keyboard, with a specified
+--   number of seconds between keypresses
+writeWithInterval :: Text -> Double -> AutoGUI ()
+writeWithInterval msg interval = call "write" [TextArg msg, DoubleArg interval]
+
+-- | Simulate a keypress
+press :: Key -> AutoGUI ()
+press key = call "press" [KeyArg key]
+
+-- | Simulate holding a key down
+keyDown :: Key -> AutoGUI ()
+keyDown key = call "keyDown" [KeyArg key]
+
+-- | Simulate releasing a key
+keyUp :: Key -> AutoGUI ()
+keyUp key = call "keyUp" [KeyArg key]
+
+-- | Press a key combination
+hotkey :: [Key] -> AutoGUI ()
+hotkey keys = call "hotkey" $ KeyArg <$> keys
diff --git a/src/AutoGUI/Keys.hs b/src/AutoGUI/Keys.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Keys.hs
@@ -0,0 +1,251 @@
+module AutoGUI.Keys
+  ( mkKey
+  , keyToText
+  , key
+  , isValidKey
+  , keys
+  , Key
+  )
+where
+
+import Data.Set (Set)
+import qualified Data.Set as Set
+import Data.Text (Text)
+import qualified Data.Text as T
+import Language.Haskell.TH
+import Language.Haskell.TH.Quote
+import Language.Haskell.TH.Syntax
+
+newtype Key = Key String
+  deriving (Eq, Show, Ord, Lift)
+
+mkKey :: Text -> Maybe Key
+mkKey key = if isValidKey key then Just (Key (T.unpack key)) else Nothing
+
+keyToText :: Key -> Text
+keyToText (Key text) = T.pack text
+
+-- | This quasiquoter lets you use [key|enter|] at compile time,
+--   so you don't get a Maybe as you would from mkKey
+key :: QuasiQuoter
+key =
+  QuasiQuoter
+    { quoteExp = compileKeyExp
+    , quotePat = compileKeyPat
+    , quoteDec = error "Key is not a declaration"
+    , quoteType = error "Key is not a type"
+    }
+  where
+    compileKeyExp :: String -> Q Exp
+    compileKeyExp s = case mkKey (T.pack s) of
+      Nothing -> fail $ "`" <> show s <> "` is not a valid key"
+      Just key -> [|key|]
+
+    compileKeyPat :: String -> Q Pat
+    compileKeyPat s = case mkKey (T.pack s) of
+      Nothing -> fail $ "`" <> show s <> "` is not a valid key"
+      Just (Key str) -> pure $ ConP 'Key [LitP $ StringL str]
+
+isValidKey :: Text -> Bool
+isValidKey key = key `Set.member` keysText
+
+keys :: Set Key
+keys = Set.map (Key . T.unpack) keysText
+
+keysText :: Set Text
+keysText = Set.fromList
+  [ "\t"
+  , "\n"
+  , "\r"
+  , " "
+  , "!"
+  , "'"
+  , "#"
+  , "$"
+  , "%"
+  , "&"
+  , "\""
+  , "("
+  , ")"
+  , "*"
+  , "+"
+  , ","
+  , "-"
+  , "."
+  , "/"
+  , "0"
+  , "1"
+  , "2"
+  , "3"
+  , "4"
+  , "5"
+  , "6"
+  , "7"
+  , "8"
+  , "9"
+  , ":"
+  , ";"
+  , "<"
+  , "="
+  , ">"
+  , "?"
+  , "@"
+  , "["
+  , "\\"
+  , "]"
+  , "^"
+  , "_"
+  , "`"
+  , "a"
+  , "b"
+  , "c"
+  , "d"
+  , "e"
+  , "f"
+  , "g"
+  , "h"
+  , "i"
+  , "j"
+  , "k"
+  , "l"
+  , "m"
+  , "n"
+  , "o"
+  , "p"
+  , "q"
+  , "r"
+  , "s"
+  , "t"
+  , "u"
+  , "v"
+  , "w"
+  , "x"
+  , "y"
+  , "z"
+  , "{"
+  , "|"
+  , "}"
+  , "~"
+  , "accept"
+  , "add"
+  , "alt"
+  , "altleft"
+  , "altright"
+  , "apps"
+  , "backspace"
+  , "browserback"
+  , "browserfavorites"
+  , "browserforward"
+  , "browserhome"
+  , "browserrefresh"
+  , "browsersearch"
+  , "browserstop"
+  , "capslock"
+  , "clear"
+  , "convert"
+  , "ctrl"
+  , "ctrlleft"
+  , "ctrlright"
+  , "decimal"
+  , "del"
+  , "delete"
+  , "divide"
+  , "down"
+  , "end"
+  , "enter"
+  , "esc"
+  , "escape"
+  , "execute"
+  , "f1"
+  , "f10"
+  , "f11"
+  , "f12"
+  , "f13"
+  , "f14"
+  , "f15"
+  , "f16"
+  , "f17"
+  , "f18"
+  , "f19"
+  , "f2"
+  , "f20"
+  , "f21"
+  , "f22"
+  , "f23"
+  , "f24"
+  , "f3"
+  , "f4"
+  , "f5"
+  , "f6"
+  , "f7"
+  , "f8"
+  , "f9"
+  , "final"
+  , "fn"
+  , "hanguel"
+  , "hangul"
+  , "hanja"
+  , "help"
+  , "home"
+  , "insert"
+  , "junja"
+  , "kana"
+  , "kanji"
+  , "launchapp1"
+  , "launchapp2"
+  , "launchmail"
+  , "launchmediaselect"
+  , "left"
+  , "modechange"
+  , "multiply"
+  , "nexttrack"
+  , "nonconvert"
+  , "num0"
+  , "num1"
+  , "num2"
+  , "num3"
+  , "num4"
+  , "num5"
+  , "num6"
+  , "num7"
+  , "num8"
+  , "num9"
+  , "numlock"
+  , "pagedown"
+  , "pageup"
+  , "pause"
+  , "pgdn"
+  , "pgup"
+  , "playpause"
+  , "prevtrack"
+  , "print"
+  , "printscreen"
+  , "prntscrn"
+  , "prtsc"
+  , "prtscr"
+  , "return"
+  , "right"
+  , "scrolllock"
+  , "select"
+  , "separator"
+  , "shift"
+  , "shiftleft"
+  , "shiftright"
+  , "sleep"
+  , "space"
+  , "stop"
+  , "subtract"
+  , "tab"
+  , "up"
+  , "volumedown"
+  , "volumemute"
+  , "volumeup"
+  , "win"
+  , "winleft"
+  , "winright"
+  , "yen"
+  , "command"
+  , "option"
+  , "optionleft"
+  , "optionright"
+  ]
diff --git a/src/AutoGUI/MessageBoxes.hs b/src/AutoGUI/MessageBoxes.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/MessageBoxes.hs
@@ -0,0 +1,57 @@
+module AutoGUI.MessageBoxes
+  ( alert
+  , confirm
+  , password
+  , prompt
+  )
+where
+
+import AutoGUI.Call
+import AutoGUI.Run
+
+import Control.Monad
+import Control.Monad.IO.Class
+import Data.Text (Text)
+import qualified Data.Text as T
+
+import qualified CPython                  as Py
+import qualified CPython.Constants        as Py
+import qualified CPython.Protocols.Object as Py
+import qualified CPython.Types            as Py
+import qualified CPython.Types.Module     as Py
+
+-- | Show a box onscreen until dismissed
+alert :: Text -> AutoGUI ()
+alert msg = call "alert" [TextArg msg]
+
+-- | Show a box onscreen until a user hits OK or Cancel
+--   Return True on OK, False on Cancel, and False if user closes the box
+confirm :: Text -> AutoGUI Bool
+confirm msg = do
+  pyObjOkOrCancel <- call' "confirm" [TextArg msg]
+  liftIO $ do
+    pyOkOrCancelCasted <- Py.cast pyObjOkOrCancel
+    case pyOkOrCancelCasted of
+      Nothing -> pure False
+      Just pyOkOrCancel -> do
+        okOrCancel <- Py.fromUnicode pyOkOrCancel
+        pure $ if okOrCancel == "OK" then True else False
+
+-- | Show a box onscreen, allowing user to enter some screened text
+--   Return empty string if user closes the box
+password :: Text -> AutoGUI Text
+password msg = textInput "password" msg
+
+-- | Show a box onscreen, allowing user to enter some text
+--   Return empty string if user closes the box
+prompt :: Text -> AutoGUI Text
+prompt msg = textInput "prompt" msg
+
+textInput :: Text -> Text -> AutoGUI Text
+textInput func msg = do
+  pyObjPrompt <- call' func [TextArg msg]
+  liftIO $ do
+    pyPrompt <- Py.cast pyObjPrompt
+    case pyPrompt of
+      Just promptText -> Py.fromUnicode promptText
+      Nothing -> pure ""
diff --git a/src/AutoGUI/Mouse.hs b/src/AutoGUI/Mouse.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Mouse.hs
@@ -0,0 +1,110 @@
+module AutoGUI.Mouse
+  ( moveTo
+  , moveToDuration
+  , moveRel
+  , moveRelDuration
+  , click
+  , leftClick
+  , doubleClick
+  , tripleClick
+  , rightClick
+  , middleClick
+  , moveAndClick
+  , drag
+  , dragDuration
+  , dragTo
+  , dragToDuration
+  , dragRel
+  , dragRelDuration
+  , scroll
+  , mouseDown
+  , mouseUp
+  )
+where
+
+import AutoGUI.Call
+import AutoGUI.Run
+
+-- | Move the mouse to an (x, y) position
+moveTo :: Integer -> Integer -> AutoGUI ()
+moveTo x y = call "moveTo" [IntArg x, IntArg y]
+
+-- | Move the mouse to an (x, y) position, over a number of seconds
+moveToDuration :: Integer -> Integer -> Double -> AutoGUI ()
+moveToDuration x y duration = call "moveTo" [IntArg x, IntArg y, DoubleArg duration]
+
+-- | Move the mouse relative to where it is now
+moveRel :: Integer -> Integer -> AutoGUI ()
+moveRel xOffset yOffset = call "moveRel" [IntArg xOffset, IntArg yOffset]
+
+-- | Move the mouse relative to where it is now, over a number of seconds
+moveRelDuration :: Integer -> Integer -> Double -> AutoGUI ()
+moveRelDuration xOffset yOffset duration =
+  call "moveRel" [IntArg xOffset, IntArg yOffset, DoubleArg duration]
+
+-- | Click the mouse
+click :: AutoGUI ()
+click = call "click" []
+
+-- | Left click the mouse
+leftClick :: AutoGUI ()
+leftClick = click
+
+-- | Double click the mouse
+doubleClick :: AutoGUI ()
+doubleClick = call "doubleClick" []
+
+-- | Triple click the mouse
+tripleClick :: AutoGUI ()
+tripleClick = call "tripleClick" []
+
+-- | Right click the mouse
+rightClick :: AutoGUI ()
+rightClick = call "rightClick" []
+
+-- | Middle click the mouse
+middleClick :: AutoGUI ()
+middleClick = call "middleClick" []
+
+-- | Move the mouse to some (x, y) position and click there
+moveAndClick :: Integer -> Integer -> AutoGUI ()
+moveAndClick x y = moveTo x y >> click
+
+-- | Clicks and drags the mouse through a motion of (x, y)
+drag :: Integer -> Integer -> AutoGUI ()
+drag x y = call "drag" [IntArg x, IntArg y]
+
+-- | Clicks and drags the mouse through a motion of (x, y), over a number of seconds
+dragDuration :: Integer -> Integer -> Double -> AutoGUI ()
+dragDuration x y duration = call "drag" [IntArg x, IntArg y, DoubleArg duration]
+
+-- | Clicks and drags the mouse to the position (x, y)
+dragTo :: Integer -> Integer -> AutoGUI ()
+dragTo x y = call "dragTo" [IntArg x, IntArg y]
+
+-- | Clicks and drags the mouse to the position (x, y), over a number of seconds
+dragToDuration :: Integer -> Integer -> Double -> AutoGUI ()
+dragToDuration x y duration = call "dragTo" [IntArg x, IntArg y, DoubleArg duration]
+
+-- | Clicks and drags the mouse through a motion of (x, y)
+dragRel :: Integer -> Integer -> AutoGUI ()
+dragRel xOffset yOffset = call "dragRel" [IntArg xOffset, IntArg yOffset]
+
+-- | Clicks and drags the mouse through a motion of (x, y)
+dragRelDuration :: Integer -> Integer -> Double -> AutoGUI ()
+dragRelDuration xOffset yOffset duration =
+  call "dragRel" [IntArg xOffset, IntArg yOffset, DoubleArg duration]
+
+-- | Scroll up (positive) or down (negative)
+scroll :: Integer -> AutoGUI ()
+scroll amount = call "scroll" [IntArg amount]
+
+-- TODO: support mouseDown/mouseUp for other mouse buttons besides LMB
+
+-- | Press the mouse button down
+mouseDown :: AutoGUI ()
+mouseDown = call "mouseDown" []
+
+-- | Release the mouse button
+mouseUp :: AutoGUI ()
+mouseUp = call "mouseUp" []
diff --git a/src/AutoGUI/Run.hs b/src/AutoGUI/Run.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Run.hs
@@ -0,0 +1,28 @@
+module AutoGUI.Run
+  ( runAutoGUI
+  , AutoGUIT(..)
+  , AutoGUI(..)
+  )
+where
+
+import Control.Monad
+import Control.Monad.IO.Class
+import Control.Monad.Reader
+
+import qualified CPython                  as Py
+import qualified CPython.Constants        as Py
+import qualified CPython.Protocols.Object as Py
+import qualified CPython.Types            as Py
+import qualified CPython.Types.Module     as Py
+
+newtype AutoGUIT m a =
+  AutoGUIT { unAutoGUI :: ReaderT Py.Module m a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadReader Py.Module)
+
+type AutoGUI = AutoGUIT IO
+
+runAutoGUI :: AutoGUI a -> IO a
+runAutoGUI autogui = do
+  Py.initialize
+  autoguiModule <- Py.importModule "pyautogui"
+  runReaderT (unAutoGUI autogui) autoguiModule
diff --git a/src/AutoGUI/Screen.hs b/src/AutoGUI/Screen.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Screen.hs
@@ -0,0 +1,64 @@
+module AutoGUI.Screen
+  ( locateOnScreen
+  , locateCenterOnScreen
+  )
+where
+
+import AutoGUI.Call
+import AutoGUI.Run
+
+import Control.Monad.IO.Class
+import Data.Text (Text)
+import qualified Data.Text as T
+
+import qualified CPython                  as Py
+import qualified CPython.Constants        as Py
+import qualified CPython.Protocols.Object as Py
+import qualified CPython.Types            as Py
+import qualified CPython.Types.Module     as Py
+
+-- TODO: Returns a Pillow/PIL Image object. Not nicely supported yet.
+-- screenshot :: AutoGUI Py.SomeObject
+-- screenshot = call' "screenshot" []
+
+-- | Return (left, top, width, height) of first place the image is found
+locateOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer, Integer, Integer))
+locateOnScreen path = do
+  pyObjTuple <- call' "locateOnScreen" [TextArg $ T.pack path]
+  isNone <- liftIO $ Py.isNone pyObjTuple
+  if isNone
+    then pure Nothing
+    else liftIO $ do
+      Just tuple <- Py.cast pyObjTuple
+      [pyObjLeft, pyObjTop, pyObjWidth, pyObjHeight] <- Py.fromTuple tuple
+      Just pyLeft <- Py.cast pyObjLeft
+      Just pyTop <- Py.cast pyObjTop
+      Just pyWidth <- Py.cast pyObjWidth
+      Just pyHeight <- Py.cast pyObjHeight
+      left <- Py.fromInteger pyLeft
+      top <- Py.fromInteger pyTop
+      width <- Py.fromInteger pyWidth
+      height <- Py.fromInteger pyHeight
+      pure $ Just (left, top, width, height)
+
+-- TODO: Returns a Python generator. Convert that to a Haskell list.
+-- locateAllOnScreen :: FilePath -> AutoGUI [(Integer, Integer, Integer, Integer)]
+-- locateAllOnScreen path = call' "locateAllOnScreen" [TextArg $ T.pack path]
+
+-- | Return (x, y) of center of an image, if the image is found
+locateCenterOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer))
+locateCenterOnScreen path = do
+  pyObjTuple <- call' "locateCenterOnScreen" [TextArg $ T.pack path]
+  isNone <- liftIO $ Py.isNone pyObjTuple
+  if isNone
+    then pure Nothing
+    else liftIO $ do
+      Just tuple <- Py.cast pyObjTuple
+      [pyObjWidth, pyObjHeight] <- Py.fromTuple tuple
+      Just pyWidth <- Py.cast pyObjWidth
+      Just pyHeight <- Py.cast pyObjHeight
+      width <- Py.fromInteger pyWidth
+      height <- Py.fromInteger pyHeight
+      pure $ Just (width, height)
+
+-- TODO pixelMatchesColor
