diff --git a/adafruit_usb_host_mouse/__init__.py b/adafruit_usb_host_mouse/__init__.py index 0e68546..011241c 100644 --- a/adafruit_usb_host_mouse/__init__.py +++ b/adafruit_usb_host_mouse/__init__.py @@ -43,22 +43,36 @@ BUTTONS = ["left", "right", "middle"] DEFAULT_CURSOR = "/".join(__file__.split("/")[:-1]) + "/mouse_cursor.bmp" +SUBCLASS_BOOT = 0x01 +SUBCLASS_RESERVED = 0x00 -def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 + +def find_and_init_mouse(cursor_image=DEFAULT_CURSOR, subclass=SUBCLASS_BOOT): """ - Scan for an attached boot mouse connected via USB host. - If one is found initialize an instance of :class:`BootMouse` class - and return it. + Scan for an attached mouse connected via USB host. + If one is found return a tuple containing the parameters needed to initalize an + instance of :class: `BootMouse` or :class: `ReportMouse` depending on the value of + the subclass parameter. :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as - `None`, the :class:`BootMouse` instance will not control a :class:`displayio.TileGrid` object. - :return: The :class:`BootMouse` instance or None if no mouse was found. + `None`, the object instance created using the returned tuple will not control + a :class:`displayio.TileGrid` object. + :param subclass: Defines whether to search for boot or non-boot mice. + SUBCLASS_BOOT (0X01), a boot mouse will be searched for + SUBCLASS_RESERVED (0x00), a non-boot (report) mouse will be searched for + :return: A tupple cotaining the arguments needed by the calling find and init helper + function. If no mouse is found None is returned. """ mouse_interface_index, mouse_endpoint_address = None, None mouse_device = None + deviceType, find_endpoint = ( + ("boot", adafruit_usb_host_descriptors.find_boot_mouse_endpoint) + if subclass == SUBCLASS_BOOT + else ("report", adafruit_usb_host_descriptors.find_report_mouse_endpoint) + ) # scan for connected USB device and loop over any found - print("scanning usb (boot)") + print(f"scanning usb ({deviceType})") for device in usb.core.find(find_all=True): # print device info try: @@ -76,9 +90,7 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 ) print(config_descriptor) - _possible_interface_index, _possible_endpoint_address = ( - adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device) - ) + _possible_interface_index, _possible_endpoint_address = find_endpoint(device) if _possible_interface_index is not None and _possible_endpoint_address is not None: mouse_device = device mouse_interface_index = _possible_interface_index @@ -88,23 +100,25 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 + f"endpoint_address: {hex(mouse_endpoint_address)}" ) break - print("was not a boot mouse") + print(f"was not a {deviceType} mouse") except usb.core.USBError as e: print_exception(e, e, None) - mouse_was_attached = None + mouse_was_attached = [] if mouse_device is not None: # detach the kernel driver if needed - if mouse_device.is_kernel_driver_active(0): - mouse_was_attached = True - mouse_device.detach_kernel_driver(0) - else: - mouse_was_attached = False + possible_interfaces = [mouse_interface_index] if subclass == SUBCLASS_BOOT else [0, 1, 2] + + for intf in possible_interfaces: + if mouse_device.is_kernel_driver_active(intf): + mouse_was_attached.append(intf) + mouse_device.detach_kernel_driver(intf) # set configuration on the mouse so we can use it mouse_device.set_configuration() # load the mouse cursor bitmap + mouse_tg = None if isinstance(cursor_image, str): mouse_bmp = OnDiskBitmap(cursor_image) @@ -114,16 +128,35 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 # create a TileGrid for the mouse, using its bitmap and pixel_shader mouse_tg = TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader) - else: - mouse_tg = None - - return BootMouse(mouse_device, mouse_endpoint_address, mouse_was_attached, mouse_tg) + return ( + (mouse_device, mouse_interface_index, mouse_endpoint_address, mouse_was_attached), + mouse_tg, + ) # if no mouse found return None -def find_and_init_report_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 +def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR, scale=1): + """ + Scan for an attached boot mouse connected via USB host. + If one is found initialize an instance of :class:`BootMouse` class + and return it. + + :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as + `None`, the :class:`BootMouse` instance will not control a :class:`displayio.TileGrid` object + :param scale: The scale of the group that the Mouse TileGrid will be put into + Needed in order to properly clamp the mouse to the display bounds + :return: The :class:`BootMouse` instance or None if no mouse was found. + """ + found_mouse = find_and_init_mouse(cursor_image, SUBCLASS_BOOT) + if found_mouse is not None: + return BootMouse(*found_mouse[0], tilegrid=found_mouse[1], scale=scale) + else: + return None + + +def find_and_init_report_mouse(cursor_image=DEFAULT_CURSOR, scale=1): """ Scan for an attached report mouse connected via USB host. If one is found initialize an instance of :class:`ReportMouse` class @@ -131,75 +164,15 @@ def find_and_init_report_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as `None`, the :class:`ReportMouse` will not control a :class:`displayio.TileGrid` object. + :param scale: The scale of the group that the Mouse TileGrid will be put into + Needed in order to properly clamp the mouse to the display bounds :return: The :class:`ReportMouse` instance or None if no mouse was found. """ - mouse_interface_index, mouse_endpoint_address = None, None - mouse_device = None - - # scan for connected USB device and loop over any found - print("scanning usb (report)") - for device in usb.core.find(find_all=True): - # print device info - try: - try: - print(f"{device.idVendor:04x}:{device.idProduct:04x}") - except usb.core.USBError as e: - print_exception(e, e, None) - try: - print(device.manufacturer, device.product) - except usb.core.USBError as e: - print_exception(e, e, None) - print() - config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor( - device, 0 - ) - print(config_descriptor) - - _possible_interface_index, _possible_endpoint_address = ( - adafruit_usb_host_descriptors.find_report_mouse_endpoint(device) - ) - if _possible_interface_index is not None and _possible_endpoint_address is not None: - mouse_device = device - mouse_interface_index = _possible_interface_index - mouse_endpoint_address = _possible_endpoint_address - print( - f"mouse interface: {mouse_interface_index} " - + f"endpoint_address: {hex(mouse_endpoint_address)}" - ) - break - print("was not a report mouse") - except usb.core.USBError as e: - print_exception(e, e, None) - - mouse_was_attached = None - if mouse_device is not None: - # detach the kernel driver if needed - if mouse_device.is_kernel_driver_active(0): - mouse_was_attached = True - mouse_device.detach_kernel_driver(0) - else: - mouse_was_attached = False - - # set configuration on the mouse so we can use it - mouse_device.set_configuration() - - # load the mouse cursor bitmap - if isinstance(cursor_image, str): - mouse_bmp = OnDiskBitmap(cursor_image) - - # make the background pink pixels transparent - mouse_bmp.pixel_shader.make_transparent(0) - - # create a TileGrid for the mouse, using its bitmap and pixel_shader - mouse_tg = TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader) - - else: - mouse_tg = None - - return ReportMouse(mouse_device, mouse_endpoint_address, mouse_was_attached, mouse_tg) - - # if no mouse found - return None + found_mouse = find_and_init_mouse(cursor_image, SUBCLASS_RESERVED) + if found_mouse is not None: + return ReportMouse(*found_mouse[0], tilegrid=found_mouse[1], scale=scale) + else: + return None class BootMouse: @@ -209,19 +182,23 @@ class BootMouse: were pressed. :param device: The usb device instance for the mouse + :param interface_index: The USB interface index of the mouse :param endpoint_address: The address of the mouse endpoint - :param was_attached: Whether the usb device was attached to the kernel + :param was_attached: A list of the usb devices detached from the kernel :param tilegrid: The TileGrid that holds the visible mouse cursor - :param scale: The scale of the group that the Mouse TileGrid will be put into. + :param scale: The scale of the group that the Mouse TileGrid will be put into Needed in order to properly clamp the mouse to the display bounds """ - def __init__(self, device, endpoint_address, was_attached, tilegrid=None, scale=1): # noqa: PLR0913, too many args + def __init__( # noqa: PLR0913, too many args + self, device, interface_index, endpoint_address, was_attached, *, tilegrid=None, scale=1 + ): self.device = device self.tilegrid = tilegrid """TileGrid containing the Mouse cursor graphic.""" + self.interface = interface_index self.endpoint = endpoint_address self.buffer = array.array("b", [0] * 4) self.was_attached = was_attached @@ -283,8 +260,11 @@ def release(self): Release the mouse cursor and re-attach it to the kernel if it was attached previously. """ - if self.was_attached and not self.device.is_kernel_driver_active(0): - self.device.attach_kernel_driver(0) + # was_attached is a list of interfaces detached from the kernel or + # an empty list if no interfaces were detached + for intf in self.was_attached: + if not self.device.is_kernel_driver_active(intf): + self.device.attach_kernel_driver(intf) def update(self): """ @@ -337,8 +317,26 @@ def update(self): class ReportMouse(BootMouse): - def __init__(self, device, endpoint_address, was_attached, tilegrid=None, scale=1): # noqa: PLR0913, too many args - super().__init__(device, endpoint_address, was_attached, tilegrid, scale) + """ + Helpler class that encapsulates the objects needed to interact with a non-Boot + mouse (Report), show a visible cursor on the display, and determine when buttons + were pressed. The class is a subclass of BootMouse that overrides the update method. + + :param device: The usb device instance for the mouse + :param interface_index: The USB interface index of the mouse + :param endpoint_address: The address of the mouse endpoint + :param was_attached: A list of the usb devices detached from the kernel + :param tilegrid: The TileGrid that holds the visible mouse cursor + :param scale: The scale of the group that the Mouse TileGrid will be put into + Needed in order to properly clamp the mouse to the display bounds + """ + + def __init__( # noqa: PLR0913, too many args + self, device, interface_index, endpoint_address, was_attached, *, tilegrid=None, scale=1 + ): + super().__init__( + device, interface_index, endpoint_address, was_attached, tilegrid=tilegrid, scale=scale + ) def update(self): """