@@ -58,7 +58,7 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912
5858 mouse_device = None
5959
6060 # scan for connected USB device and loop over any found
61- print ("scanning usb" )
61+ print ("scanning usb (boot) " )
6262 for device in usb .core .find (find_all = True ):
6363 # print device info
6464 try :
@@ -123,6 +123,85 @@ def find_and_init_boot_mouse(cursor_image=DEFAULT_CURSOR): # noqa: PLR0912
123123 return None
124124
125125
126+ def find_and_init_report_mouse (cursor_image = DEFAULT_CURSOR ): # noqa: PLR0912
127+ """
128+ Scan for an attached report mouse connected via USB host.
129+ If one is found initialize an instance of :class:`ReportMouse` class
130+ and return it.
131+
132+ :param cursor_image: Provide the absolute path to the desired cursor bitmap image. If set as
133+ `None`, the :class:`ReportMouse` will not control a :class:`displayio.TileGrid` object.
134+ :return: The :class:`ReportMouse` instance or None if no mouse was found.
135+ """
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
203+
204+
126205class BootMouse :
127206 """
128207 Helpler class that encapsulates the objects needed to interact with a boot
@@ -229,6 +308,8 @@ def update(self):
229308
230309 # update the mouse x and y coordinates
231310 # based on the delta values read from the mouse
311+ # Standard Boot Mouse: 3 bytes [Btn, X, Y]
312+
232313 dx , dy = self .buffer [1 :3 ]
233314 dx = int (round ((dx / self .sensitivity ), 0 ))
234315 dy = int (round ((dy / self .sensitivity ), 0 ))
@@ -253,3 +334,58 @@ def update(self):
253334 self .pressed_btns .append (button )
254335
255336 return tuple (self .pressed_btns )
337+
338+
339+ class 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 )
342+
343+ def update (self ):
344+ """
345+ Read data from the USB mouse and update the location of the visible cursor
346+ and check if any buttons are pressed.
347+
348+ :return: a tuple containing one or more of the strings "left", "right", "middle"
349+ indicating which buttons are pressed. If no buttons are pressed, the tuple will be empty.
350+ If a error occurred while trying to read from the usb device, `None` will be returned.
351+ """
352+ try :
353+ # attempt to read data from the mouse
354+ # 20ms timeout, so we don't block long if there
355+ # is no data
356+ count = self .device .read (self .endpoint , self .buffer , timeout = 20 ) # noqa: F841, var assigned but not used
357+ except usb .core .USBTimeoutError :
358+ # skip the rest if there is no data
359+ return None
360+ except usb .core .USBError :
361+ return None
362+
363+ # update the mouse x and y coordinates
364+ # based on the delta values read from the mouse
365+ # Mouse with Report ID: 4 bytes [ID, Btn, X, Y]
366+ offset = 1
367+
368+ dx , dy = self .buffer [1 + offset : 3 + offset ]
369+ dx = int (round ((dx / self .sensitivity ), 0 ))
370+ dy = int (round ((dy / self .sensitivity ), 0 ))
371+ if self .tilegrid :
372+ self .tilegrid .x = max (
373+ 0 , min ((self .display_size [0 ] // self .scale ) - 1 , self .tilegrid .x + dx )
374+ )
375+ self .tilegrid .y = max (
376+ 0 , min ((self .display_size [1 ] // self .scale ) - 1 , self .tilegrid .y + dy )
377+ )
378+ else :
379+ self ._x += dx
380+ self ._y += dy
381+
382+ self .pressed_btns = []
383+ for i , button in enumerate (BUTTONS ):
384+ # check if each button is pressed using bitwise AND shifted
385+ # to the appropriate index for this button
386+ if self .buffer [0 + offset ] & (1 << i ) != 0 :
387+ # append the button name to the string to show if
388+ # it is being clicked.
389+ self .pressed_btns .append (button )
390+
391+ return tuple (self .pressed_btns )
0 commit comments