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