packages feed

wild-bind-x11-0.2.0.15: test/WildBind/X11/EmulateSpec.hs

module WildBind.X11.EmulateSpec
    ( main
    , spec
    ) where

import           Control.Exception            (bracket)
import           Control.Monad                (forM_)
import           Control.Monad.Trans.Maybe    (runMaybeT)
import           Data.Bits                    ((.|.))
import           Data.Text                    (unpack)
import qualified Graphics.X11.Xlib            as Xlib
import           Test.Hspec

import           WildBind                     (FrontEvent (..), frontNextEvent)
import qualified WildBind.Description         as WBD
import           WildBind.X11                 (KeyEventType (..), XKeyEvent (..), XMod (..), alt,
                                               ctrl, defaultRootWindow, makeFrontEnd, press,
                                               release, shift, super, withX11Front)
import           WildBind.X11.Emulate         (sendKeyTo)
import           WildBind.X11.Internal.Key    (KeyMaskMap, getKeyMaskMap, xKeyEventToXKeyInput)
import           WildBind.X11.Internal.Window (fromWinID)

import           WildBind.X11.TestUtil        (checkIfX11Available)

main :: IO ()
main = hspec spec

spec :: Spec
spec = checkIfX11Available $ describe "sendKeyTo" $ do
  let inputs = [ alt $ super Xlib.xK_r,
                 release Xlib.xK_w,
                 press Xlib.xK_Right,
                 release $ ctrl $ shift Xlib.xK_F12,
                 ctrl $ alt Xlib.xK_3

                 -- ctrl $ shift Xlib.xK_bracketleft

                 ---- "Shift" modifier is tricky, because it affects
                 ---- the keysym depending on the keyboard and key map
                 ---- setting. E.g., with a typical Japanese keyboard,
                 ---- bracketleft "[" and braceleft "{" share the same
                 ---- key, and we use Shift to input the braceleft
                 ---- "{". So, if you use Shift modifier, you have to
                 ---- use xK_braceleft keysym.
               ]
  forM_ inputs $ \input -> specify (unpack $ WBD.describe input) $ withX11Front $ \x11 -> do
    bracket (Xlib.openDisplay "") Xlib.closeDisplay $ \disp -> do
      kmmap <- getKeyMaskMap disp
      win <- makeWindow disp
      -- putStrLn ("Window created: " ++ show win)
      Xlib.sync disp False
      -- putStrLn ("Do send input: " ++ show input)
      sendKeyTo x11 (fromWinID win) input
      -- putStrLn ("Receiving..")
      (nextKey kmmap disp) `shouldReturn` input

-- We have to create a dedicated window to receive events sent by
-- 'sendKeyTo', because XSendEvent ignores key grabs (so X11Front
-- cannot get the sent event.)
--
-- By the way, key events by XTEST extension emulate the real key
-- events from a real keyboard, so they are caught by key grabs.
--
-- c.f. http://t-sato.in.coocan.jp/xvkbd/events.html

makeWindow :: Xlib.Display -> IO Xlib.Window
makeWindow disp = do
  win <- Xlib.createSimpleWindow disp root x y w h border_width border_pixel bg_pixel
  -- Xlib.storeName disp win "test window"
  -- Xlib.mapWindow disp win
  Xlib.selectInput disp win select_mask
  Xlib.flush disp
  return win
  where
    root = Xlib.defaultRootWindow disp
    x = 0
    y = 0
    w = 50
    h = 50
    border_width = 1
    border_pixel = Xlib.blackPixel disp 0
    bg_pixel = Xlib.whitePixel disp 0
    select_mask = Xlib.keyPressMask .|. Xlib.keyReleaseMask

nextKey :: KeyMaskMap -> Xlib.Display -> IO XKeyEvent
nextKey kmmap disp = Xlib.allocaXEvent $ \xev -> do
  Xlib.nextEvent disp xev
  xtype <- Xlib.get_EventType xev
  -- putStrLn ("Got event type: " ++ show xtype)
  case toKeyType xtype of
   Nothing -> error ("Unknown event type: " ++ show xtype)
   Just key_type -> do
     -- putStrLn ("KeyEventType = " ++ show key_type)
     ret <- fmap unwrapMaybe $ runMaybeT $ xKeyEventToXKeyInput kmmap key_type $ Xlib.asKeyEvent xev
     -- putStrLn ("Converted: " ++ show ret)
     return ret
  where
    toKeyType xtype | xtype == Xlib.keyPress = Just KeyPress
                    | xtype == Xlib.keyRelease = Just KeyRelease
                    | otherwise = Nothing
    error_convert = error "Cannot convert the XEvent to XKeyEvent."
    unwrapMaybe = maybe error_convert id