evdev-0.1.0.0: src/Evdev.hs
module Evdev where
import Control.Exception
import Data.Int
import Data.List.Extra
import Data.Either.Combinators
import Data.Time.Clock
import Data.Tuple.Extra
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS
import Data.Set (Set)
import Foreign ((.|.))
import Foreign.C (CUInt)
import Foreign.C.Error (Errno(Errno),errnoToIOError)
import System.Posix.ByteString (RawFilePath)
import qualified Evdev.LowLevel as LL
import Evdev.Codes --TODO combine modules?
-- stores path that was originally used, as it seems impossible to recover this later
-- We don't allow the user to access the underlying low-level C device.
data Device = Device { cDevice :: LL.Device, devicePath :: RawFilePath }
instance Show Device where
show = show . devicePath
data Event = Event {
evType :: EventType,
evCode :: EventCode,
evValue :: EventValue,
evTime :: DiffTime}
deriving (Eq, Ord, Show)
-- aligns with the pattern synonyms below
prettyEvent :: Event -> String
prettyEvent x = showTime (evTime x) ++ ":" ++ " " ++ case x of
SyncEvent t -> show t
KeyEvent k t -> intercalate " " [show k, show t]
RelativeEvent c v -> intercalate " " [show c, showE v]
AbsoluteEvent c v -> intercalate " " [show c, showE v]
MiscEvent c v -> intercalate " " [show c, showE v]
SwitchEvent c v -> intercalate " " [show c, showE v]
LEDEvent c v -> intercalate " " [show c, showE v]
SoundEvent c v -> intercalate " " [show c, showE v]
RepeatEvent c v -> intercalate " " [show c, showE v]
ForceFeedbackEvent c v -> intercalate " " [showE c, showE v]
PowerEvent c v -> intercalate " " [showE c, showE v]
ForceFeedbackStatusEvent c v -> intercalate " " [showE c, showE v]
_ -> error $ "show: unrecognised Event: " ++ intercalate " "
[showE $ evType x, showE $ evCode x, showE $ evValue x]
where
showE :: Enum e => e -> String
showE = show . fromEnum
showTime t = -- fix time string to always have same length after '.', by adding 0s
let (n,r) = second tail $ span (/= '.') $ init $ show t
in n ++ "." ++ take 6 (r ++ ['0'..]) ++ "s"
pattern SyncEvent :: SyncEventType -> Event
pattern SyncEvent c <- Event EvSyn (toEnum . fromEnum -> c) _ _
pattern KeyEvent :: Key -> KeyEventType -> Event
pattern KeyEvent c v <- Event EvKey (toEnum . fromEnum -> c) (toEnum . fromEnum -> v) _
pattern RelativeEvent :: RelativeAxis -> EventValue -> Event
pattern RelativeEvent c v <- Event EvRel (toEnum . fromEnum -> c) v _
pattern AbsoluteEvent :: AbsoluteAxis -> EventValue -> Event
pattern AbsoluteEvent c v <- Event EvAbs (toEnum . fromEnum -> c) v _
pattern MiscEvent :: MiscEventType -> EventValue -> Event
pattern MiscEvent c v <- Event EvMsc (toEnum . fromEnum -> c) v _
pattern SwitchEvent :: SwitchEventType -> EventValue -> Event
pattern SwitchEvent c v <- Event EvSw (toEnum . fromEnum -> c) v _
pattern LEDEvent :: LEDEventType -> EventValue -> Event
pattern LEDEvent c v <- Event EvLed (toEnum . fromEnum -> c) v _
pattern SoundEvent :: SoundEventType -> EventValue -> Event
pattern SoundEvent c v <- Event EvSnd (toEnum . fromEnum -> c) v _
pattern RepeatEvent :: RepeatEventType -> EventValue -> Event
pattern RepeatEvent c v <- Event EvRep (toEnum . fromEnum -> c) v _
pattern ForceFeedbackEvent :: EventCode -> EventValue -> Event
pattern ForceFeedbackEvent c v <- Event EvFf c v _
pattern PowerEvent :: EventCode -> EventValue -> Event
pattern PowerEvent c v <- Event EvPwr c v _
pattern ForceFeedbackStatusEvent :: EventCode -> EventValue -> Event
pattern ForceFeedbackStatusEvent c v <- Event EvFfStatus c v _
newtype EventCode = EventCode Int16 deriving (Enum, Eq, Ord, Read, Show)
newtype EventValue = EventValue Int32 deriving (Enum, Eq, Ord, Read, Show)
data KeyEventType
= Released
| Pressed
| Repeated
deriving (Enum, Eq, Ord, Read, Show)
convertFlags :: Set LL.ReadFlags -> CUInt
convertFlags = fromIntegral . foldr ((.|.) . fromEnum) 0
defaultReadFlags :: Set LL.ReadFlags
defaultReadFlags = [LL.Normal,LL.Blocking]
grabDevice :: Device -> IO ()
grabDevice = grabDevice' LL.LibevdevGrab
ungrabDevice :: Device -> IO ()
ungrabDevice = grabDevice' LL.LibevdevUngrab
nextEvent :: Device -> Set LL.ReadFlags -> IO Event
nextEvent dev flags = do
(t,c,v,time) <- LL.convertEvent =<<
throwCErrors "nextEvent" (devicePath dev) (LL.nextEvent (cDevice dev) (convertFlags flags))
return $ Event (toEnum t) (EventCode c) (EventValue v) time
newDevice :: RawFilePath -> IO Device
newDevice path = do
dev <- throwCErrors "newDevice" path $ LL.newDevice path
return $ Device dev path
maybeNewDevice :: RawFilePath -> IO (Maybe Device)
maybeNewDevice = fmap rightToMaybe . tryIO . newDevice
evdevDir :: RawFilePath
evdevDir = "/dev/input"
getDeviceName :: Device -> IO ByteString
getDeviceName = fmap BS.pack . LL.deviceName . cDevice
tryIO :: IO a -> IO (Either IOException a)
tryIO = try
{- Util -}
-- run the action, throwing an error if the C errno is not 0
throwCErrors :: String -> RawFilePath -> IO (Errno, a) -> IO a
throwCErrors loc path x = do
(errno, res) <- x
case errno of
Errno 0 -> return res
_ -> ioError $ errnoToIOError loc errno Nothing (Just $ BS.unpack path)
grabDevice' :: LL.GrabMode -> Device -> IO ()
grabDevice' mode dev = throwCErrors "grabDevice" (devicePath dev) $ LL.grabDevice (cDevice dev) mode