diff --git a/adafruit_usb_host_mouse.py b/adafruit_usb_host_mouse/__init__.py similarity index 75% rename from adafruit_usb_host_mouse.py rename to adafruit_usb_host_mouse/__init__.py index e5f4cd4..ca00977 100644 --- a/adafruit_usb_host_mouse.py +++ b/adafruit_usb_host_mouse/__init__.py @@ -41,14 +41,18 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_USB_Host_Mouse.git" BUTTONS = ["left", "right", "middle"] +DEFAULT_CURSOR = "/".join(__file__.split("/")[:-1]) + "/mouse_cursor.bmp" -def find_and_init_boot_mouse(cursor_image="/launcher_assets/mouse_cursor.bmp"): +def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912 """ Scan for an attached boot mouse connected via USB host. - If one is found initialize an instance of BootMouse class + If one is found initialize an instance of :class:`BootMouse` class and return it. - :return: The BootMouse instance or None if no mouse was found. + + :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. """ mouse_interface_index, mouse_endpoint_address = None, None mouse_device = None @@ -101,17 +105,19 @@ def find_and_init_boot_mouse(cursor_image="/launcher_assets/mouse_cursor.bmp"): mouse_device.set_configuration() # load the mouse cursor bitmap - if not isinstance(cursor_image, str): - raise TypeError("cursor_image must be a string") - mouse_bmp = OnDiskBitmap(cursor_image) + if isinstance(cursor_image, str): + mouse_bmp = OnDiskBitmap(cursor_image) + + # make the background pink pixels transparent + mouse_bmp.pixel_shader.make_transparent(0) - # 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) - # 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_tg, mouse_was_attached) + return BootMouse(mouse_device, mouse_endpoint_address, mouse_was_attached, mouse_tg) # if no mouse found return None @@ -125,13 +131,13 @@ class BootMouse: :param device: The usb device instance for the mouse :param endpoint_address: The address of the mouse endpoint - :param tilegrid: The TileGrid that holds the visible mouse cursor :param was_attached: Whether the usb device was attached to 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__(self, device, endpoint_address, tilegrid, was_attached, scale=1): # noqa: PLR0913, too many args + def __init__(self, device, endpoint_address, was_attached, tilegrid=None, scale=1): # noqa: PLR0913, too many args self.device = device self.tilegrid = tilegrid @@ -154,29 +160,44 @@ def __init__(self, device, endpoint_address, tilegrid, was_attached, scale=1): If there's no new mouse data (nothing changes) this property can be checked to see which buttons are currently pressed.""" - self.display_size = (supervisor.runtime.display.width, supervisor.runtime.display.height) + if tilegrid is not None: + self.display_size = ( + supervisor.runtime.display.width, + supervisor.runtime.display.height, + ) + self.tilegrid.x, self.tilegrid.y = ( + x // 2 for x in self.display_size + ) # center cursor in display + else: + self._x, self._y = 0, 0 @property def x(self) -> int: """ The x coordinate of the mouse cursor """ - return self.tilegrid.x + return self.tilegrid.x if self.tilegrid else self._x @x.setter def x(self, new_x: int) -> None: - self.tilegrid.x = new_x + if self.tilegrid: + self.tilegrid.x = new_x + else: + self._x = new_x @property def y(self) -> int: """ The y coordinate of the mouse cursor """ - return self.tilegrid.y + return self.tilegrid.y if self.tilegrid else self._y @y.setter def y(self, new_y: int) -> None: - self.tilegrid.y = new_y + if self.tilegrid: + self.tilegrid.y = new_y + else: + self._y = new_y def release(self): """ @@ -206,22 +227,21 @@ def update(self): except usb.core.USBError: return None - # update the mouse tilegrid x and y coordinates + # update the mouse x and y coordinates # based on the delta values read from the mouse - self.tilegrid.x = max( - 0, - min( - (self.display_size[0] // self.scale) - 1, - self.tilegrid.x + int(round((self.buffer[1] / self.sensitivity), 0)), - ), - ) - self.tilegrid.y = max( - 0, - min( - (self.display_size[1] // self.scale) - 1, - self.tilegrid.y + int(round((self.buffer[2] / self.sensitivity), 0)), - ), - ) + dx, dy = self.buffer[1:3] + dx = int(round((dx / self.sensitivity), 0)) + dy = int(round((dy / self.sensitivity), 0)) + if self.tilegrid: + self.tilegrid.x = max( + 0, min((self.display_size[0] // self.scale) - 1, self.tilegrid.x + dx) + ) + self.tilegrid.y = max( + 0, min((self.display_size[1] // self.scale) - 1, self.tilegrid.y + dy) + ) + else: + self._x += dx + self._y += dy self.pressed_btns = [] for i, button in enumerate(BUTTONS): diff --git a/adafruit_usb_host_mouse/mouse_cursor.bmp b/adafruit_usb_host_mouse/mouse_cursor.bmp new file mode 100644 index 0000000..94ec328 Binary files /dev/null and b/adafruit_usb_host_mouse/mouse_cursor.bmp differ diff --git a/adafruit_usb_host_mouse/mouse_cursor.bmp.license b/adafruit_usb_host_mouse/mouse_cursor.bmp.license new file mode 100644 index 0000000..7afc812 --- /dev/null +++ b/adafruit_usb_host_mouse/mouse_cursor.bmp.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries + +SPDX-License-Identifier: MIT diff --git a/pyproject.toml b/pyproject.toml index f013ec1..d8b65bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ dynamic = ["dependencies", "optional-dependencies"] [tool.setuptools] # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, # CHANGE `py_modules = ['...']` TO `packages = ['...']` -py-modules = ["adafruit_usb_host_mouse"] +packages = ["adafruit_usb_host_mouse"] [tool.setuptools.dynamic] dependencies = {file = ["requirements.txt"]}