fake-type-0.2.0.0: lib/FakeType.hs
{-# LANGUAGE
ForeignFunctionInterface,
NondecreasingIndentation,
RecordWildCards,
NoImplicitPrelude
#-}
module FakeType
(
sendString,
sendStringWithDelay,
)
where
import BasePrelude
import Numeric
import Foreign
import Foreign.C.Types
import Graphics.X11
import Data.List.Split (chunksOf)
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
charSym :: Char -> KeySym
charSym char = stringToKeysym ('U' : showHex (fromEnum char) "")
-- Find unused keys (so that we'd be able to simulate a keypress without
-- having to use any key modifiers). An important thing is that the keys have
-- to be completely unused – keys -that have Alt or Super assigned to them-
-- will behave weirdly if you try to use them (even tho they don't have any
-- symbols in default positions).
findFreeKeys :: Mapping -> IO [KeyCode]
findFreeKeys mapping@Mapping{..} = do
let isEmptyKey key = do
-- Get a pointer to the part of symArray that corresponds to the key.
let keyPtr = advancePtr symArray (symIndex key 0 mapping)
-- Get the symbols (there's symPerKey of them).
syms <- peekArray (fromIntegral symPerKey) keyPtr
-- Check that they all are noSymbol.
return (all (== noSymbol) syms)
filterM isEmptyKey (take (fromIntegral keyCount) [minKey..])
sendString :: String -> IO ()
sendString = sendStringWithDelay 20 8 20
sendStringWithDelay
:: Int -- ^ Delay after changing the layout (in ms)
-> Int -- ^ Delay after pressing\/releasing a key
-> Int -- ^ Delay after entering a batch of symbols
-> String
-> IO ()
sendStringWithDelay mappingDelay pressDelay batchDelay string =
bracket (openDisplay ":0.0") closeDisplay $ \display -> do
mapping <- getKeyboardMapping display Nothing
freeKeys <- findFreeKeys mapping
when (null freeKeys) $
error "sendStringWithDelay: couldn't find a free key"
let syncAndFlush = sync display False >> flush display
-- This function assigns given symbols to freeKeys.
let assignSyms syms = do
for_ (zip syms freeKeys) $ \(sym, key) -> do
let keyPtr = advancePtr (symArray mapping) (symIndex key 0 mapping)
pokeArray keyPtr (replicate (fromIntegral (symPerKey mapping)) sym)
changeKeyboardMapping display Nothing mapping
syncAndFlush
threadDelay (mappingDelay * 1000)
-- To enter a chunk of characters, we first assign all characters to free
-- keys and then press those keys; it's done like this because
-- changeKeyboardMapping is pretty slow and doing it for *each* key
-- results in glitches when typing.
let typeChunk chunk = do
let len = length chunk
assignSyms (map charSym chunk)
for_ (take len freeKeys) $ \key -> do
xFakeKeyEvent display key True 0
syncAndFlush
threadDelay (pressDelay * 1000)
xFakeKeyEvent display key False 0
syncAndFlush
threadDelay (pressDelay * 1000)
threadDelay (batchDelay * 1000)
-- Okay, let's enter all chunks. We also have to make the keys free again
-- after entering the chunks.
let chunks = chunksOf (length freeKeys) string
mapM_ typeChunk chunks `finally`
assignSyms (repeat noSymbol)