packages feed

fake-type-0.1.0.0: lib/FakeType.hs

{-# LANGUAGE
ForeignFunctionInterface,
NondecreasingIndentation,
RecordWildCards,
NoImplicitPrelude
  #-}


module FakeType
(
  sendString,
)
where


import BasePrelude
import Numeric
import Foreign
import Foreign.C.Types
import Graphics.X11


foreign import ccall unsafe "HsXlib.h XGetKeyboardMapping"
  xGetKeyboardMapping
    :: Display
    -> KeyCode                         -- First keycode
    -> CInt                            -- Amount of keycodes
    -> Ptr CInt                        -- Keysyms per keycode
    -> IO (Ptr KeySym)                 -- Keysyms

foreign import ccall unsafe "HsXlib.h XChangeKeyboardMapping"
  xChangeKeyboardMapping
    :: Display
    -> KeyCode                         -- First keycode
    -> CInt                            -- Keysyms per keycode
    -> Ptr KeySym                      -- Array of keysyms
    -> CInt                            -- Amount of keycodes
    -> IO ()

foreign import ccall unsafe "X11/extensions/XTest.h XTestFakeKeyEvent"
  xFakeKeyEvent :: Display -> KeyCode -> Bool -> CULong -> IO Status

data Mapping = Mapping {
  minKey    :: KeyCode,
  keyCount  :: CInt,
  symArray  :: Ptr KeySym,
  symPerKey :: CInt }

getKeyboardMapping
  :: Display
  -> Maybe (KeyCode, CInt)  -- ^ First key + amount of keys,
                            --   or 'Nothing' if it's “all”
  -> IO Mapping
getKeyboardMapping display mb = do
  let (minKey, keyCount) = case mb of
        Just x  -> x
        Nothing -> let (a, b) = displayKeycodes display
                   in  (fromIntegral a, b-a+1)
  alloca $ \symPerKey_return -> do
  symArray <- xGetKeyboardMapping
                display
                minKey
                keyCount
                symPerKey_return
  symPerKey <- peek symPerKey_return
  return Mapping{..}

symIndex :: KeyCode -> Int -> Mapping -> Int
symIndex key pos Mapping{..} =
  fromIntegral (key-minKey) * fromIntegral symPerKey + pos

changeKeyboardMapping
  :: Display
  -> Maybe (KeyCode, CInt)  -- ^ First key + amount of keys,
                            --   or 'Nothing' if it's “all”
  -> Mapping
  -> IO ()
changeKeyboardMapping display mb mapping@Mapping{..} =
  case mb of
    Nothing ->
      xChangeKeyboardMapping
        display
        minKey
        symPerKey
        symArray
        keyCount
    Just (key, amount) ->
      xChangeKeyboardMapping
        display
        key
        symPerKey
        (advancePtr symArray (symIndex key 0 mapping))
        amount

getSymbol
  :: KeyCode     -- ^ Key
  -> Int         -- ^ Position (usually 0..3)
  -> Mapping
  -> IO KeySym
getSymbol key pos mapping =
  peekElemOff (symArray mapping) (symIndex key pos mapping)

changeSymbol
  :: Display
  -> KeyCode     -- ^ Key
  -> Int         -- ^ Position (usually 0..3)
  -> KeySym      -- ^ Symbol
  -> Mapping
  -> IO ()
changeSymbol display key pos sym mapping = do
  pokeElemOff (symArray mapping) (symIndex key pos mapping) sym
  changeKeyboardMapping display (Just (key, 1)) mapping
  flush display

findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
findM _ [] = return Nothing
findM p (x:xs) = do
  r <- p x
  if r then return (Just x) else findM p xs

-- Find a key that has an empty default position (so that we'd be able to
-- simulate a keypress without having to use any key modifiers). If there's
-- no empty key, throw an exception.
findFreeKey :: Mapping -> IO KeyCode
findFreeKey mapping@Mapping{..} = do
  let isEmptyKey key = (== noSymbol) <$> getSymbol key 0 mapping
  mbFreeKey <- findM isEmptyKey (take (fromIntegral keyCount) [minKey..])
  case mbFreeKey of
    Nothing -> error "findFreeKey: couldn't find a free key"
    Just k  -> return k

sendString :: String -> IO ()
sendString string = do
  display <- openDisplay ":0.0"
  mapping <- getKeyboardMapping display Nothing
  freeKey <- findFreeKey mapping
  sync display False
  forM_ string $ \char -> do
    let sym = stringToKeysym ('U' : showHex (fromEnum char) "")
    changeSymbol display freeKey 0 sym mapping
    xFakeKeyEvent display freeKey True 0
    xFakeKeyEvent display freeKey False 0
    sync display False
    threadDelay 12000
  changeSymbol display freeKey 0 noSymbol mapping
  closeDisplay display