usb-hid (empty) → 0.1.0.0
raw patch · 6 files changed
+649/−0 lines, 6 filesdep +attoparsecdep +basedep +bytestringsetup-changed
Dependencies added: attoparsec, base, bytestring, usb
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- System/USB/HID/Descriptor.hs +202/−0
- System/USB/HID/Parsers.hs +252/−0
- System/USB/HID/Requests.hs +138/−0
- usb-hid.cabal +25/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Paul Bennett++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Paul Bennett nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/USB/HID/Descriptor.hs view
@@ -0,0 +1,202 @@+{-# LANGUAGE DeriveGeneric , AutoDeriveTypeable #-}+-- | Notice that 'LongItem's are not supported as none are specified on the <http://www.usb.org/developers/hidpage/ USB Human Interface device page>+module System.USB.HID.Descriptor where +++import Data.ByteString as B+import Data.Word +import GHC.Generics++data HIDDescriptor = HIDDescriptor + {hIDDescriptorType :: !Word8,+ hIDcdHID :: !Version,+ hIDCountryCode :: !Word8,+ hIDNumDesc :: !Word8,+ hIDDescType :: !Word8,+ hIDDescLength :: !Int,+ hIDDescType' :: !(Maybe Word8),+ hIDDescLength' :: !(Maybe Word8)+ } deriving (Show,Eq,Generic)++newtype HIDReportDesc = HIDReport [HIDReportItem]+ deriving (Eq,Show,Generic)++data HIDPhysicalDescriptor = PD !HIDDesignator !HIDQualifier !HIDEffort+ deriving (Eq,Show,Generic)++data HIDReportItem = HIDReportS !ShortItem + | HIDReportL !LongItem+ deriving (Eq)++instance Show HIDReportItem where + show (HIDReportS x) = show x+ show (HIDReportL x) = show x ++data ShortItem = Main !HIDMainTag+ | Global !HIDGlobalTag+ | Local !HIDLocalTag+ deriving (Eq,Generic)++instance Show ShortItem where + show (Main x) = show x+ show (Global x) = show x + show (Local x) = show x++newtype LongItem = Long ()+ deriving (Eq,Show,Generic)++data HIDMainTag = Input !HIDMainData + | Output !HIDMainData + | Feature !HIDMainData+ | Collection !HIDCollectionData+ | EndCollection+ deriving (Show,Eq,Generic)++--Name these more meaningfully+data HIDMainData = HIDMainData + {bit0 :: !HDMBit0,+ bit1 :: !HDMBit1,+ bit2 :: !HDMBit2,+ bit3 :: !HDMBit3,+ bit4 :: !HDMBit4,+ bit5 :: !HDMBit5,+ bit6 :: !HDMBit6,+ bit8 :: !HDMBit8+ } deriving (Show,Eq,Generic)++constructHIDMainD :: [Int] -> HIDMainData+constructHIDMainD [a,b,c,d,e,f,g,h] = HIDMainData (toEnum a) (toEnum b) (toEnum c) (toEnum d) (toEnum e) (toEnum f) (toEnum g) (toEnum h)++data HDMBit0 = Data | Constant+ deriving (Eq,Show,Enum,Generic)+data HDMBit1 = Array | Variable+ deriving (Eq,Show,Enum,Generic)+data HDMBit2 = Absolute | Relative+ deriving (Eq,Show,Enum,Generic)+data HDMBit3 = NoWrap | Wrap+ deriving (Eq,Show,Enum,Generic)+data HDMBit4 = Linear | NonLinear+ deriving (Eq,Show,Enum,Generic)+data HDMBit5 = PreferredState | NoPreferred+ deriving (Eq,Show,Enum,Generic)+data HDMBit6 = NoNullPosition | NullState+ deriving (Eq,Show,Enum,Generic)+data HDMBit8 = BitField | BufferedBytes+ deriving (Eq,Show,Enum,Generic)++data HIDCollectionData = Physical + | Application+ | Logical+ | Report+ | NamedArray + | UsageSwitch+ | UsageModifier+ deriving (Eq,Show,Enum,Generic)++data HIDGlobalTag = UsagePage !HIDUsagePage+ | LogicalMinimum !Int+ | LogicalMaximum !Int+ | PhysicalMinimum !Int+ | PhysicalMaximum !Int+ | UnitExponent !Int+ | Unit !Int+ | ReportSize !Int+ | ReportID !Int+ | ReportCount !Int+ | Push !Int+ | Pop !Int + deriving (Show,Eq,Generic)++data HIDLocalTag = Usage !HIDUsage+ | UsageMinimum !Int+ | UsageMaximum !Int+ | DesignatorIndex !HIDDesignator+ | DesignatorMinimum !Int+ | DesignatorMaximum !Int+ | StringIndex !Int+ | StringMinimum !Int+ | StringMaximum !Int+ | Delimiter !HIDDelimeter+ deriving (Show,Eq,Generic)++data HIDDelimeter = Open | Close+ deriving (Show,Eq,Enum,Generic)++newtype HIDUsagePage = UP Int+ deriving (Show,Eq,Generic)+ +newtype HIDUsage = U Int+ deriving (Show,Eq,Generic)++data HIDDesignator = None+ | Hand+ | Eyeball+ | Eyebrow+ | Eyelid+ | Ear+ | Nose+ | Mouth+ | UpperLip+ | LowerLip+ | Jaw+ | Neck+ | UpperArm+ | Elbow+ | Forearm+ | Wrist+ | Palm+ | Thumb+ | IndexFinger+ | MiddleFinger+ | RingFinger+ | LittleFinger+ | Head+ | Shoulder+ | Hip+ | Waist+ | Thigh+ | Knee+ | Calf+ | Ankle+ | Foot+ | Heel+ | BallOfFoot+ | BigToe+ | SecondToe+ | ThirdToe+ | FourthToe+ | LittleToe+ | Brow+ | Cheek+ deriving (Show,Eq,Enum,Generic)++data HIDQualifier = QNotApplicable+ | RightSide+ | LeftSide+ | BothSides+ | Either+ | Center+ deriving (Eq,Show,Enum,Generic)++data HIDBias = BNotApplicable+ | RightHand+ | LeftHand+ | BothHands+ | EitherHand+ deriving (Eq,Show,Enum,Generic)++data HIDPhysDescSet = PDS !HIDBias !HIDPreference ![HIDPhysicalDescriptor]+ deriving (Eq,Show,Generic)++type HIDEffort = Int++type HIDPreference = Int+++data Version = V {main :: !Int,+ minor :: !Int+ }+ deriving (Eq,Generic)++instance Show Version where + show (V ma mi) = (show ma) ++ "." ++ (show mi)
+ System/USB/HID/Parsers.hs view
@@ -0,0 +1,252 @@+module System.USB.HID.Parsers (-- * Top Level Parsers+ -- All "Data.Attoparsec" parsers+ parseHIDDesc+ ,parsePhysDescSet+ ,parseHIDReportDesc+ -- ** Second Level Parsers+ ,parseInt+ ,convertEnum) where++import Data.Bits ((.&.),rotate,rotateR,testBit)+import Data.Attoparsec.ByteString (Parser,anyWord8,peekWord8',peekWord8,choice,(<?>),many')+import qualified Data.Attoparsec.ByteString as AB (take)+import System.USB.HID.Descriptor+import Data.Word (Word8)+import Data.ByteString (ByteString, unpack,pack)+import qualified Data.ByteString as B (zip)+import Control.Applicative ((<$>))+import Control.Monad (when, unless)++-- | Parse a 'HIDDescriptor'+parseHIDDesc :: Parser HIDDescriptor+parseHIDDesc = do + hidDesc1 <- anyWord8+ hidcd <- parseVersion+ hidCC <- anyWord8+ hidND <- anyWord8+ hidDesc2 <- anyWord8+ hidDescL <- anyWord8+ hidDescT' <- peekWord8+ hidDescL' <- peekWord8+ return (HIDDescriptor hidDesc1 + hidcd + hidCC+ hidND+ hidDesc2+ (fromEnum hidDescL) + hidDescT' + hidDescL')++-- | Parse a 'HIDReportDesc'+parseHIDReportDesc :: Parser HIDReportDesc+parseHIDReportDesc = HIDReport <$> many' parseReportItem ++parsePhysicalDesc :: Parser HIDPhysicalDescriptor+parsePhysicalDesc = do + desig <- convertEnum <$> anyWord8 + bq <- anyWord8 + return (PD desig (convertEnum . sig3 $ bq) (convertEnum . lsig5 $ bq))++-- | Parse a 'HIDPhysDescSet' +parsePhysDescSet :: Parser HIDPhysDescSet+parsePhysDescSet = do + pref <- anyWord8 + sets <- many' parsePhysicalDesc+ return (PDS (convertEnum . sig3 $ pref) (fromEnum . lsig5 $ pref) sets)++parseVersion :: Parser Version+parseVersion = do + ma <- anyWord8 + mi <- anyWord8+ return (V (binDec ma) (binDec mi))+++binDec :: Word8 -> Int +binDec w = (t * 10) + s+ where beS = 0xF0+ s = fromEnum $ w .&. beS+ t = fromEnum $ (rotate w 4) .&. beS++parseReportItem :: Parser HIDReportItem+parseReportItem = choice [HIDReportS <$> parseShortItem+ ,HIDReportL <$> parseLongItem + ]++parseShortItem :: Parser ShortItem+parseShortItem = choice [Main <$> parseMain+ ,Global <$> parseGlobal + ,Local <$> parseLocal + ]++parseTop1 :: (Word8 -> Bool) -> String -> Parser a -> Parser a+parseTop1 test err parser = do + prefix <- peekWord8' + unless (test prefix) $ + fail err+ parser++parseLongItem :: Parser LongItem+parseLongItem = parseTop1 isLong "Not a Long Item" (return $ Long ())++parseMain :: Parser HIDMainTag+parseMain = parseTop1 isMain "Not main Type" $ choice [parseMainInput,parseCollection,parseEndCollection]++parseGlobal :: Parser HIDGlobalTag +parseGlobal = parseTop1 isGlobal "Not Global" $ choice [parseUsagePage, parseGlobalRest]++parseLocal :: Parser HIDLocalTag+parseLocal = parseTop1 isLocal "Not Local Tag" $ choice [parseUsage,parseDesignatorI,parseDelim,parseLocalRest]++parseTop2 :: (Word8 -> Bool) -> String -> (Word8 -> Parser a) + -> Parser a+parseTop2 test err parseCons = do + prefix <- anyWord8+ unless (test . preTag $ prefix) $ + fail err+ parseCons (preDataL $ prefix)+ ++parseDesignatorI :: Parser HIDLocalTag+parseDesignatorI = parseTop2 (== 3) "Not DesignatorI" ((DesignatorIndex <$>) . parseDesIData . fromEnum)++parseDelim :: Parser HIDLocalTag+parseDelim = parseTop2 (== 9) "Not Delimiter" ((Delimiter <$>) . parseDelimData . fromEnum)+ +parseDelimData :: Int -> Parser HIDDelimeter+parseDelimData n = toEnum <$> parseInt n ++parseDesIData :: Int -> Parser HIDDesignator+parseDesIData n = toEnum <$> parseInt n ++parseLocalRest :: Parser HIDLocalTag+parseLocalRest = do + prefix <- anyWord8+ let int = parseInt (fromEnum . preDataL $ prefix)+ case preTag prefix of+ 1 -> UsageMinimum <$> int+ 2 -> UsageMaximum <$> int+ 4 -> DesignatorMinimum <$> int+ 5 -> DesignatorMaximum <$> int+ 7 -> StringIndex <$> int+ 8 -> StringMinimum <$> int+ 9 -> StringMaximum <$> int+ _ -> fail "Not a data Int Local"++parseUsage :: Parser HIDLocalTag+parseUsage = parseTop2 (== 0) "Not Usage Tag" ((Usage <$>) . parseUsageData . fromEnum)++parseUsageData :: Int -> Parser HIDUsage+parseUsageData n = U <$> parseInt n + +parseUsagePage :: Parser HIDGlobalTag+parseUsagePage = parseTop2 isUsagePage "Not UsagePage" ((UsagePage <$>) . parseUsagePageData . fromEnum)++parseUsagePageData :: Int -> Parser HIDUsagePage+parseUsagePageData n = UP <$> parseInt n ++-- | @parseInt n@ parses an @Int@ of length encoded in @n@ 'Word8''s+parseInt :: Int -> Parser Int+parseInt n = byteStringToInt <$> AB.take n++parseGlobalRest :: Parser HIDGlobalTag+parseGlobalRest = do+ prefix <- anyWord8+ let int = parseInt (fromEnum . preDataL $ prefix)+ case preTag prefix of+ 1 -> LogicalMinimum <$> int+ 2 -> LogicalMaximum <$> int+ 3 -> PhysicalMinimum <$> int+ 4 -> PhysicalMaximum <$> int+ 5 -> UnitExponent <$> int+ 6 -> Unit <$> int+ 7 -> ReportSize <$> int+ 8 -> ReportID <$> int+ 9 -> ReportCount <$> int+ 10 -> Push <$> int+ 11 -> Pop <$> int+ _ -> fail "Not a data Int global"++byteStringToInt :: ByteString -> Int+byteStringToInt = foldl f 0 . B.zip (pack [0..])+ where f a (b,c) = 2^(8* (fromEnum b)) * (fromEnum c) + a ++parseMainInput :: Parser HIDMainTag +parseMainInput = do+ prefix <- anyWord8+ let mainData = parseMainData (preDataL prefix)+ case preTag prefix of + 8 -> Input <$> mainData+ 9 -> Output <$> mainData+ 11 -> Feature <$> mainData+ _ -> fail "Incorrect Tag"++parseCollection :: Parser HIDMainTag+parseCollection = parseTop2 isCollection "Not Collection" ((Collection <$>) . parseCollectionData)++parseEndCollection :: Parser HIDMainTag+parseEndCollection = parseTop2 isEndCollection "Not End Collection" (const (return EndCollection))+ +parseCollectionData :: Word8 -> Parser HIDCollectionData+parseCollectionData w = do+ tdata <- convertEnum <$> anyWord8+ AB.take (fromEnum (w - 1))+ return tdata++parseMainData :: Word8 -> Parser HIDMainData+parseMainData n = do + let a = fromEnum (if n <= 2 + then n+ else 2)+ as <- AB.take a+ let bs = concatMap takingBits (unpack as) ++ (repeat False)+ return (constructHIDMainD (map fromEnum (takeBits bs)))++takeBits :: [Bool] -> [Bool]+takeBits xs = take 7 xs ++ [xs !! 9]++isMain :: Word8 -> Bool+isMain w = preType w == 0++isGlobal :: Word8 -> Bool+isGlobal w = preType w == 1++isLocal :: Word8 -> Bool+isLocal w = preType w == 2++isLong :: Word8 -> Bool +isLong w = preTag w == 15++isUsagePage :: Word8 -> Bool+isUsagePage w = preTag w == 0++isCollection :: Word8 -> Bool+isCollection w = preTag w == 10++isEndCollection :: Word8 -> Bool+isEndCollection w = preTag w == 12++preType :: Word8 -> Word8 +preType p = rotateR (p .&. typeM) 2+ where typeM = 0x0C++preTag :: Word8 -> Word8 +preTag p = rotateR (p .&. tagM) 4+ where tagM = 0xF0++preDataL :: Word8 -> Word8 +preDataL p = p .&. dataLengthM+ where dataLengthM = 0x03++sig3 :: Word8 -> Word8 +sig3 w = rotate (w .&. m) 3+ where m = 0xE0++lsig5 :: Word8 -> Word8 +lsig5 w = w .&. m + where m = 0x1F++convertEnum :: (Enum a, Enum b) => a -> b+convertEnum = toEnum . fromEnum++takingBits :: Word8 -> [Bool]+takingBits x = map (testBit x) [0..7]
+ System/USB/HID/Requests.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE DeriveGeneric , AutoDeriveTypeable #-}+-- consider writing parsers for physical descriptors+-- write documentation for all.+module System.USB.HID.Requests (-- * Request Types + HIDDescriptorClass+ ,HIDControlRequest+ ,HIDProtocol+ ,DescriptorIndex+ ,HIDInterfaceNumber+ ,Duration+ ,ReportID+ ,Report+ ,ReportType + -- * HID Requests+ -- | These are all specified in the HID Specification Section 7+ ,getHIDReportDesc+ ,getPhysicalDescriptor+ ,getReport+ ,setReport+ ,getIdle+ ,setIdle+ ,getProtocol+ ,setProtocol+ ) where+import GHC.Generics+import Data.Word+import System.USB+import Data.Attoparsec.ByteString+import System.USB.HID.Descriptor+import System.USB.HID.Parsers+import Data.ByteString as B (ByteString,empty,head)+import Data.Maybe (fromJust)+import Data.Tuple (swap)++data HIDDescriptorClass = HIDR+ | HIDReportR + | PhysicalDescriptorR+ deriving (Eq,Show,Generic)++data HIDControlRequest = GetReport + | GetIdle + | GetProtocol + | SetReport + | SetIdle + | SetProtocol+ deriving (Eq,Show,Generic)++data HIDProtocol = Boot | Report + deriving (Eq, Show,Enum,Generic)++instance Enum HIDControlRequest where + toEnum x = fromJust (lookup x (zip [1,2,3,9,10,11] [GetReport, GetIdle, GetProtocol, SetReport, SetIdle, SetProtocol]))+ fromEnum x = fromJust (lookup x (zip [GetReport, GetIdle, GetProtocol, SetReport, SetIdle, SetProtocol] [1,2,3,9,10,11]))++type DescriptorIndex = Word8++type HIDInterfaceNumber = Word16++type Duration = Word8++type ReportID = Word8++type Report = ByteString++data ReportType = RInput | ROutput | RFeature+ deriving (Eq,Show,Generic)++instance Enum ReportType where + toEnum x = fromJust (lookup x as)+ where as = [(1,RInput),(2,ROutput),(3,RFeature)]+ fromEnum x = fromJust (lookup x (map swap as))+ where as = [(1,RInput),(2,ROutput),(3,RFeature)]++instance Enum HIDDescriptorClass where + toEnum 0x21 = HIDR+ toEnum 0x22 = HIDReportR + toEnum 0x23 = PhysicalDescriptorR+ fromEnum HIDR = 0x21+ fromEnum HIDReportR = 0x22+ fromEnum PhysicalDescriptorR = 0x23++getHIDDesc :: DeviceHandle -> Parser a -> HIDControlRequest -> HIDDescriptorClass -> DescriptorIndex -> HIDInterfaceNumber -> Size -> Timeout -> IO a +getHIDDesc h parser cr dc di intN s t = do + (bs,s) <- readControl h (ControlSetup Class ToInterface (convertEnum $ cr) ((convertEnum $ dc )*256 + (convertEnum $ di)) intN) s t+ case parseOnly parser bs of + Left x -> fail "Could not Parse descriptor"+ Right x -> return x++-- | Performs 'Class' request for the HID report descriptor using the Control pipe+getHIDReportDesc :: DeviceHandle -> HIDInterfaceNumber -> Size -> Timeout -> IO HIDReportDesc+getHIDReportDesc h intN = getHIDDesc h parseHIDReportDesc GetReport HIDReportR 0 intN +++-- | Performs 'Class' request for a HID Physical Descriptor set using the Control pipe+getPhysicalDescriptor :: DeviceHandle -> HIDInterfaceNumber -> DescriptorIndex -> Size -> Timeout -> IO HIDPhysDescSet+getPhysicalDescriptor h intN di = getHIDDesc h parsePhysDescSet GetReport PhysicalDescriptorR di intN ++-- | The GetReport request allows the host to receive a report via the Control pipe.++getReport :: DeviceHandle -> ReportType -> ReportID -> HIDInterfaceNumber -> Size -> Timeout -> IO (ByteString,Status)+getReport h rt ri intN s t = hidGet h (convertEnum $ SetReport) ((convertEnum $ rt )*256 + (convertEnum $ ri)) intN s t++-- | The SetReport request allows the host to send a report to the device, possibly setting the state of input, output, or feature controls.++setReport :: DeviceHandle -> ReportType -> ReportID -> HIDInterfaceNumber -> Report -> Timeout -> IO (Size,Status)+setReport h rt ri intN r t = hidSet h (convertEnum $ SetReport) ((convertEnum $ rt )*256 + (convertEnum $ ri)) intN r t++-- | The GetIdle request reads the current idle rate for a particular Input report++getIdle :: DeviceHandle -> HIDInterfaceNumber -> ReportID -> Timeout -> IO (HIDProtocol,Status)+getIdle h intN rid t = do + (p,s) <- hidGet h (convertEnum $ GetIdle) (convertEnum $ rid) intN 1 t+ let i = fromEnum (B.head p)+ return (toEnum i ,s)++-- | The SetIdle request silences a particular report on the Interrupt In pipe until a new event occurs or the specified amount of time passes.++setIdle :: DeviceHandle -> HIDInterfaceNumber -> Duration -> ReportID -> Timeout -> IO (Size,Status)+setIdle h intN d rid = hidSet h (convertEnum $ SetIdle) ((convertEnum $ d )*256 + (convertEnum $ rid)) intN empty++-- | The GetProtocol request reads which protocol is currently active (either the boot protocol or the report protocol.)++getProtocol :: DeviceHandle -> HIDInterfaceNumber -> Timeout -> IO (HIDProtocol,Status)+getProtocol h intN t = do + (p,s) <- hidGet h (convertEnum $ GetProtocol) 0 intN 1 t+ let i = fromEnum (B.head p)+ return (toEnum i ,s)++-- | The SetProtocol switches between the boot protocol and the report protocol (or vice versa).++setProtocol :: DeviceHandle -> HIDProtocol -> HIDInterfaceNumber -> Timeout -> IO (Size,Status)+setProtocol h p intN = hidSet h (convertEnum $ SetProtocol) (convertEnum $ p) intN empty++hidSet :: DeviceHandle -> Word8 -> Word16 -> Word16 -> ByteString ->Timeout -> IO (Size,Status)+hidSet h r v = writeControl h . ControlSetup Class ToInterface r v++hidGet :: DeviceHandle -> Word8 -> Word16 -> Word16 -> Size -> Timeout -> IO (ByteString,Status)+hidGet h r v = readControl h . ControlSetup Class ToInterface r v
+ usb-hid.cabal view
@@ -0,0 +1,25 @@+-- Initial usb-hid.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++name: usb-hid+version: 0.1.0.0+synopsis: Parser and request Library for USB HIDs+-- description: +homepage: https://github.com/mapinguari/usb-hid+license: BSD3+license-file: LICENSE+author: Paul Bennett+maintainer: paul.mic.bennett@gmail.com+-- copyright: +category: System+build-type: Simple+-- extra-source-files: +cabal-version: >=1.10++library+ exposed-modules: System.USB.HID.Parsers, System.USB.HID.Requests, System.USB.HID.Descriptor+ -- other-modules: + other-extensions: DeriveGeneric, AutoDeriveTypeable+ build-depends: base >=4.7 && <4.8, attoparsec >=0.12 && <0.13, bytestring >=0.10 && <0.11, usb >=1.3 && <1.4+ -- hs-source-dirs: + default-language: Haskell2010