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 +50/−50
- app/Main.hs +0/−28
- changelog.md +4/−0
- hsautogui.cabal +9/−10
- src/AutoGUI.hs +3/−5
- src/AutoGUI/Call.hs +7/−38
- src/AutoGUI/Debug.hs +6/−20
- src/AutoGUI/Discard.hs +38/−0
- src/AutoGUI/Info.hs +8/−28
- src/AutoGUI/Keyboard.hs +19/−18
- src/AutoGUI/Keys.hs +5/−0
- src/AutoGUI/MessageBoxes.hs +13/−28
- src/AutoGUI/Mouse.hs +72/−48
- src/AutoGUI/Run.hs +0/−28
- src/AutoGUI/Screen.hs +21/−44
- test/Main.hs +37/−0
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)+ -}