4343BUTTONS = ["left" , "right" , "middle" ]
4444DEFAULT_CURSOR = "/" .join (__file__ .split ("/" )[:- 1 ]) + "/mouse_cursor.bmp"
4545
46+ SUBCLASS_BOOT = 0x01
47+ SUBCLASS_RESERVED = 0x00
4648
47- def find_and_init_boot_mouse (cursor_image = DEFAULT_CURSOR ): # noqa: PLR0912
49+
50+ def find_and_init_mouse (cursor_image = DEFAULT_CURSOR , subclass = SUBCLASS_BOOT ):
4851 """
49- Scan for an attached boot mouse connected via USB host.
50- If one is found initialize an instance of :class:`BootMouse` class
51- and return it.
52+ Scan for an attached mouse connected via USB host.
53+ If one is found return a tuple containing the parameters needed to initalize an
54+ instance of :class: `BootMouse` or :class: `ReportMouse` depending on the value of
55+ the subclass parameter.
5256
5357 :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as
54- `None`, the :class:`BootMouse` instance will not control a :class:`displayio.TileGrid` object.
55- :return: The :class:`BootMouse` instance or None if no mouse was found.
58+ `None`, the object instance created using the returned tuple will not control
59+ a :class:`displayio.TileGrid` object.
60+ :param subclass: Defines whether to search for boot or non-boot mice.
61+ SUBCLASS_BOOT (0X01), a boot mouse will be searched for
62+ SUBCLASS_RESERVED (0x00), a non-boot (report) mouse will be searched for
63+ :return: A tupple cotaining the arguments needed by the calling find and init helper
64+ function. If no mouse is found None is returned.
5665 """
5766 mouse_interface_index , mouse_endpoint_address = None , None
5867 mouse_device = None
68+ deviceType , find_endpoint = (
69+ ("boot" , adafruit_usb_host_descriptors .find_boot_mouse_endpoint )
70+ if subclass == SUBCLASS_BOOT
71+ else ("report" , adafruit_usb_host_descriptors .find_report_mouse_endpoint )
72+ )
5973
6074 # scan for connected USB device and loop over any found
61- print ("scanning usb (boot )" )
75+ print (f "scanning usb ({ deviceType } )" )
6276 for device in usb .core .find (find_all = True ):
6377 # print device info
6478 try :
@@ -76,9 +90,7 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912
7690 )
7791 print (config_descriptor )
7892
79- _possible_interface_index , _possible_endpoint_address = (
80- adafruit_usb_host_descriptors .find_boot_mouse_endpoint (device )
81- )
93+ _possible_interface_index , _possible_endpoint_address = find_endpoint (device )
8294 if _possible_interface_index is not None and _possible_endpoint_address is not None :
8395 mouse_device = device
8496 mouse_interface_index = _possible_interface_index
@@ -88,23 +100,25 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912
88100 + f"endpoint_address: { hex (mouse_endpoint_address )} "
89101 )
90102 break
91- print ("was not a boot mouse" )
103+ print (f "was not a { deviceType } mouse" )
92104 except usb .core .USBError as e :
93105 print_exception (e , e , None )
94106
95- mouse_was_attached = None
107+ mouse_was_attached = []
96108 if mouse_device is not None :
97109 # detach the kernel driver if needed
98- if mouse_device .is_kernel_driver_active (0 ):
99- mouse_was_attached = True
100- mouse_device .detach_kernel_driver (0 )
101- else :
102- mouse_was_attached = False
110+ possible_interfaces = [mouse_interface_index ] if subclass == SUBCLASS_BOOT else [0 , 1 , 2 ]
111+
112+ for intf in possible_interfaces :
113+ if mouse_device .is_kernel_driver_active (intf ):
114+ mouse_was_attached .append (intf )
115+ mouse_device .detach_kernel_driver (intf )
103116
104117 # set configuration on the mouse so we can use it
105118 mouse_device .set_configuration ()
106119
107120 # load the mouse cursor bitmap
121+ mouse_tg = None
108122 if isinstance (cursor_image , str ):
109123 mouse_bmp = OnDiskBitmap (cursor_image )
110124
@@ -114,92 +128,51 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912
114128 # create a TileGrid for the mouse, using its bitmap and pixel_shader
115129 mouse_tg = TileGrid (mouse_bmp , pixel_shader = mouse_bmp .pixel_shader )
116130
117- else :
118- mouse_tg = None
119-
120- return BootMouse ( mouse_device , mouse_endpoint_address , mouse_was_attached , mouse_tg )
131+ return (
132+ ( mouse_device , mouse_interface_index , mouse_endpoint_address , mouse_was_attached ),
133+ mouse_tg ,
134+ )
121135
122136 # if no mouse found
123137 return None
124138
125139
126- def find_and_init_report_mouse (cursor_image = DEFAULT_CURSOR ): # noqa: PLR0912
140+ def find_and_init_boot_mouse (cursor_image = DEFAULT_CURSOR , scale = 1 ):
141+ """
142+ Scan for an attached boot mouse connected via USB host.
143+ If one is found initialize an instance of :class:`BootMouse` class
144+ and return it.
145+
146+ :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as
147+ `None`, the :class:`BootMouse` instance will not control a :class:`displayio.TileGrid` object
148+ :param scale: The scale of the group that the Mouse TileGrid will be put into
149+ Needed in order to properly clamp the mouse to the display bounds
150+ :return: The :class:`BootMouse` instance or None if no mouse was found.
151+ """
152+ found_mouse = find_and_init_mouse (cursor_image , SUBCLASS_BOOT )
153+ if found_mouse is not None :
154+ return BootMouse (* found_mouse [0 ], tilegrid = found_mouse [1 ], scale = scale )
155+ else :
156+ return None
157+
158+
159+ def find_and_init_report_mouse (cursor_image = DEFAULT_CURSOR , scale = 1 ):
127160 """
128161 Scan for an attached report mouse connected via USB host.
129162 If one is found initialize an instance of :class:`ReportMouse` class
130163 and return it.
131164
132165 :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as
133166 `None`, the :class:`ReportMouse` will not control a :class:`displayio.TileGrid` object.
167+ :param scale: The scale of the group that the Mouse TileGrid will be put into
168+ Needed in order to properly clamp the mouse to the display bounds
134169 :return: The :class:`ReportMouse` instance or None if no mouse was found.
135170 """
136- mouse_interface_index , mouse_endpoint_address = None , None
137- mouse_device = None
138-
139- # scan for connected USB device and loop over any found
140- print ("scanning usb (report)" )
141- for device in usb .core .find (find_all = True ):
142- # print device info
143- try :
144- try :
145- print (f"{ device .idVendor :04x} :{ device .idProduct :04x} " )
146- except usb .core .USBError as e :
147- print_exception (e , e , None )
148- try :
149- print (device .manufacturer , device .product )
150- except usb .core .USBError as e :
151- print_exception (e , e , None )
152- print ()
153- config_descriptor = adafruit_usb_host_descriptors .get_configuration_descriptor (
154- device , 0
155- )
156- print (config_descriptor )
157-
158- _possible_interface_index , _possible_endpoint_address = (
159- adafruit_usb_host_descriptors .find_report_mouse_endpoint (device )
160- )
161- if _possible_interface_index is not None and _possible_endpoint_address is not None :
162- mouse_device = device
163- mouse_interface_index = _possible_interface_index
164- mouse_endpoint_address = _possible_endpoint_address
165- print (
166- f"mouse interface: { mouse_interface_index } "
167- + f"endpoint_address: { hex (mouse_endpoint_address )} "
168- )
169- break
170- print ("was not a report mouse" )
171- except usb .core .USBError as e :
172- print_exception (e , e , None )
173-
174- mouse_was_attached = None
175- if mouse_device is not None :
176- # detach the kernel driver if needed
177- if mouse_device .is_kernel_driver_active (0 ):
178- mouse_was_attached = True
179- mouse_device .detach_kernel_driver (0 )
180- else :
181- mouse_was_attached = False
182-
183- # set configuration on the mouse so we can use it
184- mouse_device .set_configuration ()
185-
186- # load the mouse cursor bitmap
187- if isinstance (cursor_image , str ):
188- mouse_bmp = OnDiskBitmap (cursor_image )
189-
190- # make the background pink pixels transparent
191- mouse_bmp .pixel_shader .make_transparent (0 )
192-
193- # create a TileGrid for the mouse, using its bitmap and pixel_shader
194- mouse_tg = TileGrid (mouse_bmp , pixel_shader = mouse_bmp .pixel_shader )
195-
196- else :
197- mouse_tg = None
198-
199- return ReportMouse (mouse_device , mouse_endpoint_address , mouse_was_attached , mouse_tg )
200-
201- # if no mouse found
202- return None
171+ found_mouse = find_and_init_mouse (cursor_image , SUBCLASS_RESERVED )
172+ if found_mouse is not None :
173+ return ReportMouse (* found_mouse [0 ], tilegrid = found_mouse [1 ], scale = scale )
174+ else :
175+ return None
203176
204177
205178class BootMouse :
@@ -209,19 +182,23 @@ class BootMouse:
209182 were pressed.
210183
211184 :param device: The usb device instance for the mouse
185+ :param interface_index: The USB interface index of the mouse
212186 :param endpoint_address: The address of the mouse endpoint
213- :param was_attached: Whether the usb device was attached to the kernel
187+ :param was_attached: A list of the usb devices detached from the kernel
214188 :param tilegrid: The TileGrid that holds the visible mouse cursor
215- :param scale: The scale of the group that the Mouse TileGrid will be put into.
189+ :param scale: The scale of the group that the Mouse TileGrid will be put into
216190 Needed in order to properly clamp the mouse to the display bounds
217191 """
218192
219- def __init__ (self , device , endpoint_address , was_attached , tilegrid = None , scale = 1 ): # noqa: PLR0913, too many args
193+ def __init__ ( # noqa: PLR0913, too many args
194+ self , device , interface_index , endpoint_address , was_attached , * , tilegrid = None , scale = 1
195+ ):
220196 self .device = device
221197
222198 self .tilegrid = tilegrid
223199 """TileGrid containing the Mouse cursor graphic."""
224200
201+ self .interface = interface_index
225202 self .endpoint = endpoint_address
226203 self .buffer = array .array ("b" , [0 ] * 4 )
227204 self .was_attached = was_attached
@@ -283,8 +260,11 @@ def release(self):
283260 Release the mouse cursor and re-attach it to the kernel
284261 if it was attached previously.
285262 """
286- if self .was_attached and not self .device .is_kernel_driver_active (0 ):
287- self .device .attach_kernel_driver (0 )
263+ # was_attached is a list of interfaces detached from the kernel or
264+ # an empty list if no interfaces were detached
265+ for intf in self .was_attached :
266+ if not self .device .is_kernel_driver_active (intf ):
267+ self .device .attach_kernel_driver (intf )
288268
289269 def update (self ):
290270 """
@@ -337,8 +317,26 @@ def update(self):
337317
338318
339319class ReportMouse (BootMouse ):
340- def __init__ (self , device , endpoint_address , was_attached , tilegrid = None , scale = 1 ): # noqa: PLR0913, too many args
341- super ().__init__ (device , endpoint_address , was_attached , tilegrid , scale )
320+ """
321+ Helpler class that encapsulates the objects needed to interact with a non-Boot
322+ mouse (Report), show a visible cursor on the display, and determine when buttons
323+ were pressed. The class is a subclass of BootMouse that overrides the update method.
324+
325+ :param device: The usb device instance for the mouse
326+ :param interface_index: The USB interface index of the mouse
327+ :param endpoint_address: The address of the mouse endpoint
328+ :param was_attached: A list of the usb devices detached from the kernel
329+ :param tilegrid: The TileGrid that holds the visible mouse cursor
330+ :param scale: The scale of the group that the Mouse TileGrid will be put into
331+ Needed in order to properly clamp the mouse to the display bounds
332+ """
333+
334+ def __init__ ( # noqa: PLR0913, too many args
335+ self , device , interface_index , endpoint_address , was_attached , * , tilegrid = None , scale = 1
336+ ):
337+ super ().__init__ (
338+ device , interface_index , endpoint_address , was_attached , tilegrid = tilegrid , scale = scale
339+ )
342340
343341 def update (self ):
344342 """
0 commit comments