wild-bind-x11-0.2.0.15: test/WildBind/X11Spec.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module WildBind.X11Spec
( main
, spec
) where
import Control.Applicative ((<$>))
import Control.Concurrent.Async (async, waitCatch)
import Control.Exception (Exception (fromException), finally, throwIO)
import Control.Monad (forM_)
import Control.Monad.IO.Class (liftIO)
import qualified Control.Monad.Trans.State as State
import Data.IORef (IORef, modifyIORef, newIORef, readIORef)
import Data.List (intercalate, reverse)
import Data.Monoid ((<>))
import Data.Text (unpack)
import Data.Time.Clock (diffUTCTime, getCurrentTime)
import qualified Graphics.X11.Xlib as Xlib
import System.IO (hPutStrLn, stderr)
import Test.Hspec
import WildBind (ActionDescription, Describable,
FrontEnd (frontNextEvent, frontSetGrab, frontUnsetGrab),
FrontEvent (FEChange, FEInput), Option (..), as, binds',
defOption, justBefore, on, revise, run, startFrom,
whenBack, wildBind')
import qualified WildBind.Description as WBD
import qualified WildBind.Input.NumPad as NumPad
import WildBind.X11 (ActiveWindow, KeyEventType (..), XKeyEvent (..),
XKeyInput, XMod (..), alt, ctrl, press, release, shift,
super, withFrontEnd)
import qualified WildBind.X11.KeySym as WKS
import WildBind.X11.TestUtil (checkIfX11Available, withGrabs)
newtype MyException
= MyException String
deriving (Eq, Ord, Show)
instance Exception MyException
main :: IO ()
main = hspec spec
maybeRun :: Expectation -> Expectation
#ifdef TEST_INTERACTIVE
maybeRun = id
#else
maybeRun _ = pendingWith ("You need to set test-interactive flag to run the test.")
#endif
p :: String -> IO ()
p = hPutStrLn stderr . ("--- " ++)
withFrontEndForTest :: (XKeyInput i, Describable i, Ord i) => (FrontEnd ActiveWindow i -> IO a) -> IO a
withFrontEndForTest action = withFrontEnd $ \front -> do
_ <- frontNextEvent front -- discard the first FEChange event.
action front
grabExp :: (Bounded i, Enum i, Show i, Eq i)
=> FrontEnd ActiveWindow i -> i -> Expectation
grabExp front grab_input = grabExpMain `finally` releaseAll where
grabExpMain = do
frontSetGrab front grab_input
p ("Press some numpad keys (grab="++ show grab_input ++")..")
ev <- frontNextEvent front
p ("Got event: " ++ show ev)
ev `shouldBe` FEInput grab_input
releaseAll = mapM_ (frontUnsetGrab front) (enumFromTo minBound maxBound)
grabCase :: (Bounded i, Enum i, Show i, Eq i) => FrontEnd ActiveWindow i -> Expectation
grabCase front = mapM_ (grabExp front) (enumFromTo minBound maxBound)
stopWatchMsec :: IO a -> IO (a, Int)
stopWatchMsec act = do
start <- getCurrentTime
ret <- act
end <- getCurrentTime
return (ret, floor ((diffUTCTime end start) * 1000))
describeStr :: Describable a => a -> String
describeStr = unpack . WBD.describe
unshiftNewBinding :: Eq i => IORef [[(i,ActionDescription)]] -> [(i,ActionDescription)] -> IO ()
unshiftNewBinding ref got = do
cur <- readIORef ref
case cur of
[] -> update
(latest : _) -> if latest /= got
then update
else return ()
where
update = modifyIORef ref (got :)
spec :: Spec
spec = checkIfX11Available $ do
describe "X11Front" $ do
it "should first emit FEChange event when initialized" $ withFrontEnd $ \f -> do
p "try to get the first event..."
(ev, time) <- stopWatchMsec $ frontNextEvent f :: IO (FrontEvent ActiveWindow NumPad.NumPadUnlocked, Int)
time `shouldSatisfy` (< 500)
case ev of
FEChange _ -> return ()
_ -> expectationFailure ("FEChange is expected, but got " ++ show ev)
it "should NOT throw exception when it tries to double-grab in the same process" $ withFrontEnd $ \f1 ->
withFrontEnd $ \f2 -> do
frontSetGrab f1 NumPad.NumLeft `shouldReturn` ()
frontSetGrab f2 NumPad.NumLeft `shouldReturn` ()
it "should control key grab based on ('release' || 'press')" $ maybeRun $ withFrontEnd $ \f -> do
got_binds_rev <- newIORef []
let opt = defOption { optBindingHook = unshiftNewBinding got_binds_rev,
optCatch = (\_ _ e -> throwIO e)
}
b_base = binds' $ do
on (press $ ctrl $ WKS.xK_g) `as` "P(C-g)" `run` liftIO (throwIO $ MyException "NG")
on (release $ alt $ ctrl $ WKS.xK_x) `as` "R(M-C-x)" `run` liftIO (throwIO $ MyException "OK")
b_0 = whenBack (== 0) $ binds' $ do
on (press $ ctrl $ WKS.xK_i) `as` "P(C-i)" `run` State.put 1
b_1 = whenBack (== 1) $ binds' $ do
on (press $ ctrl $ WKS.xK_i) `as` "P(C-i)" `run` State.put 0
on (press $ alt $ ctrl $ WKS.xK_x) `as` "P(M-C-x)" `run` return ()
rev _ _ i = justBefore $ p ("Input: " ++ (unpack $ WBD.describe i))
b = revise rev $ startFrom (0 :: Int) $ b_base <> b_0 <> b_1
exp_descs = [ ["P(C-g)", "R(M-C-x)", "P(C-i)"],
["P(C-g)", "R(M-C-x)", "P(C-i)", "P(M-C-x)"],
["P(C-g)", "R(M-C-x)", "P(C-i)"]
]
p "Input C-i C-i M-C-x. If wildBind is still blocked, then type C-g."
result <- waitCatch =<< async (wildBind' opt b f)
got_descs <- fmap ((map . map) snd . reverse) $ readIORef got_binds_rev
length got_descs `shouldBe` length exp_descs
forM_ (zip got_descs exp_descs) $ uncurry shouldMatchList
case result of
Right _ -> expectationFailure "expects an exception, but nothing happened."
Left e -> fromException e `shouldBe` (Just $ MyException "OK")
describe "X11Front - NumPadUnlocked" $ do
it "should grab/ungrab keys" $ maybeRun $ withFrontEndForTest $ \f -> do
let grabCase' :: FrontEnd ActiveWindow NumPad.NumPadUnlocked -> Expectation
grabCase' = grabCase
grabCase' f
describe "X11Front - NumPadLocked" $ do
it "should grab/ungrab keys" $ maybeRun $ withFrontEndForTest $ \f -> do
let grabCase' :: FrontEnd ActiveWindow NumPad.NumPadLocked -> Expectation
grabCase' = grabCase
grabCase' f
describe "X11Front - normal modified keys (pressed)" $ do
it "should distinguish modifiers" $ maybeRun $ withFrontEndForTest $ \f -> do
let inputs = [ ctrl Xlib.xK_i,
ctrl $ alt Xlib.xK_i,
super Xlib.xK_i,
shift $ super Xlib.xK_I
]
withGrabs f inputs $ do
p ("Grabbed " ++ (intercalate ", " $ map describeStr inputs))
forM_ inputs $ \input -> do
p ("Push " ++ describeStr input)
press_ev <- frontNextEvent f
p ("Got event: " ++ show press_ev)
press_ev `shouldBe` FEInput input
release_ev <- frontNextEvent f
p ("Got event: " ++ show release_ev)
release_ev `shouldBe` (FEInput $ input { xKeyEventType = KeyRelease })
describe "X11Front - Either" $ do
it "should combine input types" $ maybeRun $ withFrontEndForTest $ \f -> do
let inputs = [Left NumPad.NumLPeriod, Right NumPad.NumDelete]
withGrabs f inputs $ do
forM_ inputs $ \input -> do
p ("Push " ++ show input)
ev <- frontNextEvent f
ev `shouldBe` FEInput input