evdev 1.3.0.0 → 2.0.0.0
raw patch · 8 files changed
+289/−361 lines, 8 filesdep −eitherdep −hinotifydep −pathsdep ~basedep ~extradep ~timesetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies removed: either, hinotify, paths, posix-paths, process, rawfilepath, safe, streamly, streamly-fsnotify
Dependency ranges changed: base, extra, time
API changes (from Hackage documentation)
- Evdev: Blocking :: ReadFlag
- Evdev: ForceSync :: ReadFlag
- Evdev: Normal :: ReadFlag
- Evdev: Sync :: ReadFlag
- Evdev: [evCode] :: Event -> EventCode
- Evdev: [evTime] :: Event -> DiffTime
- Evdev: [evType] :: Event -> EventType
- Evdev: [evValue] :: Event -> EventValue
- Evdev: data KeyEventType
- Evdev: data ReadFlag
- Evdev: defaultReadFlags :: Set ReadFlag
- Evdev: instance GHC.Classes.Eq Evdev.KeyEventType
- Evdev: instance GHC.Classes.Ord Evdev.KeyEventType
- Evdev: instance GHC.Enum.Enum Evdev.KeyEventType
- Evdev: instance GHC.Read.Read Evdev.KeyEventType
- Evdev: instance GHC.Show.Show Evdev.KeyEventType
- Evdev: pattern AbsoluteEvent :: AbsoluteAxis -> EventValue -> Event
- Evdev: pattern ForceFeedbackEvent :: EventCode -> EventValue -> Event
- Evdev: pattern ForceFeedbackStatusEvent :: EventCode -> EventValue -> Event
- Evdev: pattern KeyEvent :: Key -> KeyEventType -> Event
- Evdev: pattern LEDEvent :: LEDEventType -> EventValue -> Event
- Evdev: pattern MiscEvent :: MiscEventType -> EventValue -> Event
- Evdev: pattern PowerEvent :: EventCode -> EventValue -> Event
- Evdev: pattern RelativeEvent :: RelativeAxis -> EventValue -> Event
- Evdev: pattern RepeatEvent :: RepeatEventType -> EventValue -> Event
- Evdev: pattern SoundEvent :: SoundEventType -> EventValue -> Event
- Evdev: pattern SwitchEvent :: SwitchEventType -> EventValue -> Event
- Evdev: pattern SyncEvent :: SyncEventType -> Event
- Evdev: prettyEvent :: Event -> String
- Evdev.Codes: data LEDEventType
- Evdev.Codes: data MiscEventType
- Evdev.Codes: data RepeatEventType
- Evdev.Codes: data SoundEventType
- Evdev.Codes: data SwitchEventType
- Evdev.Codes: data SyncEventType
- Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.LEDEventType
- Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.MiscEventType
- Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.RepeatEventType
- Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.SoundEventType
- Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.SwitchEventType
- Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.SyncEventType
- Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.LEDEventType
- Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.MiscEventType
- Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.RepeatEventType
- Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.SoundEventType
- Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.SwitchEventType
- Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.SyncEventType
- Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.LEDEventType
- Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.MiscEventType
- Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.RepeatEventType
- Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.SoundEventType
- Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.SwitchEventType
- Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.SyncEventType
- Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.LEDEventType
- Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.MiscEventType
- Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.RepeatEventType
- Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.SoundEventType
- Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.SwitchEventType
- Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.SyncEventType
- Evdev.Codes: instance GHC.Read.Read Evdev.Codes.LEDEventType
- Evdev.Codes: instance GHC.Read.Read Evdev.Codes.MiscEventType
- Evdev.Codes: instance GHC.Read.Read Evdev.Codes.RepeatEventType
- Evdev.Codes: instance GHC.Read.Read Evdev.Codes.SoundEventType
- Evdev.Codes: instance GHC.Read.Read Evdev.Codes.SwitchEventType
- Evdev.Codes: instance GHC.Read.Read Evdev.Codes.SyncEventType
- Evdev.Codes: instance GHC.Show.Show Evdev.Codes.LEDEventType
- Evdev.Codes: instance GHC.Show.Show Evdev.Codes.MiscEventType
- Evdev.Codes: instance GHC.Show.Show Evdev.Codes.RepeatEventType
- Evdev.Codes: instance GHC.Show.Show Evdev.Codes.SoundEventType
- Evdev.Codes: instance GHC.Show.Show Evdev.Codes.SwitchEventType
- Evdev.Codes: instance GHC.Show.Show Evdev.Codes.SyncEventType
- Evdev.Stream: allDevices :: (IsStream t, Monad (t IO)) => t IO Device
- Evdev.Stream: allEvents :: IsStream t => t IO (Device, Event)
- Evdev.Stream: makeDevices :: IsStream t => t IO RawFilePath -> t IO Device
- Evdev.Stream: newDevices :: (IsStream t, Monad (t IO)) => t IO Device
- Evdev.Stream: readEvents :: Device -> SerialT IO Event
- Evdev.Stream: readEventsMany :: IsStream t => AsyncT IO Device -> t IO (Device, Event)
+ Evdev: AbsoluteEvent :: AbsoluteAxis -> EventValue -> EventData
+ Evdev: CEvent :: Word16 -> Word16 -> Int32 -> CTimeVal -> CEvent
+ Evdev: CTimeVal :: Int64 -> Int64 -> CTimeVal
+ Evdev: ForceFeedbackEvent :: EventCode -> EventValue -> EventData
+ Evdev: ForceFeedbackStatusEvent :: EventCode -> EventValue -> EventData
+ Evdev: KeyEvent :: Key -> KeyEvent -> EventData
+ Evdev: LEDEvent :: LEDEvent -> EventValue -> EventData
+ Evdev: MiscEvent :: MiscEvent -> EventValue -> EventData
+ Evdev: PowerEvent :: EventCode -> EventValue -> EventData
+ Evdev: RelativeEvent :: RelativeAxis -> EventValue -> EventData
+ Evdev: RepeatEvent :: RepeatEvent -> EventValue -> EventData
+ Evdev: SoundEvent :: SoundEvent -> EventValue -> EventData
+ Evdev: SwitchEvent :: SwitchEvent -> EventValue -> EventData
+ Evdev: SyncEvent :: SyncEvent -> EventData
+ Evdev: UnknownEvent :: Word16 -> EventCode -> EventValue -> EventData
+ Evdev: [cEventCode] :: CEvent -> Word16
+ Evdev: [cEventTime] :: CEvent -> CTimeVal
+ Evdev: [cEventType] :: CEvent -> Word16
+ Evdev: [cEventValue] :: CEvent -> Int32
+ Evdev: [eventData] :: Event -> EventData
+ Evdev: [eventTime] :: Event -> DiffTime
+ Evdev: [tvSec] :: CTimeVal -> Int64
+ Evdev: [tvUsec] :: CTimeVal -> Int64
+ Evdev: data CEvent
+ Evdev: data CTimeVal
+ Evdev: data EventData
+ Evdev: data KeyEvent
+ Evdev: fromCEvent :: CEvent -> Event
+ Evdev: fromCTimeVal :: CTimeVal -> DiffTime
+ Evdev: instance GHC.Classes.Eq Evdev.EventData
+ Evdev: instance GHC.Classes.Eq Evdev.KeyEvent
+ Evdev: instance GHC.Classes.Ord Evdev.EventData
+ Evdev: instance GHC.Classes.Ord Evdev.KeyEvent
+ Evdev: instance GHC.Enum.Bounded Evdev.KeyEvent
+ Evdev: instance GHC.Enum.Enum Evdev.KeyEvent
+ Evdev: instance GHC.Num.Num Evdev.EventCode
+ Evdev: instance GHC.Num.Num Evdev.EventValue
+ Evdev: instance GHC.Read.Read Evdev.EventData
+ Evdev: instance GHC.Read.Read Evdev.KeyEvent
+ Evdev: instance GHC.Real.Integral Evdev.EventCode
+ Evdev: instance GHC.Real.Integral Evdev.EventValue
+ Evdev: instance GHC.Real.Real Evdev.EventCode
+ Evdev: instance GHC.Real.Real Evdev.EventValue
+ Evdev: instance GHC.Show.Show Evdev.EventData
+ Evdev: instance GHC.Show.Show Evdev.KeyEvent
+ Evdev: toCEvent :: Event -> CEvent
+ Evdev: toCTimeVal :: DiffTime -> CTimeVal
+ Evdev.Codes: data LEDEvent
+ Evdev.Codes: data MiscEvent
+ Evdev.Codes: data RepeatEvent
+ Evdev.Codes: data SoundEvent
+ Evdev.Codes: data SwitchEvent
+ Evdev.Codes: data SyncEvent
+ Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.LEDEvent
+ Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.MiscEvent
+ Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.RepeatEvent
+ Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.SoundEvent
+ Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.SwitchEvent
+ Evdev.Codes: instance GHC.Classes.Eq Evdev.Codes.SyncEvent
+ Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.LEDEvent
+ Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.MiscEvent
+ Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.RepeatEvent
+ Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.SoundEvent
+ Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.SwitchEvent
+ Evdev.Codes: instance GHC.Classes.Ord Evdev.Codes.SyncEvent
+ Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.LEDEvent
+ Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.MiscEvent
+ Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.RepeatEvent
+ Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.SoundEvent
+ Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.SwitchEvent
+ Evdev.Codes: instance GHC.Enum.Bounded Evdev.Codes.SyncEvent
+ Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.LEDEvent
+ Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.MiscEvent
+ Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.RepeatEvent
+ Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.SoundEvent
+ Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.SwitchEvent
+ Evdev.Codes: instance GHC.Enum.Enum Evdev.Codes.SyncEvent
+ Evdev.Codes: instance GHC.Read.Read Evdev.Codes.LEDEvent
+ Evdev.Codes: instance GHC.Read.Read Evdev.Codes.MiscEvent
+ Evdev.Codes: instance GHC.Read.Read Evdev.Codes.RepeatEvent
+ Evdev.Codes: instance GHC.Read.Read Evdev.Codes.SoundEvent
+ Evdev.Codes: instance GHC.Read.Read Evdev.Codes.SwitchEvent
+ Evdev.Codes: instance GHC.Read.Read Evdev.Codes.SyncEvent
+ Evdev.Codes: instance GHC.Show.Show Evdev.Codes.LEDEvent
+ Evdev.Codes: instance GHC.Show.Show Evdev.Codes.MiscEvent
+ Evdev.Codes: instance GHC.Show.Show Evdev.Codes.RepeatEvent
+ Evdev.Codes: instance GHC.Show.Show Evdev.Codes.SoundEvent
+ Evdev.Codes: instance GHC.Show.Show Evdev.Codes.SwitchEvent
+ Evdev.Codes: instance GHC.Show.Show Evdev.Codes.SyncEvent
- Evdev: Event :: EventType -> EventCode -> EventValue -> DiffTime -> Event
+ Evdev: Event :: EventData -> DiffTime -> Event
- Evdev: Pressed :: KeyEventType
+ Evdev: Pressed :: KeyEvent
- Evdev: Released :: KeyEventType
+ Evdev: Released :: KeyEvent
- Evdev: Repeated :: KeyEventType
+ Evdev: Repeated :: KeyEvent
- Evdev: nextEvent :: Device -> Set ReadFlag -> IO Event
+ Evdev: nextEvent :: Device -> IO Event
- Evdev.Codes: LedCapsl :: LEDEventType
+ Evdev.Codes: LedCapsl :: LEDEvent
- Evdev.Codes: LedCharging :: LEDEventType
+ Evdev.Codes: LedCharging :: LEDEvent
- Evdev.Codes: LedCompose :: LEDEventType
+ Evdev.Codes: LedCompose :: LEDEvent
- Evdev.Codes: LedKana :: LEDEventType
+ Evdev.Codes: LedKana :: LEDEvent
- Evdev.Codes: LedMail :: LEDEventType
+ Evdev.Codes: LedMail :: LEDEvent
- Evdev.Codes: LedMisc :: LEDEventType
+ Evdev.Codes: LedMisc :: LEDEvent
- Evdev.Codes: LedMute :: LEDEventType
+ Evdev.Codes: LedMute :: LEDEvent
- Evdev.Codes: LedNuml :: LEDEventType
+ Evdev.Codes: LedNuml :: LEDEvent
- Evdev.Codes: LedScrolll :: LEDEventType
+ Evdev.Codes: LedScrolll :: LEDEvent
- Evdev.Codes: LedSleep :: LEDEventType
+ Evdev.Codes: LedSleep :: LEDEvent
- Evdev.Codes: LedSuspend :: LEDEventType
+ Evdev.Codes: LedSuspend :: LEDEvent
- Evdev.Codes: MscGesture :: MiscEventType
+ Evdev.Codes: MscGesture :: MiscEvent
- Evdev.Codes: MscPulseled :: MiscEventType
+ Evdev.Codes: MscPulseled :: MiscEvent
- Evdev.Codes: MscRaw :: MiscEventType
+ Evdev.Codes: MscRaw :: MiscEvent
- Evdev.Codes: MscScan :: MiscEventType
+ Evdev.Codes: MscScan :: MiscEvent
- Evdev.Codes: MscSerial :: MiscEventType
+ Evdev.Codes: MscSerial :: MiscEvent
- Evdev.Codes: MscTimestamp :: MiscEventType
+ Evdev.Codes: MscTimestamp :: MiscEvent
- Evdev.Codes: RepDelay :: RepeatEventType
+ Evdev.Codes: RepDelay :: RepeatEvent
- Evdev.Codes: RepPeriod :: RepeatEventType
+ Evdev.Codes: RepPeriod :: RepeatEvent
- Evdev.Codes: SndBell :: SoundEventType
+ Evdev.Codes: SndBell :: SoundEvent
- Evdev.Codes: SndClick :: SoundEventType
+ Evdev.Codes: SndClick :: SoundEvent
- Evdev.Codes: SndTone :: SoundEventType
+ Evdev.Codes: SndTone :: SoundEvent
- Evdev.Codes: SwCameraLensCover :: SwitchEventType
+ Evdev.Codes: SwCameraLensCover :: SwitchEvent
- Evdev.Codes: SwDock :: SwitchEventType
+ Evdev.Codes: SwDock :: SwitchEvent
- Evdev.Codes: SwFrontProximity :: SwitchEventType
+ Evdev.Codes: SwFrontProximity :: SwitchEvent
- Evdev.Codes: SwHeadphoneInsert :: SwitchEventType
+ Evdev.Codes: SwHeadphoneInsert :: SwitchEvent
- Evdev.Codes: SwJackPhysicalInsert :: SwitchEventType
+ Evdev.Codes: SwJackPhysicalInsert :: SwitchEvent
- Evdev.Codes: SwKeypadSlide :: SwitchEventType
+ Evdev.Codes: SwKeypadSlide :: SwitchEvent
- Evdev.Codes: SwLid :: SwitchEventType
+ Evdev.Codes: SwLid :: SwitchEvent
- Evdev.Codes: SwLineinInsert :: SwitchEventType
+ Evdev.Codes: SwLineinInsert :: SwitchEvent
- Evdev.Codes: SwLineoutInsert :: SwitchEventType
+ Evdev.Codes: SwLineoutInsert :: SwitchEvent
- Evdev.Codes: SwMicrophoneInsert :: SwitchEventType
+ Evdev.Codes: SwMicrophoneInsert :: SwitchEvent
- Evdev.Codes: SwMuteDevice :: SwitchEventType
+ Evdev.Codes: SwMuteDevice :: SwitchEvent
- Evdev.Codes: SwRadio :: SwitchEventType
+ Evdev.Codes: SwRadio :: SwitchEvent
- Evdev.Codes: SwRfkillAll :: SwitchEventType
+ Evdev.Codes: SwRfkillAll :: SwitchEvent
- Evdev.Codes: SwRotateLock :: SwitchEventType
+ Evdev.Codes: SwRotateLock :: SwitchEvent
- Evdev.Codes: SwTabletMode :: SwitchEventType
+ Evdev.Codes: SwTabletMode :: SwitchEvent
- Evdev.Codes: SwVideooutInsert :: SwitchEventType
+ Evdev.Codes: SwVideooutInsert :: SwitchEvent
- Evdev.Codes: SynConfig :: SyncEventType
+ Evdev.Codes: SynConfig :: SyncEvent
- Evdev.Codes: SynDropped :: SyncEventType
+ Evdev.Codes: SynDropped :: SyncEvent
- Evdev.Codes: SynMtReport :: SyncEventType
+ Evdev.Codes: SynMtReport :: SyncEvent
- Evdev.Codes: SynReport :: SyncEventType
+ Evdev.Codes: SynReport :: SyncEvent
Files
- CHANGELOG.md +17/−17
- README.md +6/−24
- Setup.hs +0/−2
- evdev.cabal +41/−45
- src/Evdev.hs +154/−116
- src/Evdev/Codes.chs +40/−18
- src/Evdev/LowLevel.chs +31/−12
- src/Evdev/Stream.hs +0/−127
CHANGELOG.md view
@@ -1,27 +1,27 @@ # Revision history for evdev -## 0.1.0.0 -- 2019-07-20--* First version. Released on an unsuspecting world.--## 1.0.0.0 -- 2019-12-15--* First stable release.+## 2.0.0.0 -- 2020-05-30+* This is really what should have been the `1.0` release, had I had a better understanding of the PVP when starting off.+* Split Streamly integration into a separate package, `evdev-streamly`.+* Safety improvements - `UnknownEvent` is used to handle a number of what were previously potential crashes.+* Lots of other, minor improvements, including simplifying the API in places, and exposing lower-level functionality where it is likely to be useful. -## 1.1.0.0 -- 2019-12-16+## 1.3.0.0 -- 2020-03-03+* Fix memory management issues and file descriptor leaks.+* Rename 'ReadFlags' to 'ReadFlag' as the type clearly represents just one flag. -* Hide LowLevel.+## 1.2.0.0 -- 2019-12-19+* Add `newDevices` stream.+* More robust error handling when reading from multiple devices. ## 1.1.0.1 -- 2019-12-18- * Add `pkgconfig-depends` field to cabal file. -## 1.2.0.0 -- 2019-12-19--* Add `newDevices` stream.-* More robust error handling when reading from multiple devices.+## 1.1.0.0 -- 2019-12-16+* Hide LowLevel. -## 1.3.0.0 -- 2020-03-03+## 1.0.0.0 -- 2019-12-15+* First stable release. -* Fix memory management issues and file descriptor leaks.-* Rename 'ReadFlags' to 'ReadFlag' as the type clearly represents just one flag.+## 0.1.0.0 -- 2019-07-20+* First version. Released on an unsuspecting world.
README.md view
@@ -1,22 +1,9 @@-Haskell evdev library-=====================--http://hackage.haskell.org/package/evdev+Evdev+===== This library provides access to the Linux [evdev](https://en.wikipedia.org/wiki/Evdev) interface, for reading input events from devices. It uses [c2hs](https://github.com/haskell/c2hs/wiki/User-Guide)-generated bindings to [libevdev](https://www.freedesktop.org/wiki/Software/libevdev/), which should be available on almost any modern Linux distro. -Modules---------- `Evdev` provides the basic functionality for initialising devices, reading events etc.-- `Evdev.Codes` contains datatypes corresponding to the constants in `input-event-codes.h`, such as keys and device properties.-- `Evdev.Stream` provides a higher-level [Streamly](https://hackage.haskell.org/package/streamly)-based interface, for obtaining a stream of events.---Why streamly?---------------Compared to other Haskell streaming libraries, I've found `streamly` to have a remarkably easy-to-use API, and the best, simplest support for concurrency. For example, merging concurrent streams of events, from different devices, is trivial.--If you wish to use this library alongside `conduit`, `pipes` etc. then see [here](https://hackage.haskell.org/package/streamly-0.7.0/docs/Streamly-Tutorial.html#g:39) for a guide on interoperation.+It aims to expose the full set of functionality exposed by `libevdev`, while providing stronger types, and a higher level of abstraction - no worrying about memory management, ordering of operations etc. Getting started ---------------@@ -26,12 +13,7 @@ The [evdev-examples](https://github.com/georgefst/evdev/tree/master/evdev-examples) folder contains a basic `evtest` clone, with the added ability to read events from multiple devices concurrently. -See [Hackage](http://hackage.haskell.org/package/evdev) for further documentation.--Still to come---------------Haddock documentation for the non-stream interface. I intend to redesign the interface in some minor ways first. Until then, everything maps quite directly to the C library, while taking care of the nitty-gritty low-level stuff.--There are many more methods in the C library that I'd like to expose. So far the emphasis has very much been on squeezing out the functionality I personally needed.+Streamly+-------- -Some of the streaming functions print exceptions straight to `stderr`. Again, this was convenient at the time, but it would be cleaner to allow specifying a `Writer`, or passing callbacks.+For a higher-level, more functional API based on event streams, see [evdev-streamly](http://hackage.haskell.org/package/evdev-streamly).
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
evdev.cabal view
@@ -1,57 +1,53 @@-cabal-version: 2.2+cabal-version: 3.0 name: evdev-version: 1.3.0.0+version: 2.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.+description: Provides access to the Linux event device interface. synopsis: Bindings to libevdev homepage: https://github.com/georgefst/evdev license: BSD-3-Clause license-file: LICENSE category: System-extra-source-files: CHANGELOG.md- README.md+extra-source-files:+ CHANGELOG.md+ README.md source-repository head- type: git- location: git://github.com/georgefst/evdev.git+ type: git+ location: git://github.com/georgefst/evdev.git library- exposed-modules: Evdev- Evdev.Codes- Evdev.Stream- other-modules: Evdev.LowLevel- hs-source-dirs: src- ghc-options: -Wall- build-depends: base ^>= 4.11 || ^>= 4.12 || ^>= 4.13,- bytestring ^>= 0.10,- containers ^>= 0.6.2,- either ^>= 5.0.1,- time ^>= 1.9.3,- unix ^>= 2.7.2,- process ^>= 1.6.5,- extra ^>= 1.6.18,- hinotify ^>= 0.4,- posix-paths ^>= 0.2.1,- rawfilepath ^>= 0.2.4,- streamly ^>= 0.6.1 || ^>= 0.7,- safe ^>= 0.3.18,- monad-loops ^>= 0.4.3,- streamly-fsnotify ^>= 1.0,- paths ^>= 0.2,- build-tool-depends: c2hs:c2hs- default-language: Haskell2010- default-extensions: FlexibleContexts- GeneralizedNewtypeDeriving- LambdaCase- NumericUnderscores- OverloadedLists- OverloadedStrings- PatternSynonyms- TupleSections- ViewPatterns- includes: libevdev-1.0/libevdev/libevdev.h- linux/input.h- errno.h- c-sources: src-c/evdev-hs.c- pkgconfig-depends: libevdev+ exposed-modules:+ Evdev+ Evdev.Codes+ other-modules:+ Evdev.LowLevel+ hs-source-dirs: src+ c-sources:+ src-c/evdev-hs.c+ pkgconfig-depends:+ libevdev+ ghc-options:+ -Wall+ build-depends:+ base >= 4.11 && < 5,+ bytestring ^>= 0.10,+ containers ^>= 0.6.2,+ extra ^>= {1.6.18, 1.7},+ monad-loops ^>= 0.4.3,+ time ^>= {1.9.3, 1.10},+ unix ^>= 2.7.2,+ build-tool-depends:+ c2hs:c2hs+ default-language: Haskell2010+ default-extensions:+ DerivingStrategies+ FlexibleContexts+ GeneralizedNewtypeDeriving+ LambdaCase+ NumericUnderscores+ OverloadedStrings+ ScopedTypeVariables+ TupleSections+ ViewPatterns
src/Evdev.hs view
@@ -1,160 +1,188 @@+{-# OPTIONS_GHC -fno-state-hack #-}++-- | The main module for working with devices and events. module Evdev (- pattern SyncEvent,- pattern KeyEvent,- pattern RelativeEvent,- pattern AbsoluteEvent,- pattern MiscEvent,- pattern SwitchEvent,- pattern LEDEvent,- pattern SoundEvent,- pattern RepeatEvent,- pattern ForceFeedbackEvent,- pattern PowerEvent,- pattern ForceFeedbackStatusEvent,- prettyEvent,- defaultReadFlags,- grabDevice,- ungrabDevice,- nextEvent,+ -- * Devices+ Device, newDevice,+ nextEvent, evdevDir,+ -- ** Properties deviceName,- deviceFd, devicePath, deviceProperties,- Device,- Event(..), --TODO provide access to Word16 etc...+ deviceFd,+ -- ** Grabbing+ grabDevice,+ ungrabDevice,++ -- * Events+ Event(..),+ EventData(..),+ KeyEvent(..), EventCode(..), EventValue(..),- KeyEventType(..),- ReadFlag(..),++ -- * Lower-level types+ -- | These correspond more directly to C's /input_event/ and /timeval/.+ -- They are used internally, but may be useful for advanced users.+ LL.CEvent(..),+ toCEvent,+ fromCEvent,+ LL.CTimeVal(..),+ toCTimeVal,+ fromCTimeVal, ) where -import Control.Arrow (second)+import Control.Arrow ((&&&)) import Control.Monad (filterM,join) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as BS import Data.Int (Int32) import Data.List.Extra (enumerate)+import Data.Map ((!?), Map)+import qualified Data.Map as Map+import Data.Maybe (fromMaybe)+import Data.Ratio ((%)) import Data.Set (Set)-import Data.Time.Clock (DiffTime,picosecondsToDiffTime)+import qualified Data.Set as Set+import Data.Time.Clock (DiffTime) import Data.Word (Word16) import Foreign ((.|.))-import Foreign.C (CInt(..),CUInt(..),CUShort(..))+import Foreign.C (CUInt) import Foreign.C.Error (Errno(Errno),errnoToIOError)-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 (ReadFlag(..)) import Evdev.Codes -- 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.+-- | An input 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}+-- | An input event, including the timestamp.+data Event = Event+ { eventData :: EventData+ , eventTime :: 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 -> unwords [show k, show t]- RelativeEvent c v -> unwords [show c, showE v]- AbsoluteEvent c v -> unwords [show c, showE v]- MiscEvent c v -> unwords [show c, showE v]- SwitchEvent c v -> unwords [show c, showE v]- LEDEvent c v -> unwords [show c, showE v]- SoundEvent c v -> unwords [show c, showE v]- RepeatEvent c v -> unwords [show c, showE v]- ForceFeedbackEvent c v -> unwords [showE c, showE v]- PowerEvent c v -> unwords [showE c, showE v]- ForceFeedbackStatusEvent c v -> unwords [showE c, showE v]- _ -> error $ "show: unrecognised Event: " ++ unwords- [showE $ evType x, showE $ evCode x, showE $ evValue x]- where- showE :: Enum x => x -> String- showE = show . fromEnum- showTime t = -- fix time string to always have same length after '.', by adding 0s- let (n,r) = second tailSafe $ span (/= '.') $ initSafe $ show t- in n ++ "." ++ take 6 (r ++ ['0'..]) ++ "s"--pattern SyncEvent :: SyncEventType -> Event-pattern SyncEvent c <- Event EvSyn (convertEnum -> c) _ _--pattern KeyEvent :: Key -> KeyEventType -> Event-pattern KeyEvent c v <- Event EvKey (convertEnum -> c) (convertEnum -> v) _--pattern RelativeEvent :: RelativeAxis -> EventValue -> Event-pattern RelativeEvent c v <- Event EvRel (convertEnum -> c) v _--pattern AbsoluteEvent :: AbsoluteAxis -> EventValue -> Event-pattern AbsoluteEvent c v <- Event EvAbs (convertEnum -> c) v _--pattern MiscEvent :: MiscEventType -> EventValue -> Event-pattern MiscEvent c v <- Event EvMsc (convertEnum -> c) v _--pattern SwitchEvent :: SwitchEventType -> EventValue -> Event-pattern SwitchEvent c v <- Event EvSw (convertEnum -> c) v _--pattern LEDEvent :: LEDEventType -> EventValue -> Event-pattern LEDEvent c v <- Event EvLed (convertEnum -> c) v _--pattern SoundEvent :: SoundEventType -> EventValue -> Event-pattern SoundEvent c v <- Event EvSnd (convertEnum -> c) v _--pattern RepeatEvent :: RepeatEventType -> EventValue -> Event-pattern RepeatEvent c v <- Event EvRep (convertEnum -> 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 _+--TODO name?+-- | An input event, without the timestamp.+-- Each constructor corresponds to one [event type](https://www.kernel.org/doc/html/latest/input/event-codes.html#event-types), except for 'UnknownEvent'.+data EventData+ = SyncEvent SyncEvent+ | KeyEvent Key KeyEvent+ | RelativeEvent RelativeAxis EventValue+ | AbsoluteEvent AbsoluteAxis EventValue+ | MiscEvent MiscEvent EventValue+ | SwitchEvent SwitchEvent EventValue+ | LEDEvent LEDEvent EventValue+ | SoundEvent SoundEvent EventValue+ | RepeatEvent RepeatEvent EventValue+ | ForceFeedbackEvent EventCode EventValue+ | PowerEvent EventCode EventValue+ | ForceFeedbackStatusEvent EventCode EventValue+ | UnknownEvent Word16 EventCode EventValue {- ^ We include this primarily so that 'fromCEvent' can be well-defined -+ let us know if you ever actually see one emitted by a device, as it would likely+ indicate a shortcoming in the library. -}+ deriving (Eq, Ord, Read, Show) -newtype EventCode = EventCode Word16 deriving (Enum, Eq, Ord, Read, Show)-newtype EventValue = EventValue Int32 deriving (Enum, Eq, Ord, Read, Show)+-- | A direct representation of the /code/ field of the C /input_event/, for when there is no obvious meaningful sum type.+newtype EventCode = EventCode Word16+ deriving stock (Eq, Ord, Read, Show)+ deriving newtype (Enum, Integral, Real, Num) --TODO all this baggage to make 'toEnum'' slightly easier?+-- | A direct representation of the /value/ field of the C /input_event/, for when there is no obvious meaningful sum type.+newtype EventValue = EventValue Int32+ deriving stock (Eq, Ord, Read, Show)+ deriving newtype (Enum, Integral, Real, Num) -data KeyEventType+-- | The status of a key.+data KeyEvent = Released | Pressed | Repeated- deriving (Enum, Eq, Ord, Read, Show)+ deriving (Bounded, Enum, Eq, Ord, Read, Show) -convertFlags :: Set ReadFlag -> CUInt+convertFlags :: Set LL.ReadFlag -> CUInt convertFlags = fromIntegral . foldr ((.|.) . fromEnum) 0 -defaultReadFlags :: Set ReadFlag-defaultReadFlags = [Normal,Blocking]+defaultReadFlags :: Set LL.ReadFlag+defaultReadFlags = Set.fromList [LL.Normal, LL.Blocking] +-- | Prevent other clients (including kernel-internal ones) from receiving events. Often a bad idea. grabDevice :: Device -> IO () grabDevice = grabDevice' LL.LibevdevGrab+-- | Release a grabbed device. ungrabDevice :: Device -> IO () ungrabDevice = grabDevice' LL.LibevdevUngrab -nextEvent :: Device -> Set ReadFlag -> IO Event-nextEvent dev flags = do- (CUShort t, CUShort c, CInt v, s, us) <-- throwCErrors "nextEvent" (Right dev) $ LL.nextEvent (cDevice dev) (convertFlags flags)- return $ Event (convertEnum t) (EventCode c) (EventValue v) $- picosecondsToDiffTime $ 1_000_000_000_000 * fromIntegral s + 1_000_000 * fromIntegral us+-- | Get the next event from the device.+nextEvent :: Device -> IO Event+nextEvent dev =+ fromCEvent <$> throwCErrors "nextEvent" (Right dev) (LL.nextEvent (cDevice dev) (convertFlags defaultReadFlags)) +fromCEvent :: LL.CEvent -> Event+fromCEvent (LL.CEvent t (EventCode -> c) (EventValue -> v) time) = Event event $ fromCTimeVal time+ where+ event = fromMaybe unknown $ toEnum' t >>= \case+ EvSyn -> SyncEvent <$> toEnum' c+ EvKey -> KeyEvent <$> toEnum' c <*> toEnum' v+ EvRel -> RelativeEvent <$> toEnum' c <*> pure v+ EvAbs -> AbsoluteEvent <$> toEnum' c <*> pure v+ EvMsc -> MiscEvent <$> toEnum' c <*> pure v+ EvSw -> SwitchEvent <$> toEnum' c <*> pure v+ EvLed -> LEDEvent <$> toEnum' c <*> pure v+ EvSnd -> SoundEvent <$> toEnum' c <*> pure v+ EvRep -> RepeatEvent <$> toEnum' c <*> pure v+ EvFf -> Just $ ForceFeedbackEvent c v+ EvPwr -> Just $ PowerEvent c v+ EvFfStatus -> Just $ ForceFeedbackStatusEvent c v+ unknown = UnknownEvent t c v++toCEvent :: Event -> LL.CEvent+toCEvent (Event e time) = case e of+ -- from kernel docs, 'EV_SYN event values are undefined' - we always seem to see 0, so may as well use that+ SyncEvent (fe -> c) -> LL.CEvent (fe EvSyn) c 0 cTime+ KeyEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvKey) c v cTime+ RelativeEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvRel) c v cTime+ AbsoluteEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvAbs) c v cTime+ MiscEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvMsc) c v cTime+ SwitchEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvSw) c v cTime+ LEDEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvLed) c v cTime+ SoundEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvSnd) c v cTime+ RepeatEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvRep) c v cTime+ ForceFeedbackEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvFf) c v cTime+ PowerEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvPwr) c v cTime+ ForceFeedbackStatusEvent (fe -> c) (fe -> v) -> LL.CEvent (fe EvFfStatus) c v cTime+ UnknownEvent (fe -> t) (fe -> c) (fe -> v) -> LL.CEvent t c v cTime+ where+ --TODO this isn't entirely safe in general, though it's really no worse than 'fromEnum'+ -- if we could tell C2HS which int type each #defined enum corresponded to, we could check this statically+ fe :: (Enum a, Integral b) => a -> b+ fe = fromIntegral . fromEnum+ cTime = toCTimeVal time++fromCTimeVal :: LL.CTimeVal -> DiffTime+fromCTimeVal (LL.CTimeVal s us) =+ fromRational $ fromIntegral s + (fromIntegral us % 1_000_000)++--TODO QuickCheck inverse+toCTimeVal :: DiffTime -> LL.CTimeVal+toCTimeVal t = LL.CTimeVal n (round $ f * 1_000_000)+ where (n,f) = properFraction t++-- | Create a device from a valid path - usually /\/dev\/input\/eventX/ for some /X/. newDevice :: RawFilePath -> IO Device newDevice path = do dev <- throwCErrors "newDevice" (Left path) $ LL.newDevice path return $ Device dev path +-- | The usual directory containing devices (/"\/dev\/input"/). evdevDir :: RawFilePath evdevDir = "/dev/input" @@ -187,15 +215,25 @@ grabDevice' :: LL.GrabMode -> Device -> IO () grabDevice' mode dev = throwCErrors "grabDevice" (Right dev) $ LL.grabDevice (cDevice dev) mode ---TODO ensure all uses are safe- -- really, fromEnum should be :: e -> Integer- -- and toEnum :: Integral a => a -> Maybe e- -- the issues with Enum are great enough that it may be worth considering using something more bespoke than c2hs- -- or it could be worth a PR- -- auto-generate safe *toInt/*fromInt based on the current logic- -- then implement Enum instance in terms of those- -- note that we also use to/from-Enum in LowLevel--- 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++{-+TODO this is a workaround until c2hs has a better story for enum conversions+ when we remove it we can get rid of '-fno-state-hack'++based on profiling, and Debug.Trace, it seems that 'enumMap' is computed no more times than necessary+ (6 - number of combinations of a and k that it is called with)+ but based on https://www.reddit.com/r/haskell/comments/grskne/help_reasoning_about_performance_memoization/,+ it's possible that behaviour is worse without profiling on (argh...)++open c2hs issue+ we perhaps essentially want the `CEnum` class proposed at: https://github.com/haskell/c2hs/issues/78+ but perhaps belonging (at least initially) in c2hs rather than base, for expediency+ this doesn't necessarily consider enum defines though - discussion is around capturing the semantics of actual C enums+ alternatively, monomorphic functions for each type, as with c2hs's with* functions+-}+toEnum' :: forall k a. (Integral k, Bounded a, Enum a) => k -> Maybe a+toEnum' = (enumMap !?)+ where+ --TODO HashMap, IntMap?+ enumMap :: Map k a+ enumMap = Map.fromList $ map (fromIntegral . fromEnum &&& id) enumerate
src/Evdev/Codes.chs view
@@ -1,17 +1,19 @@+{-+TODO haddock doesn't quite work correctly with LINE pragmas+ https://github.com/haskell/haddock/issues/441+ for now we can work around this by deleting the pragmas before upload to hackage++seems to be on its way to being fixed with `.hie` files (enable `-fwrite-ide-info`)+ https://github.com/haskell/haddock/commit/8bc3c2990475a254e168fbdb005af93f9397b19c+-}++-- | Datatypes corresponding to the constants in [input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h).+-- See [the Linux Kernel documentation](https://www.kernel.org/doc/html/latest/input/event-codes.html) for full details, noting that all names have been mechanically transformed into CamelCase. module Evdev.Codes where #include <linux/input-event-codes.h> -{#enum define DeviceProperty {- INPUT_PROP_POINTER as InputPropPointer,- INPUT_PROP_DIRECT as InputPropDirect,- INPUT_PROP_BUTTONPAD as InputPropButtonpad,- INPUT_PROP_SEMI_MT as InputPropSemiMt,- INPUT_PROP_TOPBUTTONPAD as InputPropTopbuttonpad,- INPUT_PROP_POINTING_STICK as InputPropPointingStick,- INPUT_PROP_ACCELEROMETER as InputPropAccelerometer}- deriving (Bounded, Eq, Ord, Read, Show) #}-+-- | Each of these corresponds to one of the contructors of 'Evdev.EventData'. So you're unlikely to need to use these directly (C doesn't have ADTs - we do). {#enum define EventType { EV_SYN as EvSyn, EV_KEY as EvKey,@@ -27,13 +29,15 @@ EV_FF_STATUS as EvFfStatus} deriving (Bounded, Eq, Ord, Read, Show) #} -{#enum define SyncEventType {- SYN_REPORT as SynReport,+-- | Synchronization events+{#enum define SyncEvent {+ SYN_REPORT as SynReport, -- | Used to separate packets of simultaneous events SYN_CONFIG as SynConfig, SYN_MT_REPORT as SynMtReport,- SYN_DROPPED as SynDropped}+ SYN_DROPPED as SynDropped} --TODO handle SYN_DROPPED automatically for streams deriving (Bounded, Eq, Ord, Read, Show) #} +-- | Keys and buttons {#enum define Key { KEY_RESERVED as KeyReserved, KEY_ESC as KeyEsc,@@ -577,6 +581,7 @@ BTN_TRIGGER_HAPPY40 as BtnTriggerHappy40} deriving (Bounded, Eq, Ord, Read, Show) #} +-- | Relative changes {#enum define RelativeAxis { REL_X as RelX, REL_Y as RelY,@@ -590,6 +595,7 @@ REL_MISC as RelMisc} deriving (Bounded, Eq, Ord, Read, Show) #} +-- | Absolute changes {#enum define AbsoluteAxis { ABS_X as AbsX, ABS_Y as AbsY,@@ -635,7 +641,8 @@ ABS_MT_TOOL_Y as AbsMtToolY} deriving (Bounded, Eq, Ord, Read, Show) #} -{#enum define SwitchEventType {+-- | Stateful binary switches+{#enum define SwitchEvent { SW_LID as SwLid, SW_TABLET_MODE as SwTabletMode, SW_HEADPHONE_INSERT as SwHeadphoneInsert,@@ -654,7 +661,8 @@ SW_MUTE_DEVICE as SwMuteDevice} deriving (Bounded, Eq, Ord, Read, Show) #} -{#enum define MiscEventType {+-- | Miscellaneous+{#enum define MiscEvent { MSC_SERIAL as MscSerial, MSC_PULSELED as MscPulseled, MSC_GESTURE as MscGesture,@@ -663,7 +671,8 @@ MSC_TIMESTAMP as MscTimestamp} deriving (Bounded, Eq, Ord, Read, Show) #} -{#enum define LEDEventType {+-- | LEDs+{#enum define LEDEvent { LED_NUML as LedNuml, LED_CAPSL as LedCapsl, LED_SCROLLL as LedScrolll,@@ -677,13 +686,26 @@ LED_CHARGING as LedCharging} deriving (Bounded, Eq, Ord, Read, Show) #} -{#enum define RepeatEventType {+-- | Specifying autorepeating events+{#enum define RepeatEvent { REP_DELAY as RepDelay, REP_PERIOD as RepPeriod} deriving (Bounded, Eq, Ord, Read, Show) #} -{#enum define SoundEventType {+-- | For simple sound output devices+{#enum define SoundEvent { SND_CLICK as SndClick, SND_BELL as SndBell, SND_TONE as SndTone}+ deriving (Bounded, Eq, Ord, Read, Show) #}++-- | Device properties+{#enum define DeviceProperty {+ INPUT_PROP_POINTER as InputPropPointer,+ INPUT_PROP_DIRECT as InputPropDirect,+ INPUT_PROP_BUTTONPAD as InputPropButtonpad,+ INPUT_PROP_SEMI_MT as InputPropSemiMt,+ INPUT_PROP_TOPBUTTONPAD as InputPropTopbuttonpad,+ INPUT_PROP_POINTING_STICK as InputPropPointingStick,+ INPUT_PROP_ACCELEROMETER as InputPropAccelerometer} deriving (Bounded, Eq, Ord, Read, Show) #}
src/Evdev/LowLevel.chs view
@@ -2,8 +2,11 @@ import Control.Monad.Loops (iterateWhile) import Data.ByteString (ByteString,packCString)+import Data.Coerce (coerce)+import Data.Int (Int32,Int64)+import Data.Word (Word16) import Foreign (Ptr,allocaBytes)-import Foreign.C (CInt(..),CUInt(..),CUShort(..),CLong)+import Foreign.C (CInt(..),CLong(..),CUInt(..),CUShort(..)) import Foreign.C.Error (Errno(Errno)) import System.Posix.ByteString (RawFilePath) import System.Posix.IO.ByteString (OpenMode(ReadOnly),defaultFileFlags,openFd)@@ -32,20 +35,35 @@ void libevdev_hs_close(struct libevdev *dev); #endc +data CEvent = CEvent+ { cEventType :: Word16+ , cEventCode :: Word16+ , cEventValue :: Int32+ , cEventTime :: CTimeVal+ }+ deriving (Eq, Ord, Read, Show) +data CTimeVal = CTimeVal+ { tvSec :: Int64+ , tvUsec :: Int64+ }+ deriving (Eq, Ord, Read, Show)++ {- Conversions -} {#fun libevdev_next_event { `Device', `CUInt', `Ptr ()' } -> `Errno' Errno #}-nextEvent :: Device -> CUInt -> IO (Errno, (CUShort,CUShort,CInt,CLong,CLong)) --TODO not CLong on all platforms+nextEvent :: Device -> CUInt -> IO (Errno, CEvent) nextEvent dev flags = allocaBytes {#sizeof input_event #} $ \evPtr -> (,) <$> iterateWhile (== Errno (-{#const EAGAIN #})) (libevdev_next_event dev flags evPtr)- <*> (- (,,,,)- <$> {#get input_event->type #} evPtr- <*> {#get input_event->code #} evPtr- <*> {#get input_event->value #} evPtr- <*> {#get input_event->time.tv_sec #} evPtr- <*> {#get input_event->time.tv_usec #} evPtr+ <*> ( CEvent+ <$> (coerce <$> {#get input_event->type #} evPtr)+ <*> (coerce <$> {#get input_event->code #} evPtr)+ <*> (coerce <$> {#get input_event->value #} evPtr)+ <*> ( CTimeVal+ <$> (coerce <$> {#get input_event->time.tv_sec #} evPtr)+ <*> (coerce <$> {#get input_event->time.tv_usec #} evPtr)+ ) ) {#fun libevdev_grab { `Device', `GrabMode' } -> `Errno' Errno #}@@ -65,14 +83,15 @@ {- Simpler functions -} -{#fun libevdev_has_property as hasProperty { `Device', convertEnum `DeviceProperty' } -> `Bool' #}+{#fun libevdev_has_property as hasProperty { `Device', devPropToInt `DeviceProperty' } -> `Bool' #} {#fun libevdev_get_fd as deviceFd { `Device' } -> `Fd' Fd #} {#fun libevdev_get_name as deviceName { `Device' } -> `IO ByteString' packCString #} + {- Util -} -convertEnum :: DeviceProperty -> CUInt-convertEnum = fromIntegral . fromEnum+devPropToInt :: DeviceProperty -> CUInt+devPropToInt = fromIntegral . fromEnum (.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d (.:) = (.) . (.)
− src/Evdev/Stream.hs
@@ -1,127 +0,0 @@--- | The high-level, stream-based API.--- Unless stated otherwise, these functions will throw exceptions if the underlying C calls fail.-module Evdev.Stream (- allDevices,- allEvents,- makeDevices,- newDevices,- readEvents,- readEventsMany,-) where--import Data.Bool-import Data.Either.Extra-import Data.Functor-import System.IO-import System.IO.Error--import Data.Set (Set)-import qualified Data.Set as Set-import qualified Data.ByteString.Char8 as BS-import RawFilePath.Directory (doesFileExist,listDirectory)-import qualified Streamly.FSNotify as N-import Streamly.FSNotify (EventPredicate(EventPredicate),FSEntryType(NotDir),watchDirectory)-import System.Path (Absolute,Path,fromFilePath,toFilePath)-import System.Posix.ByteString (RawFilePath)-import System.Posix.FilePath ((</>))--import Streamly-import qualified Streamly.Prelude as S--import Evdev----- | Read all events from a device.-readEvents :: Device -> SerialT IO Event-readEvents dev = S.repeatM $ nextEvent dev defaultReadFlags---- | Concurrently read events from multiple devices.--- If a read fails on one, the exception is printed to stderr and the stream continues to read from the others.-readEventsMany :: IsStream t => AsyncT IO Device -> t IO (Device, Event)-readEventsMany ds = asyncly $ do- d <- ds- S.map (d,) $ serially $ readEvents' d- where- -- catch all IO errors- readEvents' :: Device -> SerialT IO Event- readEvents' dev = unfoldM $ printIOError' $ nextEvent dev defaultReadFlags---- | Create devices for all paths in the stream.-makeDevices :: IsStream t => t IO RawFilePath -> t IO Device-makeDevices = S.mapM newDevice---- | All events on all valid devices (in /\/dev\/input/).--- Prints any exceptions.------ > allEvents == readEventsMany allDevices-allEvents :: IsStream t => t IO (Device, Event)-allEvents = readEventsMany allDevices----TODO call this 'oldDevices' or 'existingDevices', and have 'allDevices' include 'newDevices'?--- | All valid existing devices (in /\/dev\/input/).--- If a device can't be initialised for an individual path, then the exception is printed,--- and the function continues to try to initialise the others.-allDevices :: (IsStream t, Monad (t IO)) => t IO Device-allDevices =- let paths = S.filterM doesFileExist $ S.map (evdevDir </>) $ S.fromFoldable =<< S.yieldM (listDirectory evdevDir)- in S.mapMaybeM (printIOError' . newDevice) paths---- | All new devices created (in /\/dev\/input/).--- Watches for new file paths (using \inotify\), and those corresponding to valid devices are added to the stream.-newDevices :: (IsStream t, Monad (t IO)) => t IO Device-newDevices =- let -- 'watching' keeps track of the set of paths which have been added, but don't yet have the right permissions- watch :: Set (Path Absolute) -> N.Event -> IO (Maybe Device, Set (Path Absolute))- watch watching = \case- N.Added p _ NotDir ->- tryNewDevice p <&> \case- Right d -> -- success - return new device- (Just d, watching)- Left e -> -- fail - if it's only a permission error then watch for changes on device- (Nothing, applyWhen (isPermissionError e) (Set.insert p) watching)- N.Modified p _ NotDir ->- if p `elem` watching then- tryNewDevice p <&> \case- Right d -> -- success - no longer watch for changes- (Just d, Set.delete p watching)- Left _ -> -- fail - continue to watch- (Nothing, watching)- else -- this isn't an event we care about- return (Nothing, watching)- N.Removed p _ NotDir -> -- device is gone - no longer watch for changes- return (Nothing, Set.delete p watching)- _ -> return (Nothing, watching)- tryNewDevice = printIOError . newDevice . BS.pack . toFilePath- in do- (_,es) <- S.yieldM $ watchDirectory (fromFilePath $ BS.unpack evdevDir) (EventPredicate $ const True)- scanMaybe watch [] es---{- Util -}---- specialized form of S.scanlM'--- for each a, f updates s, and possibly produces a new b, to add to the output stream--- I really can't think of a good name for this...--- TODO perhaps some way to use State monad instead?-scanMaybe :: (IsStream t, Monad m) => (s -> a -> m (Maybe b, s)) -> s -> t m a -> t m b-scanMaybe f e = S.mapMaybe fst . S.scanlM' (f . snd) (Nothing, e)---- specialised form of S.unfoldrM--- this should perhaps be in streamly (it's in monad-loops)---TODO this is rather ugly - can it be done in terms of the Unfold type?-unfoldM :: (IsStream t, MonadAsync m) => m (Maybe a) -> t m a-unfoldM x = S.unfoldrM (const $ fmap (,undefined) <$> x) undefined---- like tryIOError, but also prints the error to stderr-printIOError :: IO a -> IO (Either IOError a)-printIOError f = (Right <$> f) `catchIOError` \err -> do- hPrint stderr err- return $ Left err---- variant of printIOError which doesn't care what the exception was-printIOError' :: IO a -> IO (Maybe a)-printIOError' = fmap eitherToMaybe . printIOError---- apply the function iff the guard passes-applyWhen :: Bool -> (a -> a) -> a -> a-applyWhen = flip $ bool id