{-# LANGUAGE ForeignFunctionInterface #-} module Graphics.X11.XTest (queryXTestSupport, fakeMotion, fakeButtonPress, movePointer, withGrabbedControl, sendKey) where import Control.Monad import Graphics.X11.Xlib import Foreign import Foreign.C.Types -- FFI imports -- XTestFakeButtonEvent(display, button, is_press, delay) foreign import ccall unsafe "X11/extensions/XTest.h XTestFakeButtonEvent" xFakeButtonEvent :: Display -> Button -> Bool -> Time -> IO Status foreign import ccall unsafe "X11/extensions/XTest.h XTestFakeMotionEvent" xFakeMotionEvent :: Display -> CInt -> CInt -> CInt -> Time -> IO Status foreign import ccall unsafe "X11/extensions/XTest.h XTestGrabControl" xGrabControl :: Display -> Bool -> IO Status foreign import ccall unsafe "X11/extensions/XTest.h XTestFakeKeyEvent" xFakeKeyEvent :: Display -> KeyCode -> Bool -> CULong -> IO Status foreign import ccall unsafe "X11/extensions/XTest.h XTestQueryExtension" xQueryExtension :: Display -> Ptr CInt -> Ptr CInt -> Ptr CInt -> Ptr CInt -> IO Bool -- | Ask the X server if XTest extension is supported. -- Returns Nothing, if extension is not supported. -- Otherwise, it returns: -- -- * Event number for the first event for this extension (undefined for current version of XTest). -- -- * Error number for the first error for this extension (undefined for current version of XTest). -- -- * Major and -- -- * minor versions of the extension. -- queryXTestSupport :: Display -> IO (Maybe (Int, Int, Int, Int)) queryXTestSupport dpy = do alloca $ \pevent -> alloca $ \perror -> alloca $ \pmajor -> alloca $ \pminor -> do b <- xQueryExtension dpy pevent perror pmajor pminor if b then do event <- peek pevent error <- peek perror major <- peek pmajor minor <- peek pminor return $ Just (fromIntegral event, fromIntegral error, fromIntegral major, fromIntegral minor) else return Nothing -- | Perform some IO actions while control grabbed by XTest withGrabbedControl :: Display -> IO a -> IO a withGrabbedControl dpy action = do st <- xGrabControl dpy True if st /= 0 -- Grabbed successfully then do result <- action xGrabControl dpy False return result else fail $ "XTest cannot grab control" -- | Send fake key press sendKey :: Display -> [KeySym] -- ^ Modifier keys (say, xK_Control_L). Set to [] if modifier is not needed. -> KeySym -- ^ Key to press (say, xK_n). -> IO () sendKey dpy mods keysym = do keycode <- keysymToKeycode dpy keysym when (keycode /= 0) $ withGrabbedControl dpy $ do -- Press mods forM_ mods $ \modsym -> do code <- keysymToKeycode dpy modsym xFakeKeyEvent dpy code True 0 -- Press and release key xFakeKeyEvent dpy keycode True 0 xFakeKeyEvent dpy keycode False 0 -- Release mods forM_ (reverse mods) $ \modsym -> do code <- keysymToKeycode dpy modsym xFakeKeyEvent dpy code False 0 sync dpy False return () -- | Create fake pointer motion event. fakeMotion :: Display -- -> ScreenNumber -- -> Int -- ^ X -> Int -- ^ Y -> IO () fakeMotion dpy sid x y = do xFakeMotionEvent dpy (fromIntegral sid) (fromIntegral x) (fromIntegral y) 0 return () -- | Create fake mouse button click event. fakeButtonPress :: Display -> Button -- ^ Mouse button number -> IO () fakeButtonPress dpy button = do xFakeButtonEvent dpy button True 0 xFakeButtonEvent dpy button False 0 return () -- | Move mouse pointer. movePointer :: Display -> ScreenNumber -> XID -- ^ Root window XID -> Int -- ^ delta X -> Int -- ^ delta Y -> IO () movePointer dpy sid root dx dy = do (_,_,_,x,y,_,_,_) <- queryPointer dpy root fakeMotion dpy sid (fromIntegral x + dx) (fromIntegral y + dy)