packages feed

hsautogui (empty) → 0.1.0

raw patch · 16 files changed

+1008/−0 lines, 16 filesdep +basedep +containersdep +cpythonsetup-changed

Dependencies added: base, containers, cpython, hsautogui, mtl, template-haskell, text

Files

+ LICENSE view
@@ -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.
+ README.md view
@@ -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+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -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)+-}
+ changelog.md view
@@ -0,0 +1,5 @@+# ChangeLog++## 0.1.0++Initial bindings to (most of) PyAutoGUI
+ hsautogui.cabal view
@@ -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
+ src/AutoGUI.hs view
@@ -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
+ src/AutoGUI/Call.hs view
@@ -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 ()
+ src/AutoGUI/Debug.hs view
@@ -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
+ src/AutoGUI/Info.hs view
@@ -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)
+ src/AutoGUI/Keyboard.hs view
@@ -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
+ src/AutoGUI/Keys.hs view
@@ -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"+  ]
+ src/AutoGUI/MessageBoxes.hs view
@@ -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 ""
+ src/AutoGUI/Mouse.hs view
@@ -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" []
+ src/AutoGUI/Run.hs view
@@ -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
+ src/AutoGUI/Screen.hs view
@@ -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