Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 94 additions & 96 deletions adafruit_usb_host_mouse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -114,92 +128,51 @@ 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
and return it.

: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:
Expand All @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand Down