diff --git a/usb_protocol/emitters/descriptors/hid.py b/usb_protocol/emitters/descriptors/hid.py new file mode 100644 index 0000000..6709804 --- /dev/null +++ b/usb_protocol/emitters/descriptors/hid.py @@ -0,0 +1,109 @@ +import unittest + +from contextlib import contextmanager + +from .. import emitter_for_format +from ..descriptor import ComplexDescriptorEmitter +from ...types.descriptors.hid import \ + HIDDescriptor as HIDDescriptorType +from ...types.descriptors.hid import * + +ReportDescriptorEmitter = emitter_for_format(ReportDescriptor) + +_hid_item_length = [ 0, 1, 2, 4 ] + +class HIDDescriptor(ComplexDescriptorEmitter): + DESCRIPTOR_FORMAT = HIDDescriptorType + + def add_report_raw(self, report_data): + """Append raw report item or bytes to HID report + + Arguments: + report_data -- bytes-like or ReportDescriptor to be appended to + the HID report. + """ + self._reports.append(report_data) + + def add_report_item(self, report_prefix, *report_data): + """Convenience function to add formatted HID report item + + Arguments: + report_prefix -- HIDPrefix enum representing report item type + *report_data -- Additional bytes-like report item data. + Valid lengths are 1, 2, or 4 bytes. + """ + hid_report = ReportDescriptorEmitter() + report_len = _hid_item_length.index(len(report_data)) + hid_report.bHeader = { + "prefix": report_prefix, + "bSize": report_len + } + hid_report.data = report_data + self._reports.append(hid_report) + + def add_input_item(self, + data_constant = False, + array_variable = True, + absolute_relative = False, + wrap = False, + linear = False, + preferred = True, + null = False, + volatile = False): + """Convenience function to add HID input item with preformatted flags. + See HID 1.11 section 6.2.2.5 for flag meanings. + """ + item_flags = ItemFlags.build({ + "data_constant": data_constant, + "array_variable": array_variable, + "absolute_relative": absolute_relative, + "wrap": wrap, + "linear": linear, + "nPreferred": ~preferred, + "null": null, + "volatile": volatile, + }) + self.add_report_item(HIDPrefix.INPUT, ord(item_flags)) + + def add_output_item(self, + data_constant = False, + array_variable = True, + absolute_relative = False, + wrap = False, + linear = False, + preferred = True, + null = False, + volatile = False): + """Convenience function to add HID output item with preformatted flags. + See HID 1.11 section 6.2.2.5 for flag meanings. + """ + item_flags = ItemFlags.build({ + "data_constant": data_constant, + "array_variable": array_variable, + "absolute_relative": absolute_relative, + "wrap": wrap, + "linear": linear, + "nPreferred": ~preferred, + "null": null, + "volatile": volatile, + }) + self.add_report_item(HIDPrefix.OUTPUT, ord(item_flags)) + + def __init__(self, parent_descriptor): + super().__init__() + # The HID Report Descriptor sits under a different USB Descriptor, + # we need access to the descriptor root to create this. + self._parent_descriptor = parent_descriptor + self._reports = [] + + def _pre_emit(self): + report_descriptor = [] + for report in self._reports: + if hasattr(report, "emit"): + report_descriptor.append(report.emit()) + else: + report_descriptor.append(report) + report_descriptor = b"".join(report_descriptor) + descriptor_len = len(report_descriptor) + self.wDescriptorLength = descriptor_len + self._parent_descriptor.add_descriptor(report_descriptor, descriptor_type=0x22) \ No newline at end of file diff --git a/usb_protocol/emitters/descriptors/standard.py b/usb_protocol/emitters/descriptors/standard.py index af57886..f7ff9ec 100644 --- a/usb_protocol/emitters/descriptors/standard.py +++ b/usb_protocol/emitters/descriptors/standard.py @@ -177,12 +177,14 @@ def get_index_for_string(self, string): return index - def add_descriptor(self, descriptor, index=0): + def add_descriptor(self, descriptor, index=0, descriptor_type=None): """ Adds a descriptor to our collection. Parameters: - descriptor -- The descriptor to be added. - index -- The index of the relevant descriptor. Defaults to 0. + descriptor -- The descriptor to be added. + index -- The index of the relevant descriptor. Defaults to 0. + descriptor_type -- The type of the descriptor to be added. If `None`, + this is automatically derived from the descriptor contents. """ # If this is an emitter rather than a descriptor itself, convert it. @@ -190,7 +192,8 @@ def add_descriptor(self, descriptor, index=0): descriptor = descriptor.emit() # Figure out the identifier (type + index) for this descriptor... - descriptor_type = descriptor[1] + if(descriptor_type is None): + descriptor_type = descriptor[1] identifier = descriptor_type, index # ... and store it. diff --git a/usb_protocol/types/descriptors/hid.py b/usb_protocol/types/descriptors/hid.py new file mode 100644 index 0000000..7c814f7 --- /dev/null +++ b/usb_protocol/types/descriptors/hid.py @@ -0,0 +1,85 @@ +# +# This file is part of usb-protocol. +# +""" Structures describing Communications Device Class descriptors. """ + +import unittest +from enum import IntEnum, unique + +import construct +from construct import this, Default + +from .. import LanguageIDs +from ..descriptor import \ + DescriptorField, DescriptorNumber, DescriptorFormat, \ + BCDFieldAdapter, DescriptorLength + +@unique +class HIDPrefix(IntEnum): + # Main items + INPUT = 0b1000_00 + OUTPUT = 0b1001_00 + FEATURE = 0b1011_00 + COLLECTION = 0b1010_00 + END_COLLECTION = 0b1100_00 + # Global items + USAGE_PAGE = 0b0000_01 + LOGICAL_MIN = 0b0001_01 + LOGICAL_MAX = 0b0010_01 + PHYSICAL_MIN = 0b0011_01 + PHYSICAL_MAX = 0b0100_01 + UNIT_EXPONENT = 0b0101_01 + UNIT = 0b0110_01 + REPORT_SIZE = 0b0111_01 + REPORT_ID = 0b1000_01 + REPORT_COUNT = 0b1001_01 + PUSH = 0b1010_01 + POP = 0b1011_01 + # Local Items + USAGE = 0b0000_10 + USAGE_MIN = 0b0001_10 + USAGE_MAX = 0b0010_10 + DESIGNATOR_IDX = 0b0011_10 + DESIGNATOR_MIN = 0b0100_10 + DESIGNATOR_MAX = 0b0101_10 + STRING_IDX = 0b0111_10 + STRING_MIN = 0b1000_10 + STRING_MAX = 0b1001_10 + DELIMITER = 0b1010_10 + +HIDDescriptor = DescriptorFormat( + "bLength" / construct.Const(0x09, construct.Int8ul), + "bDescriptorType" / DescriptorNumber(33), + "bcdHID" / DescriptorField("HID Protocol Version", default=1.11), + "bCountryCode" / DescriptorField("HID Device Language", default=0), + "bNumDescriptors" / DescriptorField("Number of HID Descriptors", default=1), + "bDescriptorType" / DescriptorField("HID Descriptor Type", default=34), + "wDescriptorLength" / DescriptorField("HID Descriptor Length") + # bDescriptorType and wDescriptorLength repeat bNumDescriptors times +) + +_hid_item_length = [ 0, 1, 2, 4 ] +ReportDescriptor = DescriptorFormat( + "bHeader" / construct.BitStruct( + # prefix technically consists of a 4 byte tag and a 2 byte type, + # however, they're all listed together in the HID spec + "prefix" / construct.Enum(construct.BitsInteger(6), HIDPrefix), + "bSize" / construct.BitsInteger(2), + ), + "data" / construct.Byte[lambda ctx: _hid_item_length[ctx.bHeader.bSize]] +) + +# Flags for INPUT/OUTPUT/FEATURE items. Named under one of the following conventions: +# valA_valB: valA when 0, valB when 1 +# flag: Flag disabled when 0, flag enabled when 1 +# nFlag: Flag enabled when 0, flag disabled when 1 +ItemFlags = construct.BitStruct( + "volatile" / construct.Flag, + "null" / construct.Flag, + "nPreferred" / construct.Flag, + "linear" / construct.Flag, + "wrap" / construct.Flag, + "absolute_relative" / construct.Flag, + "array_variable" / construct.Flag, + "data_constant" / construct.Flag, +) \ No newline at end of file