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 +30/−0
- README.md +120/−0
- Setup.hs +2/−0
- app/Main.hs +28/−0
- changelog.md +5/−0
- hsautogui.cabal +71/−0
- src/AutoGUI.hs +61/−0
- src/AutoGUI/Call.hs +41/−0
- src/AutoGUI/Debug.hs +43/−0
- src/AutoGUI/Info.hs +46/−0
- src/AutoGUI/Keyboard.hs +51/−0
- src/AutoGUI/Keys.hs +251/−0
- src/AutoGUI/MessageBoxes.hs +57/−0
- src/AutoGUI/Mouse.hs +110/−0
- src/AutoGUI/Run.hs +28/−0
- src/AutoGUI/Screen.hs +64/−0
+ 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