evdev (empty) → 0.1.0.0
raw patch · 7 files changed
+1043/−0 lines, 7 filesdep +basedep +bytestringdep +compositionsetup-changed
Dependencies added: base, bytestring, composition, containers, either, extra, hinotify, posix-paths, process, rawfilepath, streamly, time, unix
Files
- CHANGELOG.md +5/−0
- Setup.hs +2/−0
- evdev.cabal +48/−0
- src/Evdev.hs +149/−0
- src/Evdev/Codes.chs +689/−0
- src/Evdev/LowLevel.chs +83/−0
- src/Evdev/Stream.hs +67/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for evdev++## 0.1.0.0 -- 2019-07-20++* First version. Released on an unsuspecting world.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ evdev.cabal view
@@ -0,0 +1,48 @@+cabal-version: 2.4+name: evdev+version: 0.1.0.0+author: George Thomas+maintainer: George Thomas+description: Allows use of the linux event device interface, with an optional high-level Streamly API.+synopsis: Bindings to libevdev+license: BSD-3-Clause+category: System+extra-source-files: CHANGELOG.md++source-repository head+ type: git+ location: git://github.com/georgefst/evdev.git++library+ exposed-modules: Evdev+ Evdev.Codes+ Evdev.LowLevel+ Evdev.Stream+ hs-source-dirs: src+ ghc-options: -Wall+ build-depends: base >= 4.13.0 && < 4.14,+ bytestring >= 0.10.9 && < 0.11,+ composition >= 1.0.2 && < 1.1,+ containers >= 0.6.2 && < 0.7,+ either >= 5.0.1 && < 5.1,+ time >= 1.9.3 && < 1.10,+ unix >= 2.7.2 && < 2.8,+ process >= 1.6.5 && < 1.7,+ extra >= 1.6.18 && < 1.7,+ hinotify >= 0.4 && < 0.5,+ posix-paths >= 0.2.1 && < 0.3,+ rawfilepath >= 0.2.4 && < 0.3,+ streamly >= 0.6.1 && < 0.7,+ build-tool-depends: c2hs:c2hs+ default-language: Haskell2010+ default-extensions: FlexibleContexts+ GeneralizedNewtypeDeriving+ LambdaCase+ OverloadedLists+ OverloadedStrings+ PatternSynonyms+ TupleSections+ ViewPatterns+ includes: libevdev-1.0/libevdev/libevdev.h+ linux/input.h+ extra-libraries: evdev
+ src/Evdev.hs view
@@ -0,0 +1,149 @@+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
+ src/Evdev/Codes.chs view
@@ -0,0 +1,689 @@+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) #}++{#enum define EventType {+ EV_SYN as EvSyn,+ EV_KEY as EvKey,+ EV_REL as EvRel,+ EV_ABS as EvAbs,+ EV_MSC as EvMsc,+ EV_SW as EvSw,+ EV_LED as EvLed,+ EV_SND as EvSnd,+ EV_REP as EvRep,+ EV_FF as EvFf,+ EV_PWR as EvPwr,+ EV_FF_STATUS as EvFfStatus}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define SyncEventType {+ SYN_REPORT as SynReport,+ SYN_CONFIG as SynConfig,+ SYN_MT_REPORT as SynMtReport,+ SYN_DROPPED as SynDropped}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define Key {+ KEY_RESERVED as KeyReserved,+ KEY_ESC as KeyEsc,+ KEY_1 as Key1,+ KEY_2 as Key2,+ KEY_3 as Key3,+ KEY_4 as Key4,+ KEY_5 as Key5,+ KEY_6 as Key6,+ KEY_7 as Key7,+ KEY_8 as Key8,+ KEY_9 as Key9,+ KEY_0 as Key0,+ KEY_MINUS as KeyMinus,+ KEY_EQUAL as KeyEqual,+ KEY_BACKSPACE as KeyBackspace,+ KEY_TAB as KeyTab,+ KEY_Q as KeyQ,+ KEY_W as KeyW,+ KEY_E as KeyE,+ KEY_R as KeyR,+ KEY_T as KeyT,+ KEY_Y as KeyY,+ KEY_U as KeyU,+ KEY_I as KeyI,+ KEY_O as KeyO,+ KEY_P as KeyP,+ KEY_LEFTBRACE as KeyLeftbrace,+ KEY_RIGHTBRACE as KeyRightbrace,+ KEY_ENTER as KeyEnter,+ KEY_LEFTCTRL as KeyLeftctrl,+ KEY_A as KeyA,+ KEY_S as KeyS,+ KEY_D as KeyD,+ KEY_F as KeyF,+ KEY_G as KeyG,+ KEY_H as KeyH,+ KEY_J as KeyJ,+ KEY_K as KeyK,+ KEY_L as KeyL,+ KEY_SEMICOLON as KeySemicolon,+ KEY_APOSTROPHE as KeyApostrophe,+ KEY_GRAVE as KeyGrave,+ KEY_LEFTSHIFT as KeyLeftshift,+ KEY_BACKSLASH as KeyBackslash,+ KEY_Z as KeyZ,+ KEY_X as KeyX,+ KEY_C as KeyC,+ KEY_V as KeyV,+ KEY_B as KeyB,+ KEY_N as KeyN,+ KEY_M as KeyM,+ KEY_COMMA as KeyComma,+ KEY_DOT as KeyDot,+ KEY_SLASH as KeySlash,+ KEY_RIGHTSHIFT as KeyRightshift,+ KEY_KPASTERISK as KeyKpasterisk,+ KEY_LEFTALT as KeyLeftalt,+ KEY_SPACE as KeySpace,+ KEY_CAPSLOCK as KeyCapslock,+ KEY_F1 as KeyF1,+ KEY_F2 as KeyF2,+ KEY_F3 as KeyF3,+ KEY_F4 as KeyF4,+ KEY_F5 as KeyF5,+ KEY_F6 as KeyF6,+ KEY_F7 as KeyF7,+ KEY_F8 as KeyF8,+ KEY_F9 as KeyF9,+ KEY_F10 as KeyF10,+ KEY_NUMLOCK as KeyNumlock,+ KEY_SCROLLLOCK as KeyScrolllock,+ KEY_KP7 as KeyKp7,+ KEY_KP8 as KeyKp8,+ KEY_KP9 as KeyKp9,+ KEY_KPMINUS as KeyKpminus,+ KEY_KP4 as KeyKp4,+ KEY_KP5 as KeyKp5,+ KEY_KP6 as KeyKp6,+ KEY_KPPLUS as KeyKpplus,+ KEY_KP1 as KeyKp1,+ KEY_KP2 as KeyKp2,+ KEY_KP3 as KeyKp3,+ KEY_KP0 as KeyKp0,+ KEY_KPDOT as KeyKpdot,+ KEY_ZENKAKUHANKAKU as KeyZenkakuhankaku,+ KEY_102ND as Key102nd,+ KEY_F11 as KeyF11,+ KEY_F12 as KeyF12,+ KEY_RO as KeyRo,+ KEY_KATAKANA as KeyKatakana,+ KEY_HIRAGANA as KeyHiragana,+ KEY_HENKAN as KeyHenkan,+ KEY_KATAKANAHIRAGANA as KeyKatakanahiragana,+ KEY_MUHENKAN as KeyMuhenkan,+ KEY_KPJPCOMMA as KeyKpjpcomma,+ KEY_KPENTER as KeyKpenter,+ KEY_RIGHTCTRL as KeyRightctrl,+ KEY_KPSLASH as KeyKpslash,+ KEY_SYSRQ as KeySysrq,+ KEY_RIGHTALT as KeyRightalt,+ KEY_LINEFEED as KeyLinefeed,+ KEY_HOME as KeyHome,+ KEY_UP as KeyUp,+ KEY_PAGEUP as KeyPageup,+ KEY_LEFT as KeyLeft,+ KEY_RIGHT as KeyRight,+ KEY_END as KeyEnd,+ KEY_DOWN as KeyDown,+ KEY_PAGEDOWN as KeyPagedown,+ KEY_INSERT as KeyInsert,+ KEY_DELETE as KeyDelete,+ KEY_MACRO as KeyMacro,+ KEY_MUTE as KeyMute,+ KEY_VOLUMEDOWN as KeyVolumedown,+ KEY_VOLUMEUP as KeyVolumeup,+ KEY_POWER as KeyPower,+ KEY_KPEQUAL as KeyKpequal,+ KEY_KPPLUSMINUS as KeyKpplusminus,+ KEY_PAUSE as KeyPause,+ KEY_SCALE as KeyScale,+ KEY_KPCOMMA as KeyKpcomma,+ KEY_HANGEUL as KeyHangeul,+ KEY_HANGUEL as KeyHanguel,+ KEY_HANJA as KeyHanja,+ KEY_YEN as KeyYen,+ KEY_LEFTMETA as KeyLeftmeta,+ KEY_RIGHTMETA as KeyRightmeta,+ KEY_COMPOSE as KeyCompose,+ KEY_STOP as KeyStop,+ KEY_AGAIN as KeyAgain,+ KEY_PROPS as KeyProps,+ KEY_UNDO as KeyUndo,+ KEY_FRONT as KeyFront,+ KEY_COPY as KeyCopy,+ KEY_OPEN as KeyOpen,+ KEY_PASTE as KeyPaste,+ KEY_FIND as KeyFind,+ KEY_CUT as KeyCut,+ KEY_HELP as KeyHelp,+ KEY_MENU as KeyMenu,+ KEY_CALC as KeyCalc,+ KEY_SETUP as KeySetup,+ KEY_SLEEP as KeySleep,+ KEY_WAKEUP as KeyWakeup,+ KEY_FILE as KeyFile,+ KEY_SENDFILE as KeySendfile,+ KEY_DELETEFILE as KeyDeletefile,+ KEY_XFER as KeyXfer,+ KEY_PROG1 as KeyProg1,+ KEY_PROG2 as KeyProg2,+ KEY_WWW as KeyWww,+ KEY_MSDOS as KeyMsdos,+ KEY_COFFEE as KeyCoffee,+ KEY_SCREENLOCK as KeyScreenlock,+ KEY_ROTATE_DISPLAY as KeyRotateDisplay,+ KEY_DIRECTION as KeyDirection,+ KEY_CYCLEWINDOWS as KeyCyclewindows,+ KEY_MAIL as KeyMail,+ KEY_BOOKMARKS as KeyBookmarks,+ KEY_COMPUTER as KeyComputer,+ KEY_BACK as KeyBack,+ KEY_FORWARD as KeyForward,+ KEY_CLOSECD as KeyClosecd,+ KEY_EJECTCD as KeyEjectcd,+ KEY_EJECTCLOSECD as KeyEjectclosecd,+ KEY_NEXTSONG as KeyNextsong,+ KEY_PLAYPAUSE as KeyPlaypause,+ KEY_PREVIOUSSONG as KeyPrevioussong,+ KEY_STOPCD as KeyStopcd,+ KEY_RECORD as KeyRecord,+ KEY_REWIND as KeyRewind,+ KEY_PHONE as KeyPhone,+ KEY_ISO as KeyIso,+ KEY_CONFIG as KeyConfig,+ KEY_HOMEPAGE as KeyHomepage,+ KEY_REFRESH as KeyRefresh,+ KEY_EXIT as KeyExit,+ KEY_MOVE as KeyMove,+ KEY_EDIT as KeyEdit,+ KEY_SCROLLUP as KeyScrollup,+ KEY_SCROLLDOWN as KeyScrolldown,+ KEY_KPLEFTPAREN as KeyKpleftparen,+ KEY_KPRIGHTPAREN as KeyKprightparen,+ KEY_NEW as KeyNew,+ KEY_REDO as KeyRedo,+ KEY_F13 as KeyF13,+ KEY_F14 as KeyF14,+ KEY_F15 as KeyF15,+ KEY_F16 as KeyF16,+ KEY_F17 as KeyF17,+ KEY_F18 as KeyF18,+ KEY_F19 as KeyF19,+ KEY_F20 as KeyF20,+ KEY_F21 as KeyF21,+ KEY_F22 as KeyF22,+ KEY_F23 as KeyF23,+ KEY_F24 as KeyF24,+ KEY_PLAYCD as KeyPlaycd,+ KEY_PAUSECD as KeyPausecd,+ KEY_PROG3 as KeyProg3,+ KEY_PROG4 as KeyProg4,+ KEY_DASHBOARD as KeyDashboard,+ KEY_SUSPEND as KeySuspend,+ KEY_CLOSE as KeyClose,+ KEY_PLAY as KeyPlay,+ KEY_FASTFORWARD as KeyFastforward,+ KEY_BASSBOOST as KeyBassboost,+ KEY_PRINT as KeyPrint,+ KEY_HP as KeyHp,+ KEY_CAMERA as KeyCamera,+ KEY_SOUND as KeySound,+ KEY_QUESTION as KeyQuestion,+ KEY_EMAIL as KeyEmail,+ KEY_CHAT as KeyChat,+ KEY_SEARCH as KeySearch,+ KEY_CONNECT as KeyConnect,+ KEY_FINANCE as KeyFinance,+ KEY_SPORT as KeySport,+ KEY_SHOP as KeyShop,+ KEY_ALTERASE as KeyAlterase,+ KEY_CANCEL as KeyCancel,+ KEY_BRIGHTNESSDOWN as KeyBrightnessdown,+ KEY_BRIGHTNESSUP as KeyBrightnessup,+ KEY_MEDIA as KeyMedia,+ KEY_SWITCHVIDEOMODE as KeySwitchvideomode,+ KEY_KBDILLUMTOGGLE as KeyKbdillumtoggle,+ KEY_KBDILLUMDOWN as KeyKbdillumdown,+ KEY_KBDILLUMUP as KeyKbdillumup,+ KEY_SEND as KeySend,+ KEY_REPLY as KeyReply,+ KEY_FORWARDMAIL as KeyForwardmail,+ KEY_SAVE as KeySave,+ KEY_DOCUMENTS as KeyDocuments,+ KEY_BATTERY as KeyBattery,+ KEY_BLUETOOTH as KeyBluetooth,+ KEY_WLAN as KeyWlan,+ KEY_UWB as KeyUwb,+ KEY_UNKNOWN as KeyUnknown,+ KEY_VIDEO_NEXT as KeyVideoNext,+ KEY_VIDEO_PREV as KeyVideoPrev,+ KEY_BRIGHTNESS_CYCLE as KeyBrightnessCycle,+ KEY_BRIGHTNESS_AUTO as KeyBrightnessAuto,+ KEY_BRIGHTNESS_ZERO as KeyBrightnessZero,+ KEY_DISPLAY_OFF as KeyDisplayOff,+ KEY_WWAN as KeyWwan,+ KEY_WIMAX as KeyWimax,+ KEY_RFKILL as KeyRfkill,+ KEY_MICMUTE as KeyMicmute,+ BTN_MISC as BtnMisc,+ BTN_0 as Btn0,+ BTN_1 as Btn1,+ BTN_2 as Btn2,+ BTN_3 as Btn3,+ BTN_4 as Btn4,+ BTN_5 as Btn5,+ BTN_6 as Btn6,+ BTN_7 as Btn7,+ BTN_8 as Btn8,+ BTN_9 as Btn9,+ BTN_MOUSE as BtnMouse,+ BTN_LEFT as BtnLeft,+ BTN_RIGHT as BtnRight,+ BTN_MIDDLE as BtnMiddle,+ BTN_SIDE as BtnSide,+ BTN_EXTRA as BtnExtra,+ BTN_FORWARD as BtnForward,+ BTN_BACK as BtnBack,+ BTN_TASK as BtnTask,+ BTN_JOYSTICK as BtnJoystick,+ BTN_TRIGGER as BtnTrigger,+ BTN_THUMB as BtnThumb,+ BTN_THUMB2 as BtnThumb2,+ BTN_TOP as BtnTop,+ BTN_TOP2 as BtnTop2,+ BTN_PINKIE as BtnPinkie,+ BTN_BASE as BtnBase,+ BTN_BASE2 as BtnBase2,+ BTN_BASE3 as BtnBase3,+ BTN_BASE4 as BtnBase4,+ BTN_BASE5 as BtnBase5,+ BTN_BASE6 as BtnBase6,+ BTN_DEAD as BtnDead,+ BTN_GAMEPAD as BtnGamepad,+ BTN_SOUTH as BtnSouth,+ BTN_A as BtnA,+ BTN_EAST as BtnEast,+ BTN_B as BtnB,+ BTN_C as BtnC,+ BTN_NORTH as BtnNorth,+ BTN_X as BtnX,+ BTN_WEST as BtnWest,+ BTN_Y as BtnY,+ BTN_Z as BtnZ,+ BTN_TL as BtnTl,+ BTN_TR as BtnTr,+ BTN_TL2 as BtnTl2,+ BTN_TR2 as BtnTr2,+ BTN_SELECT as BtnSelect,+ BTN_START as BtnStart,+ BTN_MODE as BtnMode,+ BTN_THUMBL as BtnThumbl,+ BTN_THUMBR as BtnThumbr,+ BTN_DIGI as BtnDigi,+ BTN_TOOL_PEN as BtnToolPen,+ BTN_TOOL_RUBBER as BtnToolRubber,+ BTN_TOOL_BRUSH as BtnToolBrush,+ BTN_TOOL_PENCIL as BtnToolPencil,+ BTN_TOOL_AIRBRUSH as BtnToolAirbrush,+ BTN_TOOL_FINGER as BtnToolFinger,+ BTN_TOOL_MOUSE as BtnToolMouse,+ BTN_TOOL_LENS as BtnToolLens,+ BTN_TOOL_QUINTTAP as BtnToolQuinttap,+ BTN_TOUCH as BtnTouch,+ BTN_STYLUS as BtnStylus,+ BTN_STYLUS2 as BtnStylus2,+ BTN_TOOL_DOUBLETAP as BtnToolDoubletap,+ BTN_TOOL_TRIPLETAP as BtnToolTripletap,+ BTN_TOOL_QUADTAP as BtnToolQuadtap,+ BTN_WHEEL as BtnWheel,+ BTN_GEAR_DOWN as BtnGearDown,+ BTN_GEAR_UP as BtnGearUp,+ KEY_OK as KeyOk,+ KEY_SELECT as KeySelect,+ KEY_GOTO as KeyGoto,+ KEY_CLEAR as KeyClear,+ KEY_POWER2 as KeyPower2,+ KEY_OPTION as KeyOption,+ KEY_INFO as KeyInfo,+ KEY_TIME as KeyTime,+ KEY_VENDOR as KeyVendor,+ KEY_ARCHIVE as KeyArchive,+ KEY_PROGRAM as KeyProgram,+ KEY_CHANNEL as KeyChannel,+ KEY_FAVORITES as KeyFavorites,+ KEY_EPG as KeyEpg,+ KEY_PVR as KeyPvr,+ KEY_MHP as KeyMhp,+ KEY_LANGUAGE as KeyLanguage,+ KEY_TITLE as KeyTitle,+ KEY_SUBTITLE as KeySubtitle,+ KEY_ANGLE as KeyAngle,+ KEY_ZOOM as KeyZoom,+ KEY_MODE as KeyMode,+ KEY_KEYBOARD as KeyKeyboard,+ KEY_SCREEN as KeyScreen,+ KEY_PC as KeyPc,+ KEY_TV as KeyTv,+ KEY_TV2 as KeyTv2,+ KEY_VCR as KeyVcr,+ KEY_VCR2 as KeyVcr2,+ KEY_SAT as KeySat,+ KEY_SAT2 as KeySat2,+ KEY_CD as KeyCd,+ KEY_TAPE as KeyTape,+ KEY_RADIO as KeyRadio,+ KEY_TUNER as KeyTuner,+ KEY_PLAYER as KeyPlayer,+ KEY_TEXT as KeyText,+ KEY_DVD as KeyDvd,+ KEY_AUX as KeyAux,+ KEY_MP3 as KeyMp3,+ KEY_AUDIO as KeyAudio,+ KEY_VIDEO as KeyVideo,+ KEY_DIRECTORY as KeyDirectory,+ KEY_LIST as KeyList,+ KEY_MEMO as KeyMemo,+ KEY_CALENDAR as KeyCalendar,+ KEY_RED as KeyRed,+ KEY_GREEN as KeyGreen,+ KEY_YELLOW as KeyYellow,+ KEY_BLUE as KeyBlue,+ KEY_CHANNELUP as KeyChannelup,+ KEY_CHANNELDOWN as KeyChanneldown,+ KEY_FIRST as KeyFirst,+ KEY_LAST as KeyLast,+ KEY_AB as KeyAb,+ KEY_NEXT as KeyNext,+ KEY_RESTART as KeyRestart,+ KEY_SLOW as KeySlow,+ KEY_SHUFFLE as KeyShuffle,+ KEY_BREAK as KeyBreak,+ KEY_PREVIOUS as KeyPrevious,+ KEY_DIGITS as KeyDigits,+ KEY_TEEN as KeyTeen,+ KEY_TWEN as KeyTwen,+ KEY_VIDEOPHONE as KeyVideophone,+ KEY_GAMES as KeyGames,+ KEY_ZOOMIN as KeyZoomin,+ KEY_ZOOMOUT as KeyZoomout,+ KEY_ZOOMRESET as KeyZoomreset,+ KEY_WORDPROCESSOR as KeyWordprocessor,+ KEY_EDITOR as KeyEditor,+ KEY_SPREADSHEET as KeySpreadsheet,+ KEY_GRAPHICSEDITOR as KeyGraphicseditor,+ KEY_PRESENTATION as KeyPresentation,+ KEY_DATABASE as KeyDatabase,+ KEY_NEWS as KeyNews,+ KEY_VOICEMAIL as KeyVoicemail,+ KEY_ADDRESSBOOK as KeyAddressbook,+ KEY_MESSENGER as KeyMessenger,+ KEY_DISPLAYTOGGLE as KeyDisplaytoggle,+ KEY_BRIGHTNESS_TOGGLE as KeyBrightnessToggle,+ KEY_SPELLCHECK as KeySpellcheck,+ KEY_LOGOFF as KeyLogoff,+ KEY_DOLLAR as KeyDollar,+ KEY_EURO as KeyEuro,+ KEY_FRAMEBACK as KeyFrameback,+ KEY_FRAMEFORWARD as KeyFrameforward,+ KEY_CONTEXT_MENU as KeyContextMenu,+ KEY_MEDIA_REPEAT as KeyMediaRepeat,+ KEY_10CHANNELSUP as Key10channelsup,+ KEY_10CHANNELSDOWN as Key10channelsdown,+ KEY_IMAGES as KeyImages,+ KEY_DEL_EOL as KeyDelEol,+ KEY_DEL_EOS as KeyDelEos,+ KEY_INS_LINE as KeyInsLine,+ KEY_DEL_LINE as KeyDelLine,+ KEY_FN as KeyFn,+ KEY_FN_ESC as KeyFnEsc,+ KEY_FN_F1 as KeyFnF1,+ KEY_FN_F2 as KeyFnF2,+ KEY_FN_F3 as KeyFnF3,+ KEY_FN_F4 as KeyFnF4,+ KEY_FN_F5 as KeyFnF5,+ KEY_FN_F6 as KeyFnF6,+ KEY_FN_F7 as KeyFnF7,+ KEY_FN_F8 as KeyFnF8,+ KEY_FN_F9 as KeyFnF9,+ KEY_FN_F10 as KeyFnF10,+ KEY_FN_F11 as KeyFnF11,+ KEY_FN_F12 as KeyFnF12,+ KEY_FN_1 as KeyFn1,+ KEY_FN_2 as KeyFn2,+ KEY_FN_D as KeyFnD,+ KEY_FN_E as KeyFnE,+ KEY_FN_F as KeyFnF,+ KEY_FN_S as KeyFnS,+ KEY_FN_B as KeyFnB,+ KEY_BRL_DOT1 as KeyBrlDot1,+ KEY_BRL_DOT2 as KeyBrlDot2,+ KEY_BRL_DOT3 as KeyBrlDot3,+ KEY_BRL_DOT4 as KeyBrlDot4,+ KEY_BRL_DOT5 as KeyBrlDot5,+ KEY_BRL_DOT6 as KeyBrlDot6,+ KEY_BRL_DOT7 as KeyBrlDot7,+ KEY_BRL_DOT8 as KeyBrlDot8,+ KEY_BRL_DOT9 as KeyBrlDot9,+ KEY_BRL_DOT10 as KeyBrlDot10,+ KEY_NUMERIC_0 as KeyNumeric0,+ KEY_NUMERIC_1 as KeyNumeric1,+ KEY_NUMERIC_2 as KeyNumeric2,+ KEY_NUMERIC_3 as KeyNumeric3,+ KEY_NUMERIC_4 as KeyNumeric4,+ KEY_NUMERIC_5 as KeyNumeric5,+ KEY_NUMERIC_6 as KeyNumeric6,+ KEY_NUMERIC_7 as KeyNumeric7,+ KEY_NUMERIC_8 as KeyNumeric8,+ KEY_NUMERIC_9 as KeyNumeric9,+ KEY_NUMERIC_STAR as KeyNumericStar,+ KEY_NUMERIC_POUND as KeyNumericPound,+ KEY_NUMERIC_A as KeyNumericA,+ KEY_NUMERIC_B as KeyNumericB,+ KEY_NUMERIC_C as KeyNumericC,+ KEY_NUMERIC_D as KeyNumericD,+ KEY_CAMERA_FOCUS as KeyCameraFocus,+ KEY_WPS_BUTTON as KeyWpsButton,+ KEY_TOUCHPAD_TOGGLE as KeyTouchpadToggle,+ KEY_TOUCHPAD_ON as KeyTouchpadOn,+ KEY_TOUCHPAD_OFF as KeyTouchpadOff,+ KEY_CAMERA_ZOOMIN as KeyCameraZoomin,+ KEY_CAMERA_ZOOMOUT as KeyCameraZoomout,+ KEY_CAMERA_UP as KeyCameraUp,+ KEY_CAMERA_DOWN as KeyCameraDown,+ KEY_CAMERA_LEFT as KeyCameraLeft,+ KEY_CAMERA_RIGHT as KeyCameraRight,+ KEY_ATTENDANT_ON as KeyAttendantOn,+ KEY_ATTENDANT_OFF as KeyAttendantOff,+ KEY_ATTENDANT_TOGGLE as KeyAttendantToggle,+ KEY_LIGHTS_TOGGLE as KeyLightsToggle,+ BTN_DPAD_UP as BtnDpadUp,+ BTN_DPAD_DOWN as BtnDpadDown,+ BTN_DPAD_LEFT as BtnDpadLeft,+ BTN_DPAD_RIGHT as BtnDpadRight,+ KEY_ALS_TOGGLE as KeyAlsToggle,+ KEY_BUTTONCONFIG as KeyButtonconfig,+ KEY_TASKMANAGER as KeyTaskmanager,+ KEY_JOURNAL as KeyJournal,+ KEY_CONTROLPANEL as KeyControlpanel,+ KEY_APPSELECT as KeyAppselect,+ KEY_SCREENSAVER as KeyScreensaver,+ KEY_VOICECOMMAND as KeyVoicecommand,+ KEY_BRIGHTNESS_MIN as KeyBrightnessMin,+ KEY_BRIGHTNESS_MAX as KeyBrightnessMax,+ KEY_KBDINPUTASSIST_PREV as KeyKbdinputassistPrev,+ KEY_KBDINPUTASSIST_NEXT as KeyKbdinputassistNext,+ KEY_KBDINPUTASSIST_PREVGROUP as KeyKbdinputassistPrevgroup,+ KEY_KBDINPUTASSIST_NEXTGROUP as KeyKbdinputassistNextgroup,+ KEY_KBDINPUTASSIST_ACCEPT as KeyKbdinputassistAccept,+ KEY_KBDINPUTASSIST_CANCEL as KeyKbdinputassistCancel,+ BTN_TRIGGER_HAPPY as BtnTriggerHappy,+ BTN_TRIGGER_HAPPY1 as BtnTriggerHappy1,+ BTN_TRIGGER_HAPPY2 as BtnTriggerHappy2,+ BTN_TRIGGER_HAPPY3 as BtnTriggerHappy3,+ BTN_TRIGGER_HAPPY4 as BtnTriggerHappy4,+ BTN_TRIGGER_HAPPY5 as BtnTriggerHappy5,+ BTN_TRIGGER_HAPPY6 as BtnTriggerHappy6,+ BTN_TRIGGER_HAPPY7 as BtnTriggerHappy7,+ BTN_TRIGGER_HAPPY8 as BtnTriggerHappy8,+ BTN_TRIGGER_HAPPY9 as BtnTriggerHappy9,+ BTN_TRIGGER_HAPPY10 as BtnTriggerHappy10,+ BTN_TRIGGER_HAPPY11 as BtnTriggerHappy11,+ BTN_TRIGGER_HAPPY12 as BtnTriggerHappy12,+ BTN_TRIGGER_HAPPY13 as BtnTriggerHappy13,+ BTN_TRIGGER_HAPPY14 as BtnTriggerHappy14,+ BTN_TRIGGER_HAPPY15 as BtnTriggerHappy15,+ BTN_TRIGGER_HAPPY16 as BtnTriggerHappy16,+ BTN_TRIGGER_HAPPY17 as BtnTriggerHappy17,+ BTN_TRIGGER_HAPPY18 as BtnTriggerHappy18,+ BTN_TRIGGER_HAPPY19 as BtnTriggerHappy19,+ BTN_TRIGGER_HAPPY20 as BtnTriggerHappy20,+ BTN_TRIGGER_HAPPY21 as BtnTriggerHappy21,+ BTN_TRIGGER_HAPPY22 as BtnTriggerHappy22,+ BTN_TRIGGER_HAPPY23 as BtnTriggerHappy23,+ BTN_TRIGGER_HAPPY24 as BtnTriggerHappy24,+ BTN_TRIGGER_HAPPY25 as BtnTriggerHappy25,+ BTN_TRIGGER_HAPPY26 as BtnTriggerHappy26,+ BTN_TRIGGER_HAPPY27 as BtnTriggerHappy27,+ BTN_TRIGGER_HAPPY28 as BtnTriggerHappy28,+ BTN_TRIGGER_HAPPY29 as BtnTriggerHappy29,+ BTN_TRIGGER_HAPPY30 as BtnTriggerHappy30,+ BTN_TRIGGER_HAPPY31 as BtnTriggerHappy31,+ BTN_TRIGGER_HAPPY32 as BtnTriggerHappy32,+ BTN_TRIGGER_HAPPY33 as BtnTriggerHappy33,+ BTN_TRIGGER_HAPPY34 as BtnTriggerHappy34,+ BTN_TRIGGER_HAPPY35 as BtnTriggerHappy35,+ BTN_TRIGGER_HAPPY36 as BtnTriggerHappy36,+ BTN_TRIGGER_HAPPY37 as BtnTriggerHappy37,+ BTN_TRIGGER_HAPPY38 as BtnTriggerHappy38,+ BTN_TRIGGER_HAPPY39 as BtnTriggerHappy39,+ BTN_TRIGGER_HAPPY40 as BtnTriggerHappy40}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define RelativeAxis {+ REL_X as RelX,+ REL_Y as RelY,+ REL_Z as RelZ,+ REL_RX as RelRx,+ REL_RY as RelRy,+ REL_RZ as RelRz,+ REL_HWHEEL as RelHwheel,+ REL_DIAL as RelDial,+ REL_WHEEL as RelWheel,+ REL_MISC as RelMisc}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define AbsoluteAxis {+ ABS_X as AbsX,+ ABS_Y as AbsY,+ ABS_Z as AbsZ,+ ABS_RX as AbsRx,+ ABS_RY as AbsRy,+ ABS_RZ as AbsRz,+ ABS_THROTTLE as AbsThrottle,+ ABS_RUDDER as AbsRudder,+ ABS_WHEEL as AbsWheel,+ ABS_GAS as AbsGas,+ ABS_BRAKE as AbsBrake,+ ABS_HAT0X as AbsHat0x,+ ABS_HAT0Y as AbsHat0y,+ ABS_HAT1X as AbsHat1x,+ ABS_HAT1Y as AbsHat1y,+ ABS_HAT2X as AbsHat2x,+ ABS_HAT2Y as AbsHat2y,+ ABS_HAT3X as AbsHat3x,+ ABS_HAT3Y as AbsHat3y,+ ABS_PRESSURE as AbsPressure,+ ABS_DISTANCE as AbsDistance,+ ABS_TILT_X as AbsTiltX,+ ABS_TILT_Y as AbsTiltY,+ ABS_TOOL_WIDTH as AbsToolWidth,+ ABS_VOLUME as AbsVolume,+ ABS_MISC as AbsMisc,+ ABS_RESERVED as AbsReserved,+ ABS_MT_SLOT as AbsMtSlot,+ ABS_MT_TOUCH_MAJOR as AbsMtTouchMajor,+ ABS_MT_TOUCH_MINOR as AbsMtTouchMinor,+ ABS_MT_WIDTH_MAJOR as AbsMtWidthMajor,+ ABS_MT_WIDTH_MINOR as AbsMtWidthMinor,+ ABS_MT_ORIENTATION as AbsMtOrientation,+ ABS_MT_POSITION_X as AbsMtPositionX,+ ABS_MT_POSITION_Y as AbsMtPositionY,+ ABS_MT_TOOL_TYPE as AbsMtToolType,+ ABS_MT_BLOB_ID as AbsMtBlobId,+ ABS_MT_TRACKING_ID as AbsMtTrackingId,+ ABS_MT_PRESSURE as AbsMtPressure,+ ABS_MT_DISTANCE as AbsMtDistance,+ ABS_MT_TOOL_X as AbsMtToolX,+ ABS_MT_TOOL_Y as AbsMtToolY}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define SwitchEventType {+ SW_LID as SwLid,+ SW_TABLET_MODE as SwTabletMode,+ SW_HEADPHONE_INSERT as SwHeadphoneInsert,+ SW_RFKILL_ALL as SwRfkillAll,+ SW_RADIO as SwRadio,+ SW_MICROPHONE_INSERT as SwMicrophoneInsert,+ SW_DOCK as SwDock,+ SW_LINEOUT_INSERT as SwLineoutInsert,+ SW_JACK_PHYSICAL_INSERT as SwJackPhysicalInsert,+ SW_VIDEOOUT_INSERT as SwVideooutInsert,+ SW_CAMERA_LENS_COVER as SwCameraLensCover,+ SW_KEYPAD_SLIDE as SwKeypadSlide,+ SW_FRONT_PROXIMITY as SwFrontProximity,+ SW_ROTATE_LOCK as SwRotateLock,+ SW_LINEIN_INSERT as SwLineinInsert,+ SW_MUTE_DEVICE as SwMuteDevice}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define MiscEventType {+ MSC_SERIAL as MscSerial,+ MSC_PULSELED as MscPulseled,+ MSC_GESTURE as MscGesture,+ MSC_RAW as MscRaw,+ MSC_SCAN as MscScan,+ MSC_TIMESTAMP as MscTimestamp}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define LEDEventType {+ LED_NUML as LedNuml,+ LED_CAPSL as LedCapsl,+ LED_SCROLLL as LedScrolll,+ LED_COMPOSE as LedCompose,+ LED_KANA as LedKana,+ LED_SLEEP as LedSleep,+ LED_SUSPEND as LedSuspend,+ LED_MUTE as LedMute,+ LED_MISC as LedMisc,+ LED_MAIL as LedMail,+ LED_CHARGING as LedCharging}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define RepeatEventType {+ REP_DELAY as RepDelay,+ REP_PERIOD as RepPeriod}+ deriving (Bounded, Eq, Ord, Read, Show) #}++{#enum define SoundEventType {+ SND_CLICK as SndClick,+ SND_BELL as SndBell,+ SND_TONE as SndTone}+ deriving (Bounded, Eq, Ord, Read, Show) #}
+ src/Evdev/LowLevel.chs view
@@ -0,0 +1,83 @@+module Evdev.LowLevel where++import Data.Int+import Data.Time.Clock++import Foreign (Ptr)+import Foreign.C (CInt,CUInt,CLong)+import Foreign.C.Error (Errno(Errno))+import Foreign.ForeignPtr (mallocForeignPtrBytes,withForeignPtr)+import Foreign.Storable (peekByteOff)+import System.Posix.ByteString (RawFilePath)+import System.Posix.IO.ByteString (OpenMode(ReadOnly),defaultFileFlags,openFd)+import System.Posix.Types (Fd(Fd))++#include <libevdev-1.0/libevdev/libevdev.h>+#include <linux/input.h>++{#enum libevdev_read_flag as ReadFlags {+ LIBEVDEV_READ_FLAG_SYNC as Sync,+ LIBEVDEV_READ_FLAG_NORMAL as Normal,+ LIBEVDEV_READ_FLAG_FORCE_SYNC as ForceSync,+ LIBEVDEV_READ_FLAG_BLOCKING as Blocking }+ deriving (Eq,Ord,Show) #}++{#enum libevdev_grab_mode as GrabMode { underscoreToCase } deriving (Show) #}++{#pointer *input_event as Event foreign newtype#}++{#pointer *timeval as Time foreign newtype#}++{#pointer *libevdev as Device newtype #}+++{- Conversions -}++convertEvent :: Event -> IO (Int,Int16,Int32,DiffTime)+convertEvent ev = (,,,)+ <$> getIntField {#get input_event->type #}+ <*> getIntField {#get input_event->code #}+ <*> getIntField {#get input_event->value #}+ <*> withEvent ev getTime+ where+ convertTime s us = picosecondsToDiffTime $ 1000000000000 * fromIntegral s + 1000000 * fromIntegral us+ getIntField :: (Integral a,Integral b) => (Ptr Event -> IO a) -> IO b+ getIntField f = withEvent ev (fmap fromIntegral . f)+ getTime :: Ptr Event -> IO DiffTime+ getTime ptr =+ let sec, usec :: IO CLong+ sec = peekByteOff ptr 0+ usec = peekByteOff ptr {#sizeof __kernel_time_t #}+ in convertTime <$> sec <*> usec++nextEvent :: Device -> CUInt -> IO (Errno, Event)+nextEvent dev flags = do+ ptr <- mallocForeignPtrBytes {#sizeof input_event #}+ err <- withForeignPtr ptr $ {#call libevdev_next_event #} dev flags+ return (Errno err, Event ptr)++{#fun libevdev_grab { `Device', `GrabMode' } -> `CInt' #}+grabDevice :: Device -> GrabMode -> IO (Errno, ())+grabDevice dev mode = do+ err <- libevdev_grab dev mode+ return (Errno err, ())++newDevice :: RawFilePath -> IO (Errno, Device)+newDevice path = do+ Fd n <- openFd path ReadOnly Nothing $ defaultFileFlags+ dev <- {#call libevdev_new #}+ err <- {#call libevdev_set_fd #} dev n+ return (Errno err, dev)+++{- Simpler functions -}++{#fun libevdev_get_fd as deviceFd { `Device' } -> `Fd' Fd #}+{#fun libevdev_get_name as deviceName { `Device' } -> `String' #}+--TODO should really be ByteString+++{- Util -}++convertEnum :: (Enum a, Integral b) => a -> b+convertEnum = fromIntegral . fromEnum
+ src/Evdev/Stream.hs view
@@ -0,0 +1,67 @@+module Evdev.Stream where++import Control.Concurrent+import Control.Exception.Extra+import Control.Monad+import Control.Monad.IO.Class+import Data.Either.Combinators++import GHC.IO.Exception (IOErrorType(PermissionDenied),ioe_type)+import RawFilePath.Directory (doesFileExist,listDirectory)+import System.INotify (addWatch,initINotify)+import qualified System.INotify as I+import System.Posix.ByteString (RawFilePath)+import System.Posix.FilePath ((</>),combine)++import Streamly+import qualified Streamly.Prelude as S++import Evdev++allEvents :: IsStream t => t IO (Device, Event)+allEvents = filteredEvents $ const True++filteredEvents :: IsStream t => (Device -> Bool) -> t IO (Device, Event)+filteredEvents p = readEventsMany $ S.filter p $ makeDevices $ existingDevicePaths <> newDevicePaths++-- reads until encountering an IOException+readEvents :: IsStream t => Device -> t IO Event+readEvents dev = serially $ unfoldrM' $ fmap rightToMaybe $ tryIO $ nextEvent dev defaultReadFlags++readEventsMany :: IsStream t => AsyncT IO Device -> t IO (Device, Event)+readEventsMany = asyncly . join . (S.map $ \d -> (S.map (d,) $ readEvents d))++makeDevices :: (Functor (t IO), IsStream t) => t IO RawFilePath -> t IO Device+makeDevices = S.mapMaybeM maybeNewDevice++existingDevicePaths :: (IsStream t, MonadIO (t IO)) => t IO RawFilePath+existingDevicePaths = S.fromFoldable =<< S.yieldM (lsFiles evdevDir)++newDevicePaths :: (MonadIO (t IO), IsStream t) => t IO RawFilePath+newDevicePaths =+ let watcher mvar = \case+ I.Created False path -> do -- file (not directory) created+ let fullPath = evdevDir </> path+ handleBoolRetry ((== PermissionDenied) . ioe_type) 100 $ putMVar mvar fullPath+ _ -> return ()+ in do+ mvar <- liftIO newEmptyMVar+ iNot <- liftIO initINotify+ _ <- liftIO $ addWatch iNot [I.Create] evdevDir (watcher mvar)+ serially $ S.repeatM $ takeMVar mvar+++-- retry the action after encountering an exception satisfying p+handleBoolRetry :: Exception e => (e -> Bool) -> Int -> IO a -> IO a+handleBoolRetry p t x = handleBool p (const $ threadDelay t >> handleBoolRetry p t x) x+++{- Util -}++-- a specialization of S.unfoldrM which doesn't make use of any value from the previous round+unfoldrM' :: (IsStream t, MonadAsync m) => m (Maybe a) -> t m a+unfoldrM' x = S.unfoldrM (const $ fmap (,undefined) <$> x) undefined++-- lists files only, and returns full paths.+lsFiles :: RawFilePath -> IO [RawFilePath]+lsFiles = filterM doesFileExist <=< ((fmap . map . combine) <*> listDirectory)