packages feed

hsautogui 0.2.0 → 0.3.0

raw patch · 16 files changed

+292/−345 lines, 16 filesdep +hspecdep ~cpython

Dependencies added: hspec

Dependency ranges changed: cpython

Files

README.md view
@@ -10,28 +10,30 @@  ```haskell import AutoGUI-main = runAutoGUI $ write "Hello, world!"+main = do+  initialize+  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:+The following Haskell and Python examples do the same thing:  ```haskell import AutoGUI-import Control.Monad.IO.Class (liftIO)+import Control.Monad (forM_)  main :: IO () main = do+  initialize   putStrLn "3 seconds until beeping begins"   sleep 3-  runAutoGUI $-    forM_ [1..100] $ \i -> do-      write "beep"-      press [key|enter|]-      liftIO $ sleep 0.5+  forM_ [1..100] $ \i -> do+    write "beep"+    press [key|enter|]+    sleep 0.5 ```  ```python@@ -57,27 +59,25 @@  ### 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+- `pause :: Double -> IO ()` - Set a number of seconds to wait in between autogui actions+- `failsafe :: Bool -> IO ()` - 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+- `size :: IO (Integer, Integer)` - (screenWidth, screenHeight) of the primary monitor in pixels+- `position :: IO (Integer, Integer)` - (x, y) position of the mouse+- `onScreen :: Integer -> Integer -> IO 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+- `write :: Text -> IO ()` - Write out some Text as though it were entered with the keyboard+- `typewrite :: Text -> IO ()` - Write out some Text as though it were entered with the keyboard, newline is enter+- `typewriteKeys :: [Key] -> IO ()` - Write out some Text as though it were entered with the keyboard, newline is enter+- `writeWithInterval :: Text -> Double -> IO ()` - Write out some Text as though it were entered with the keyboard, with a specified number of seconds between keypresses+- `press :: Key -> IO ()` - Simulate a keypress+- `keyDown :: Key -> IO ()` - Simulate holding a key down+- `keyUp :: Key -> IO ()` - Simulate releasing a key+- `hotkey :: [Key] -> IO ()` - 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@@ -86,35 +86,35 @@ - `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+### Message Boxes+- `alert :: Text -> IO ()` - Show a box onscreen until dismissed+- `confirm :: Text -> IO 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 -> IO Text` - Show a box onscreen, allowing user to enter some screened text. Return empty string if user closes the box+- `prompt :: Text -> IO 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+- `moveTo :: Integer -> Integer -> IO ()` - Move the mouse to an (x, y) position+- `moveToDuration :: Integer -> Integer -> Double -> IO ()` - Move the mouse to an (x, y) position, over a number of seconds+- `moveRel :: Integer -> Integer -> IO ()` - Move the mouse relative to where it is now+- `moveRelDuration :: Integer -> Integer -> Double -> IO ()` - Move the mouse relative to where it is now, over a number of seconds+- `click :: MouseButton -> IO ()` - Click the specified mouse button+- `leftClick :: IO ()` - Left click the mouse+- `doubleClick :: IO ()` - Double click the mouse+- `tripleClick :: IO ()` - Triple click the mouse+- `rightClick :: IO ()` - Right click the mouse+- `middleClick :: IO ()` - Middle click the mouse+- `moveAndClick :: Integer -> Integer -> IO ()` - Move the mouse to some (x, y) position and click there+- `drag :: Integer -> Integer -> IO ()` - Clicks and drags the mouse through a motion of (x, y)+- `dragDuration :: Integer -> Integer -> Double -> IO ()` - Clicks and drags the mouse through a motion of (x, y), over a number of seconds+- `dragTo :: Integer -> Integer -> IO ()` - Clicks and drags the mouse to the position (x, y)+- `dragToDuration :: Integer -> Integer -> Double -> IO ()` - Clicks and drags the mouse to the position (x, y), over a number of seconds+- `dragRel :: Integer -> Integer -> IO ()` - Clicks and drags the mouse through a motion of (x, y)+- `dragRelDuration :: Integer -> Integer -> Double -> IO ()` - Clicks and drags the mouse through a motion of (x, y)+- `scroll :: Integer -> IO ()` - Scroll up (positive) or down (negative)+- `mouseDown :: IO ()` - Press the mouse button down+- `mouseUp :: IO ()` - 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+- `locateOnScreen :: FilePath -> IO (Maybe (Integer, Integer, Integer, Integer))` - Return (left, top, width, height) of first place the image is found+- `locateCenterOnScreen :: FilePath -> IO (Maybe (Integer, Integer))` - Return (x, y) of center of an image, if the image is found 
− app/Main.hs
@@ -1,28 +0,0 @@-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
@@ -1,5 +1,9 @@ # ChangeLog +## 0.3.0++Use the new CPython.Simple API+ ## 0.2.0  Update cpython version to be >= 3.5
hsautogui.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: c820d39c8cb3bc4809c69dc07f83f8d8acfc9dc85e35fe48959ba344c966e88c+-- hash: afddd7eba7bc5790dc58b9e8518daf7be96aa5123b503b0f420f9272aff74185  name:           hsautogui-version:        0.2.0+version:        0.3.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@@ -32,40 +32,39 @@       AutoGUI       AutoGUI.Call       AutoGUI.Debug+      AutoGUI.Discard       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:-      python   build-depends:       base     , containers-    , cpython >=3.5+    , cpython >=3.5.1     , mtl     , template-haskell     , text   default-language: Haskell2010 -executable hsautogui-sample-exe+test-suite hsautogui-test+  type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:       Paths_hsautogui   hs-source-dirs:-      app+      test   default-extensions: OverloadedStrings QuasiQuotes   ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -W-  extra-libraries:-      python   build-depends:       base >=4.7 && <5+    , cpython >=3.5.1     , hsautogui+    , hspec   default-language: Haskell2010
src/AutoGUI.hs view
@@ -1,7 +1,5 @@ module AutoGUI-  ( AutoGUI(..)-  , AutoGUIT(..)-  , Key+  ( Key   , alert   , click   , confirm@@ -14,6 +12,7 @@   , dragToDuration   , failsafe   , hotkey+  , initialize   , isValidKey   , key   , keyDown@@ -39,7 +38,6 @@   , press   , prompt   , rightClick-  , runAutoGUI   , scroll   , size   , sleep@@ -57,5 +55,5 @@ import AutoGUI.Keys import AutoGUI.MessageBoxes import AutoGUI.Mouse-import AutoGUI.Run import AutoGUI.Screen+import CPython.Simple (initialize)
src/AutoGUI/Call.hs view
@@ -1,41 +1,10 @@-module AutoGUI.Call where--import AutoGUI.Run-import AutoGUI.Keys+module AutoGUI.Call+  ( pyautogui )+where -import Control.Monad.Reader+import CPython.Simple+import CPython.Simple.Instances 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 ()+pyautogui :: FromPy a => Text -> [Arg] -> [(Text, Arg)] -> IO a+pyautogui = call "pyautogui"
src/AutoGUI/Debug.hs view
@@ -5,39 +5,25 @@   ) where -import AutoGUI.Call-import AutoGUI.Run+import AutoGUI.Discard  import Control.Concurrent import Control.Monad.IO.Class import Control.Monad.Reader+import CPython.Simple 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+pause :: Double -> IO ()+pause = setAttribute "pyautogui" "PAUSE"  -- | 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+failsafe :: Bool -> IO ()+failsafe = setAttribute "pyautogui" "FAILSAFE"  -- | 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/Discard.hs view
@@ -0,0 +1,38 @@+module AutoGUI.Discard where++import CPython.Simple.Instances+import qualified CPython.Types.Tuple as Py (fromTuple, toTuple)+import qualified CPython.Types.Unicode as Py+import Data.Typeable+import qualified Data.Text as T++-- TODO: Cpython.Simple should export the stuff from CPython.Simple.Instances++-- TODO toPy Bool+instance ToPy Bool where+  toPy b = error "nope"++-- TODO fromPy Bool+instance FromPy Bool where+  fromPy _ = pure False++-- TODO FilePath+-- instance {-# OVERLAPS #-} ToPy String where+  -- toPy = easyToPy Py.toUnicode . T.pack++-- TODO: fromPy (a, b, c, d)+instance (FromPy a, FromPy b, FromPy c, FromPy d) => FromPy (a, b, c, d) where+  fromPy val = do+    [pyA, pyB, pyC, pyD] <- easyFromPy Py.fromTuple Proxy val+    a <- fromPy pyA+    b <- fromPy pyB+    c <- fromPy pyC+    d <- fromPy pyD+    pure (a, b, c, d)++instance (ToPy a, ToPy b, ToPy c) => ToPy (a, b, c) where+  toPy (a, b, c) = do+    pyA <- toPy a+    pyB <- toPy b+    pyC <- toPy c+    easyToPy Py.toTuple [pyA, pyB, pyC]
src/AutoGUI/Info.hs view
@@ -7,40 +7,20 @@ where  import AutoGUI.Call-import AutoGUI.Run-+import AutoGUI.Discard import Control.Monad.IO.Class+import CPython.Simple 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"+size :: IO (Integer, Integer)+size = pyautogui "size" [] []  -- | (x, y) position of the mouse-position :: AutoGUI (Integer, Integer)-position = getXY "position"+position :: IO (Integer, Integer)+position = pyautogui "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)+onScreen :: Integer -> Integer -> IO Bool+onScreen x y = pyautogui "onScreen" [arg x, arg y] []
src/AutoGUI/Keyboard.hs view
@@ -10,42 +10,43 @@   ) where -import AutoGUI.Call import AutoGUI.Keys-import AutoGUI.Run+import AutoGUI.Call +import CPython.Simple+import CPython.Simple.Instances 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 :: Text -> IO ()+write msg = pyautogui "write" [arg 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]+typewrite :: Text -> IO ()+typewrite msg = pyautogui "typewrite" [arg 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+typewriteKeys :: [Key] -> IO ()+typewriteKeys keys = pyautogui "typewrite" (map arg 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]+writeWithInterval :: Text -> Double -> IO ()+writeWithInterval msg interval = pyautogui "write" [arg msg, arg interval] []  -- | Simulate a keypress-press :: Key -> AutoGUI ()-press key = call "press" [KeyArg key]+press :: Key -> IO ()+press key = pyautogui "press" [arg key] []  -- | Simulate holding a key down-keyDown :: Key -> AutoGUI ()-keyDown key = call "keyDown" [KeyArg key]+keyDown :: Key -> IO ()+keyDown key = pyautogui "keyDown" [arg key] []  -- | Simulate releasing a key-keyUp :: Key -> AutoGUI ()-keyUp key = call "keyUp" [KeyArg key]+keyUp :: Key -> IO ()+keyUp key = pyautogui "keyUp" [arg key] []  -- | Press a key combination-hotkey :: [Key] -> AutoGUI ()-hotkey keys = call "hotkey" $ KeyArg <$> keys+hotkey :: [Key] -> IO ()+hotkey keys = pyautogui "hotkey" (map arg keys) []
src/AutoGUI/Keys.hs view
@@ -8,6 +8,7 @@   ) where +import CPython.Simple.Instances import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text)@@ -18,6 +19,10 @@  newtype Key = Key String   deriving (Eq, Show, Ord, Lift)++-- TODO make the string overlapping instance nicer+instance ToPy Key where+  toPy (Key str) = toPy $ T.pack str  mkKey :: Text -> Maybe Key mkKey key = if isValidKey key then Just (Key (T.unpack key)) else Nothing
src/AutoGUI/MessageBoxes.hs view
@@ -7,51 +7,36 @@ where  import AutoGUI.Call-import AutoGUI.Run-+import CPython.Simple 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]+alert :: Text -> IO ()+alert msg = pyautogui "alert" [arg 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 :: Text -> IO 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+  okOrCancel :: Text <- pyautogui "confirm" [arg msg] []+  pure $ okOrCancel == "OK"  -- | Show a box onscreen, allowing user to enter some screened text --   Return empty string if user closes the box-password :: Text -> AutoGUI Text+password :: Text -> IO 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 :: Text -> IO Text prompt msg = textInput "prompt" msg -textInput :: Text -> Text -> AutoGUI Text+textInput :: Text -> Text -> IO 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 ""+  pyPrompt :: Maybe Text <- pyautogui func [arg msg] []+  pure $ case pyPrompt of+    Just promptText -> promptText+    Nothing -> ""
src/AutoGUI/Mouse.hs view
@@ -1,5 +1,8 @@+{-# LANGUAGE TypeApplications #-}+ module AutoGUI.Mouse-  ( moveTo+  ( MouseButton(..)+  , moveTo   , moveToDuration   , moveRel   , moveRelDuration@@ -23,88 +26,109 @@ where  import AutoGUI.Call-import AutoGUI.Run+import CPython.Simple+import CPython.Simple.Instances+import Data.Text +data MouseButton+  = LeftMouseButton+  | RightMouseButton+  | MiddleMouseButton++-- TODO: try to make this as easy/automatic as possible+instance ToPy MouseButton where+  toPy LeftMouseButton   = toPy @Text "left"+  toPy RightMouseButton  = toPy @Text "right"+  toPy MiddleMouseButton = toPy @Text "middle"+ -- | Move the mouse to an (x, y) position-moveTo :: Integer -> Integer -> AutoGUI ()-moveTo x y = call "moveTo" [IntArg x, IntArg y]+moveTo :: Integer -> Integer -> IO ()+moveTo x y = pyautogui "moveTo" [arg x, arg 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]+moveToDuration :: Integer -> Integer -> Double -> IO ()+moveToDuration x y duration =+  pyautogui "moveTo" [arg x, arg y] [("duration", arg duration)]  -- | Move the mouse relative to where it is now-moveRel :: Integer -> Integer -> AutoGUI ()-moveRel xOffset yOffset = call "moveRel" [IntArg xOffset, IntArg yOffset]+moveRel :: Integer -> Integer -> IO ()+moveRel xOffset yOffset =+  pyautogui "moveRel" [arg xOffset, arg yOffset] []  -- | Move the mouse relative to where it is now, over a number of seconds-moveRelDuration :: Integer -> Integer -> Double -> AutoGUI ()+moveRelDuration :: Integer -> Integer -> Double -> IO () moveRelDuration xOffset yOffset duration =-  call "moveRel" [IntArg xOffset, IntArg yOffset, DoubleArg duration]---- | Click the mouse-click :: AutoGUI ()-click = call "click" []+  pyautogui "moveRel" [arg xOffset, arg yOffset, arg duration] [] --- | Left click the mouse-leftClick :: AutoGUI ()-leftClick = click+-- | Click a specified mouse button+click :: MouseButton -> IO ()+click button = pyautogui "click" [] [("button", arg button)]  -- | Double click the mouse-doubleClick :: AutoGUI ()-doubleClick = call "doubleClick" []+doubleClick :: IO ()+doubleClick = pyautogui "doubleClick" [] []  -- | Triple click the mouse-tripleClick :: AutoGUI ()-tripleClick = call "tripleClick" []+tripleClick :: IO ()+tripleClick = pyautogui "tripleClick" [] [] +-- | Left click the mouse+leftClick :: IO ()+leftClick = click LeftMouseButton+ -- | Right click the mouse-rightClick :: AutoGUI ()-rightClick = call "rightClick" []+rightClick :: IO ()+rightClick = click RightMouseButton  -- | Middle click the mouse-middleClick :: AutoGUI ()-middleClick = call "middleClick" []+middleClick :: IO ()+middleClick = click MiddleMouseButton  -- | Move the mouse to some (x, y) position and click there-moveAndClick :: Integer -> Integer -> AutoGUI ()-moveAndClick x y = moveTo x y >> click+moveAndClick :: Integer -> Integer -> IO ()+moveAndClick x y = moveTo x y >> click LeftMouseButton  -- | Clicks and drags the mouse through a motion of (x, y)-drag :: Integer -> Integer -> AutoGUI ()-drag x y = call "drag" [IntArg x, IntArg y]+drag :: Integer -> Integer -> IO ()+drag x y = pyautogui "drag" [arg x, arg 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]+dragDuration :: Integer -> Integer -> Double -> IO ()+dragDuration x y duration = pyautogui "drag" [arg x, arg y, arg duration] []  -- | Clicks and drags the mouse to the position (x, y)-dragTo :: Integer -> Integer -> AutoGUI ()-dragTo x y = call "dragTo" [IntArg x, IntArg y]+dragTo :: Integer -> Integer -> IO ()+dragTo x y = pyautogui "dragTo" [arg x, arg 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]+dragToDuration :: Integer -> Integer -> Double -> IO ()+dragToDuration x y duration = pyautogui "dragTo" [arg x, arg y, arg 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]+dragRel :: Integer -> Integer -> IO ()+dragRel xOffset yOffset = pyautogui "dragRel" [arg xOffset, arg yOffset] []  -- | Clicks and drags the mouse through a motion of (x, y)-dragRelDuration :: Integer -> Integer -> Double -> AutoGUI ()+dragRelDuration :: Integer -> Integer -> Double -> IO () dragRelDuration xOffset yOffset duration =-  call "dragRel" [IntArg xOffset, IntArg yOffset, DoubleArg duration]+  pyautogui "dragRel" [arg xOffset, arg yOffset, arg duration] []  -- | Scroll up (positive) or down (negative)-scroll :: Integer -> AutoGUI ()-scroll amount = call "scroll" [IntArg amount]+scroll :: Integer -> IO ()+scroll amount = pyautogui "scroll" [arg amount] [] --- TODO: support mouseDown/mouseUp for other mouse buttons besides LMB+-- | Press the left mouse button down+mouseDown :: IO ()+mouseDown = pyautogui "mouseDown" [] [] --- | Press the mouse button down-mouseDown :: AutoGUI ()-mouseDown = call "mouseDown" []+-- | Release the left mouse button+mouseUp :: IO ()+mouseUp = pyautogui "mouseUp" [] [] --- | Release the mouse button-mouseUp :: AutoGUI ()-mouseUp = call "mouseUp" []+-- | Press a specified mouse button+mouseButtonDown :: MouseButton -> IO ()+mouseButtonDown button = pyautogui "mouseDown" [] [("button", arg button)]++-- | Press a specified mouse button+mouseButtonUp :: MouseButton -> IO ()+mouseButtonUp button = pyautogui "mouseUp" [] [("button", arg button)]
− src/AutoGUI/Run.hs
@@ -1,28 +0,0 @@-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
@@ -1,64 +1,41 @@ module AutoGUI.Screen   ( locateOnScreen   , locateCenterOnScreen+  , Color(..)   ) where  import AutoGUI.Call-import AutoGUI.Run-+import AutoGUI.Discard+import CPython.Simple 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+type Color = (Integer, Integer, Integer) --- TODO: Returns a Pillow/PIL Image object. Not nicely supported yet.+-- TODO: Returns a Pillow/PIL Image object. Not 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)+locateOnScreen :: FilePath -> IO (Maybe (Integer, Integer, Integer, Integer))+locateOnScreen path =+  pyautogui "locateOnScreen" [arg $ T.pack path] [] --- 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]+-- TODO: Returns a Python generator. Convert that to a Haskell list. Not supported yet.+-- locateAllOnScreen :: FilePath -> IO [(Integer, Integer, Integer, Integer)]+-- locateAllOnScreen path = pyautogui "locateAllOnScreen" [arg $ 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)+locateCenterOnScreen :: FilePath -> IO (Maybe (Integer, Integer))+locateCenterOnScreen path =+  pyautogui "locateCenterOnScreen" [arg $ T.pack path] [] --- TODO pixelMatchesColor+pixelMatchesColor :: Integer -> Integer -> Color -> IO Bool+pixelMatchesColor x y color =+  pyautogui "pixelMatchesColor" [arg x, arg y, arg color] []++pixelMatchesColorWithTolerance :: Integer -> Integer -> Color -> Double -> IO Bool+pixelMatchesColorWithTolerance x y color tolerance =+  pyautogui "pixelMatchesColor" [arg x, arg y, arg color] [("tolerance", arg tolerance)]
+ test/Main.hs view
@@ -0,0 +1,37 @@+module Main where++import Test.Hspec++import AutoGUI+import CPython.Simple+import Control.Monad (forM_)+import Control.Monad.IO.Class (liftIO)++main :: IO ()+main = hspec $ do+  describe "basic" $ do+    it "runs" $ do+      initialize+      pause 1.0++    it "sample program" $ do+      putStrLn "3 seconds until beeping begins"+      sleep 3+      forM_ [1..100] $ \i -> do+        write "beep"+        press [key|enter|]+      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)+      -}