packages feed

evdev 0.2.0.1 → 1.0.0.0

raw patch · 5 files changed

+85/−64 lines, 5 filesdep +monad-loopsdep −compositionPVP ok

version bump matches the API change (PVP)

Dependencies added: monad-loops

Dependencies removed: composition

API changes (from Hackage documentation)

- Evdev: getDeviceName :: Device -> IO ByteString
- Evdev: maybeNewDevice :: RawFilePath -> IO (Maybe Device)
- Evdev.LowLevel: Blocking :: ReadFlags
- Evdev.LowLevel: Device :: Ptr Device -> Device
- Evdev.LowLevel: Event :: ForeignPtr Event -> Event
- Evdev.LowLevel: ForceSync :: ReadFlags
- Evdev.LowLevel: LibevdevGrab :: GrabMode
- Evdev.LowLevel: LibevdevUngrab :: GrabMode
- Evdev.LowLevel: Normal :: ReadFlags
- Evdev.LowLevel: Sync :: ReadFlags
- Evdev.LowLevel: Time :: ForeignPtr Time -> Time
- Evdev.LowLevel: convertEnum :: (Enum a, Integral b) => a -> b
- Evdev.LowLevel: convertEvent :: Event -> IO (Int, Int16, Int32, DiffTime)
- Evdev.LowLevel: data GrabMode
- Evdev.LowLevel: data ReadFlags
- Evdev.LowLevel: deviceFd :: Device -> IO Fd
- Evdev.LowLevel: deviceFd'_ :: Device -> IO CInt
- Evdev.LowLevel: deviceName :: Device -> IO String
- Evdev.LowLevel: deviceName'_ :: Device -> IO (Ptr CChar)
- Evdev.LowLevel: grabDevice :: Device -> GrabMode -> IO (Errno, ())
- Evdev.LowLevel: instance GHC.Classes.Eq Evdev.LowLevel.ReadFlags
- Evdev.LowLevel: instance GHC.Classes.Ord Evdev.LowLevel.ReadFlags
- Evdev.LowLevel: instance GHC.Enum.Enum Evdev.LowLevel.GrabMode
- Evdev.LowLevel: instance GHC.Enum.Enum Evdev.LowLevel.ReadFlags
- Evdev.LowLevel: instance GHC.Show.Show Evdev.LowLevel.GrabMode
- Evdev.LowLevel: instance GHC.Show.Show Evdev.LowLevel.ReadFlags
- Evdev.LowLevel: libevdev_grab :: Device -> GrabMode -> IO CInt
- Evdev.LowLevel: libevdev_grab'_ :: Device -> CInt -> IO CInt
- Evdev.LowLevel: libevdev_new :: IO Device
- Evdev.LowLevel: libevdev_next_event :: Device -> CUInt -> Ptr Event -> IO CInt
- Evdev.LowLevel: libevdev_set_fd :: Device -> CInt -> IO CInt
- Evdev.LowLevel: newDevice :: RawFilePath -> IO (Errno, Device)
- Evdev.LowLevel: newtype Device
- Evdev.LowLevel: newtype Event
- Evdev.LowLevel: newtype Time
- Evdev.LowLevel: nextEvent :: Device -> CUInt -> IO (Errno, Event)
- Evdev.LowLevel: withEvent :: Event -> (Ptr Event -> IO b) -> IO b
- Evdev.LowLevel: withTime :: Time -> (Ptr Time -> IO b) -> IO b
- Evdev.Stream: allDevicePaths :: (IsStream t, Monad (t IO)) => t IO RawFilePath
- Evdev.Stream: filteredEvents :: (IsStream t, Monad (t IO)) => (Device -> Bool) -> t IO (Device, Event)
+ Evdev: deviceFd :: Device -> IO Fd
+ Evdev: deviceName :: Device -> IO ByteString
+ Evdev: devicePath :: Device -> RawFilePath
+ Evdev: deviceProperties :: Device -> IO [DeviceProperty]
+ Evdev.Stream: allDevices :: (IsStream t, Monad (t IO)) => t IO Device
- Evdev.Stream: allEvents :: (IsStream t, Monad (t IO)) => t IO (Device, Event)
+ Evdev.Stream: allEvents :: IsStream t => t IO (Device, Event)
- Evdev.Stream: makeDevices :: (IsStream t, Functor (t IO)) => t IO RawFilePath -> t IO Device
+ Evdev.Stream: makeDevices :: IsStream t => t IO RawFilePath -> t IO Device
- Evdev.Stream: readEvents :: IsStream t => Device -> t IO Event
+ Evdev.Stream: readEvents :: Device -> SerialT IO Event

Files

CHANGELOG.md view
@@ -3,3 +3,7 @@ ## 0.1.0.0 -- 2019-07-20  * First version. Released on an unsuspecting world.++## 1.0.0.0 -- 2019-12-15++* First stable release.
evdev.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                evdev-version:             0.2.0.1+version:             1.0.0.0 author:              George Thomas maintainer:          George Thomas description:         Provides access to the Linux event device interface, with an optional high-level Streamly-based API.@@ -24,7 +24,6 @@   ghc-options:         -Wall   build-depends:       base                 ^>= 4.12.0 || ^>= 4.13,                        bytestring           ^>= 0.10,-                       composition          ^>= 1.0,                        containers           ^>= 0.6.2,                        either               ^>= 5.0.1,                        time                 ^>= 1.9.3,@@ -36,6 +35,7 @@                        rawfilepath          ^>= 0.2.4,                        streamly             ^>= 0.6.1 || ^>= 0.7,                        safe                 ^>= 0.3.18,+                       monad-loops          ^>= 0.4.3,   build-tool-depends:  c2hs:c2hs   default-language:    Haskell2010   default-extensions:  FlexibleContexts@@ -48,4 +48,5 @@                        ViewPatterns   includes:            libevdev-1.0/libevdev/libevdev.h                        linux/input.h+                       errno.h   extra-libraries:     evdev
src/Evdev.hs view
@@ -17,10 +17,12 @@     ungrabDevice,     nextEvent,     newDevice,-    maybeNewDevice,     evdevDir,-    getDeviceName,-    Device (devicePath),+    deviceName,+    deviceFd,+    devicePath,+    deviceProperties,+    Device,     Event,     EventCode(..),     EventValue(..),@@ -28,20 +30,20 @@     ReadFlags (..), ) where -import Control.Exception-import Data.Int-import Data.Either.Combinators-import Data.Time.Clock-import Data.Tuple.Extra-import Safe-+import Control.Arrow (second)+import Control.Monad (filterM) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as BS+import Data.Int (Int16,Int32)+import Data.List.Extra (enumerate) import Data.Set (Set)+import Data.Time.Clock (DiffTime) import Foreign ((.|.)) import Foreign.C (CUInt) import Foreign.C.Error (Errno(Errno),errnoToIOError)-import System.Posix.ByteString (RawFilePath)+import Safe (initSafe,tailSafe)+import System.Posix.ByteString (Fd,RawFilePath)+import System.Posix.IO.ByteString (fdToHandle)  import qualified Evdev.LowLevel as LL import Evdev.LowLevel (ReadFlags(..))@@ -85,31 +87,31 @@                 in  n ++ "." ++ take 6 (r ++ ['0'..]) ++ "s"  pattern SyncEvent :: SyncEventType -> Event-pattern SyncEvent c <- Event EvSyn (toEnum . fromEnum -> c) _ _+pattern SyncEvent c <- Event EvSyn (convertEnum -> c) _ _  pattern KeyEvent :: Key -> KeyEventType -> Event-pattern KeyEvent c v <- Event EvKey (toEnum . fromEnum -> c) (toEnum . fromEnum -> v) _+pattern KeyEvent c v <- Event EvKey (convertEnum -> c) (convertEnum -> v) _  pattern RelativeEvent :: RelativeAxis -> EventValue -> Event-pattern RelativeEvent c v <- Event EvRel (toEnum . fromEnum -> c) v _+pattern RelativeEvent c v <- Event EvRel (convertEnum -> c) v _  pattern AbsoluteEvent :: AbsoluteAxis -> EventValue -> Event-pattern AbsoluteEvent c v <- Event EvAbs (toEnum . fromEnum -> c) v _+pattern AbsoluteEvent c v <- Event EvAbs (convertEnum -> c) v _  pattern MiscEvent :: MiscEventType -> EventValue -> Event-pattern MiscEvent c v <- Event EvMsc (toEnum . fromEnum -> c) v _+pattern MiscEvent c v <- Event EvMsc (convertEnum -> c) v _  pattern SwitchEvent :: SwitchEventType -> EventValue -> Event-pattern SwitchEvent c v <- Event EvSw (toEnum . fromEnum -> c) v _+pattern SwitchEvent c v <- Event EvSw (convertEnum -> c) v _  pattern LEDEvent :: LEDEventType -> EventValue -> Event-pattern LEDEvent c v <- Event EvLed (toEnum . fromEnum -> c) v _+pattern LEDEvent c v <- Event EvLed (convertEnum -> c) v _  pattern SoundEvent :: SoundEventType -> EventValue -> Event-pattern SoundEvent c v <- Event EvSnd (toEnum . fromEnum -> c) v _+pattern SoundEvent c v <- Event EvSnd (convertEnum -> c) v _  pattern RepeatEvent :: RepeatEventType -> EventValue -> Event-pattern RepeatEvent c v <- Event EvRep (toEnum . fromEnum -> c) v _+pattern RepeatEvent c v <- Event EvRep (convertEnum -> c) v _  pattern ForceFeedbackEvent :: EventCode -> EventValue -> Event pattern ForceFeedbackEvent c v <- Event EvFf c v _@@ -143,36 +145,47 @@ nextEvent :: Device -> Set ReadFlags -> IO Event nextEvent dev flags = do     (t,c,v,time) <- LL.convertEvent =<<-        throwCErrors "nextEvent" (devicePath dev) (LL.nextEvent (cDevice dev) (convertFlags flags))+        throwCErrors "nextEvent" (Right 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+    dev <- throwCErrors "newDevice" (Left 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+deviceName :: Device -> IO ByteString+deviceName = fmap BS.pack . LL.deviceName . cDevice +deviceFd :: Device -> IO Fd+deviceFd = LL.deviceFd . cDevice -{- Util -}+deviceProperties :: Device -> IO [DeviceProperty]+deviceProperties dev = filterM (LL.hasProperty $ cDevice dev) enumerate -tryIO :: IO a -> IO (Either IOException a)-tryIO = try --- 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+{- Util -}++-- run the action, throwing a relevant exception if the C errno is not 0+throwCErrors :: String -> Either ByteString Device -> IO (Errno, a) -> IO a+throwCErrors func pathOrDev x = do     (errno, res) <- x     case errno of         Errno 0 -> return res-        _ -> ioError $ errnoToIOError loc errno Nothing (Just $ BS.unpack path)+        Errno n -> do+            (handle,path) <- case pathOrDev of+                Left path -> return (Nothing,path)+                Right dev -> do+                    h <- fdToHandle =<< deviceFd dev+                    return (Just h, devicePath dev)+            ioError $ errnoToIOError func (Errno $ abs n) handle (Just $ BS.unpack path)  grabDevice' :: LL.GrabMode -> Device -> IO ()-grabDevice' mode dev = throwCErrors "grabDevice" (devicePath dev) $ LL.grabDevice (cDevice dev) mode+grabDevice' mode dev = throwCErrors "grabDevice" (Right dev) $ LL.grabDevice (cDevice dev) mode++-- obviously this isn't safe in general+-- we use it only after matching on 'EventType', to get the corresponding 'EventCode' and 'EventValue'+convertEnum :: (Enum a, Enum b) => a -> b+convertEnum = toEnum . fromEnum
src/Evdev/LowLevel.chs view
@@ -1,8 +1,10 @@-module Evdev.LowLevel where+{-# OPTIONS_HADDOCK hide, prune, ignore-exports #-} -import Data.Int-import Data.Time.Clock+module Evdev.LowLevel where +import Control.Monad.Loops (iterateWhile)+import Data.Int (Int16,Int32)+import Data.Time.Clock (DiffTime,picosecondsToDiffTime) import Foreign (Ptr) import Foreign.C (CInt,CUInt,CLong) import Foreign.C.Error (Errno(Errno))@@ -12,6 +14,9 @@ import System.Posix.IO.ByteString (OpenMode(ReadOnly),defaultFileFlags,openFd) import System.Posix.Types (Fd(Fd)) +import Evdev.Codes++#include <errno.h> #include <libevdev-1.0/libevdev/libevdev.h> #include <linux/input.h> @@ -51,7 +56,7 @@             in  convertTime <$> sec <*> usec  nextEvent :: Device -> CUInt -> IO (Errno, Event)-nextEvent dev flags = do+nextEvent dev flags = iterateWhile ((== Errno (-{#const EAGAIN #})) . fst) $ do     ptr <- mallocForeignPtrBytes {#sizeof input_event #}     err <- withForeignPtr ptr $ {#call libevdev_next_event #} dev flags     return (Errno err, Event ptr)@@ -72,6 +77,7 @@  {- Simpler functions -} +{#fun libevdev_has_property as hasProperty { `Device', convertEnum `DeviceProperty' } -> `Bool' #} {#fun libevdev_get_fd as deviceFd { `Device' } -> `Fd' Fd #} {#fun libevdev_get_name as deviceName { `Device' } -> `String' #} --TODO should really be ByteString
src/Evdev/Stream.hs view
@@ -1,13 +1,14 @@ module Evdev.Stream (-    allDevicePaths,+    allDevices,     allEvents,-    filteredEvents,     makeDevices,     readEvents,     readEventsMany,     ) where +import Control.Exception import Control.Monad+import System.IO.Error  import RawFilePath.Directory (doesFileExist,listDirectory) import System.Posix.ByteString (RawFilePath)@@ -19,32 +20,28 @@ import Evdev  -allEvents :: (IsStream t, Monad (t IO)) => t IO (Device, Event)-allEvents = filteredEvents $ const True--filteredEvents :: (IsStream t, Monad (t IO)) => (Device -> Bool) -> t IO (Device, Event)-filteredEvents p = readEventsMany $ S.filter p $ makeDevices allDevicePaths--readEvents :: IsStream t => Device -> t IO Event+-- | Read all events from a device.+readEvents :: Device -> SerialT IO Event readEvents dev = S.repeatM $ nextEvent dev defaultReadFlags +-- | Concurrently read events from multiple devices. readEventsMany :: IsStream t => AsyncT IO Device -> t IO (Device, Event) readEventsMany ds = asyncly $ do     d <- ds-    S.map (d,) $ readEvents d--makeDevices :: (IsStream t, Functor (t IO)) => t IO RawFilePath -> t IO Device-makeDevices = S.mapMaybeM maybeNewDevice----TODO use Streamly directly-allDevicePaths :: (IsStream t, Monad (t IO)) => t IO RawFilePath-allDevicePaths = do-    fs <- S.yieldM $ lsFiles evdevDir-    S.fromFoldable fs+    S.map (d,) $ serially $ readEvents d +-- | Create devices for all paths in the stream.+-- | Will throw an exception if a path doesn't correspond to a valid input device.+makeDevices :: IsStream t => t IO RawFilePath -> t IO Device+makeDevices = S.mapM newDevice -{- Util -}+-- | All events on all valid devices (in /dev/input).+allEvents :: IsStream t => t IO (Device, Event)+allEvents = readEventsMany allDevices --- lists files only, and returns full paths.-lsFiles :: RawFilePath -> IO [RawFilePath]-lsFiles p = filterM doesFileExist =<< (map (p </>) <$> listDirectory p)+-- | All valid devices (in /dev/input).+allDevices :: (IsStream t, Monad (t IO)) => t IO Device+allDevices =+    let maybeNewDevice path = catchJust (guard . isIllegalOperation) (Just <$> newDevice path) (\() -> return Nothing)+        paths = S.filterM doesFileExist $ S.map (evdevDir </>) $ S.fromFoldable =<< S.yieldM (listDirectory evdevDir)+    in  S.mapMaybeM maybeNewDevice paths