robot-1.4: Test/Robot.hs
-- | The main Robot interface.
module Test.Robot
(
-- * Running your robot
Robot() -- hide implementation
, runRobot
-- * Key and button constants
, module Test.Robot.Types
-- * Doing things
, Pressable(press, release, hold)
, moveBy
, moveTo
, tap
-- * Miscellaneous
, sleep
, module Test.Robot.Connection
) where
import Control.Applicative
import Control.Concurrent (threadDelay)
import Control.Monad.Catch
import Control.Monad.IO.Class
import Test.Robot.Connection
import Test.Robot.Internal
import Test.Robot.Types
infixr 4 `hold` -- Allow e.g. xs ++ ys `hold` m
-- | Represents things that can be pressed: either a single 'Switch' or
-- a list of 'Switch'es.
class Pressable x where
-- | Press a key or button.
press :: x -> Robot ()
-- | Release a key or button.
release :: x -> Robot ()
-- | @hold x act@ holds down @x@ while executing @act@. It is
-- equivalent to:
--
-- @
-- press x >> act >> release x
-- @
--
-- except @hold@ ensures that the argument is released in the event
-- of an exception.
--
hold :: x -> Robot a -> Robot a
hold = bracket_ <$> press <*> release
instance Pressable Switch where
press = switch True
release = switch False
-- | Press items from left-to-right, but release from right-to-left.
--
-- This behavior ensures the following equivalence holds:
--
-- @
-- press xs >> act >> release xs
-- === xs \`hold\` act
-- === x1 \`hold\` x2 \`hold\` ... xn \`hold\` act
-- @
--
instance Pressable x => Pressable [x] where
press = mapM_ press
release = mapM_ release . reverse
hold = foldr (.) id . map hold
--hold [] = id
--hold (x:xs) = hold x . hold xs
-- | Move the pointer by an offset.
moveBy :: Int -> Int -> Robot ()
moveBy = motion True
-- | Move the pointer to a point on the screen.
moveTo :: Int -> Int -> Robot ()
moveTo = motion False
-- | Press the argument, then release it.
--
-- Note that the underlying events are fired very quickly; much faster
-- than some applications (such as Xmonad) can handle. If this becomes
-- an issue, you may introduce a delay using 'sleep':
--
-- @
-- slowTap x = x \`hold\` sleep 0.1
-- @
--
tap :: Pressable x => x -> Robot ()
tap = (`hold` return ())
-- | Do nothing for the specified number of seconds.
sleep :: Rational -> Robot ()
sleep = liftIO . threadDelay . round . (* 1000000)