Source code for facedancer.classes.hid.descriptor

#
# This file is part of Facedancer.
#
""" Code for implementing HID classes. """

# Support annotations on Python < 3.9
from __future__  import annotations

from enum        import IntEnum
from typing      import Tuple, Iterable

from ...descriptor import USBDescriptor, USBDescriptorTypeNumber


#
# Global items.
#

def _hid_item_generator(constant) -> Tuple[int]:
    """ Generates a HID descriptor global item entry. """
    # See See HID1.1 [6.2.2.1 Items Types and Tags]
    size_code_map = {
        0: 0b00,  # No data
        1: 0b01,  # 1 byte
        2: 0b10,  # 2 bytes
        4: 0b11,  # 4 bytes
    }
    # Generate a function that creates a item with
    # the relevant type...
    def hid_item(*octets):
        size = len(octets)
        if size not in size_code_map:
            raise ValueError(
                f"HID short item can only have 0, 1, 2 or 4 data bytes, got {size}"
            )

        size_code = size_code_map[size]
        prefix = constant | size_code

        return (prefix, *octets)

    # ... and return it.
    return hid_item


def _io_item_generator(type_constant) -> Tuple[int]:

    # Generate a function that creates a item with
    # the relevant type...
    def hid_io_item(
            constant=False,
            variable=False,
            relative=False,
            wrap=False,
            nonlinear=False,
            preferred_state=True,
            nullable=False,
            buffered_bytes=False
        ):

        # If we have a buffered bytes byte, include it.
        item_length = 2 if buffered_bytes else 1

        # Build the relevant item.
        # See HID1.1 [6.2.2.4]
        item  = (1 << 0) if constant  else 0
        item |= (1 << 1) if variable  else 0
        item |= (1 << 2) if relative  else 0
        item |= (1 << 3) if wrap      else 0
        item |= (1 << 4) if nonlinear else 0
        item |= 0 if preferred_state  else (1 << 5)
        item |= (1 << 6) if nullable else 0

        # Build the item, and return it.
        extra = (1,) if buffered_bytes else ()
        return (type_constant | item_length, item, *extra)

    # ... and return our function.
    return hid_io_item


#
# Main items.
#

INPUT              =  _io_item_generator(0b1000_00_00)
OUTPUT             =  _io_item_generator(0b1001_00_00)
FEATURE            =  _io_item_generator(0b1011_00_00)
COLLECTION         = _hid_item_generator(0b1010_00_00)
END_COLLECTION     = lambda : (0b1100_00_00,)


# Note: the odd separation of the last two bits here is due to
# the formatting of the USB specification (and due to the fact)
# that those bits are overridden, and thus always should be zero.
USAGE_PAGE         = _hid_item_generator(0b0000_01_00)
LOGICAL_MINIMUM    = _hid_item_generator(0b0001_01_00)
LOGICAL_MAXIMUM    = _hid_item_generator(0b0010_01_00)
PHYSICAL_MINIMUM   = _hid_item_generator(0b0011_01_00)
PHYSICAL_MAXIMUM   = _hid_item_generator(0b0100_01_00)
UNIT_EXPONENT      = _hid_item_generator(0b0101_01_00)
UNIT               = _hid_item_generator(0b0110_01_00)
REPORT_SIZE        = _hid_item_generator(0b0111_01_00)
REPORT_ID          = _hid_item_generator(0b1000_01_00)
REPORT_COUNT       = _hid_item_generator(0b1001_01_00)
PUSH               = _hid_item_generator(0b1010_01_00)
POP                = _hid_item_generator(0b1011_01_00)

#
# Local items.
#
USAGE              = _hid_item_generator(0b0000_10_00)
USAGE_MINIMUM      = _hid_item_generator(0b0001_10_00)
USAGE_MAXIMUM      = _hid_item_generator(0b0010_10_00)
DESGINATOR_INDEX   = _hid_item_generator(0b0011_10_00)
DESGINATOR_MINIMUM = _hid_item_generator(0b0100_10_00)
DESGINATOR_MAXIMUM = _hid_item_generator(0b0101_10_00)
STRING_INDEX       = _hid_item_generator(0b0111_10_00)
STRING_MINIMUM     = _hid_item_generator(0b1000_10_00)
STRING_MAXIMUM     = _hid_item_generator(0b1001_10_00)
DELIMITER          = _hid_item_generator(0b1010_10_00)


[docs] class HIDCollection(IntEnum): """ HID collections; from HID1.1 [6.2.2.4]. """ PHYSICAL = 0x00 APPLICATION = 0x01 LOGICAL = 0x02 REPORT = 0x03 NAMED_ARRAY = 0x04 USAGE_SWITCH = 0x05 USAGE_MODIFIER = 0x06 VENDOR = 0xFF
[docs] class HIDReportDescriptor(USBDescriptor): """ Descriptor class representing a HID report descriptor. """ # Parameter where the user defines the descriptor's fields. fields: Iterable[bytes] = () # Mark this as a HID report descriptor. type_number : int = USBDescriptorTypeNumber.REPORT raw : None | bytes = None
[docs] def __call__(self, index=0): """ Converts the descriptor object into raw bytes. """ if self.raw is not None: return self.raw raw = bytearray() # Squish together all of our fields to make a descriptor. for field in self.fields: raw.extend(field) return bytes(raw)