3434
3535_REQ_GET_DESCRIPTOR = const (6 )
3636
37+ _RECIP_INTERFACE = const (1 )
38+
3739# No const because these are public
3840DESC_DEVICE = 0x01
3941DESC_CONFIGURATION = 0x02
4042DESC_STRING = 0x03
4143DESC_INTERFACE = 0x04
4244DESC_ENDPOINT = 0x05
45+ DESC_HID = 0x21
46+ DESC_REPORT = 0X22
4347
4448INTERFACE_HID = 0x03
4549SUBCLASS_BOOT = 0x01
50+ SUBCLASS_REPORT = None
4651PROTOCOL_MOUSE = 0x02
4752PROTOCOL_KEYBOARD = 0x01
4853
54+ # --- HID Report Descriptor Item Tags (The "Command") ---
55+ HID_TAG_USAGE_PAGE = 0x05 # Defines the category (e.g., Generic Desktop, Game Controls)
56+ HID_TAG_USAGE = 0x09 # Defines the specific item (e.g., Mouse, Joystick)
57+
58+ # --- Usage Page IDs (Values for 0x05) ---
59+ USAGE_PAGE_GENERIC_DESKTOP = 0x01
60+
61+ # --- Usage IDs (Values for 0x09, inside Generic Desktop) ---
62+ USAGE_MOUSE = 0x02
63+ USAGE_JOYSTICK = 0x04
64+ USAGE_GAMEPAD = 0x05
65+ USAGE_KEYBOARD = 0x06
66+
4967
5068def get_descriptor (device , desc_type , index , buf , language_id = 0 ):
5169 """Fetch the descriptor from the device into buf."""
@@ -82,35 +100,139 @@ def get_configuration_descriptor(device, index):
82100 get_descriptor (device , DESC_CONFIGURATION , index , full_buf )
83101 return full_buf
84102
103+ def get_report_descriptor (device , interface_num , length ):
104+ """
105+ Fetches the HID Report Descriptor.
106+ This tells us what the device actually IS (Mouse vs Joystick).
107+ """
108+ buf = bytearray (length )
109+ try :
110+ # 0x81 = Dir: IN | Type: Standard | Recipient: Interface
111+ # wValue = 0x2200 (Report Descriptor)
112+ device .ctrl_transfer (
113+ _RECIP_INTERFACE | _REQ_TYPE_STANDARD | _DIR_IN ,
114+ _REQ_GET_DESCRIPTOR ,
115+ DESC_REPORT << 8 ,
116+ interface_num ,
117+ buf
118+ )
119+ return buf
120+ except Exception as e :
121+ print (f"Failed to read Report Descriptor: { e } " )
122+ return None
123+
124+ def _is_confirmed_mouse (report_desc ):
125+ """
126+ Scans the raw descriptor bytes for:
127+ Usage Page (Generic Desktop) = 0x05, 0x01
128+ Usage (Mouse) = 0x09, 0x02
129+ """
130+ if not report_desc :
131+ return False
132+
133+ # Simple byte scan check
134+ # We look for Usage Page Generic Desktop (0x05 0x01)
135+ has_generic_desktop = False
136+ for i in range (len (report_desc ) - 1 ):
137+ if report_desc [i ] == HID_TAG_USAGE_PAGE and report_desc [i + 1 ] == USAGE_PAGE_GENERIC_DESKTOP :
138+ has_generic_desktop = True
139+
140+ # We look for Usage Mouse (0x09 0x02)
141+ has_mouse_usage = False
142+ for i in range (len (report_desc ) - 1 ):
143+ if report_desc [i ] == HID_TAG_USAGE and report_desc [i + 1 ] == USAGE_MOUSE :
144+ has_mouse_usage = True
145+
146+ return has_generic_desktop and has_mouse_usage
147+
148+
149+ def _find_endpoint (device , count , protocol_type : Literal [PROTOCOL_MOUSE , PROTOCOL_KEYBOARD ], subclass ):
150+ # pass a count of <= 0 to return all HID interfaces/endpoints of selected protocol_type on the device
85151
86- def _find_boot_endpoint (device , protocol_type : Literal [PROTOCOL_MOUSE , PROTOCOL_KEYBOARD ]):
87152 config_descriptor = get_configuration_descriptor (device , 0 )
88153 i = 0
89154 mouse_interface_index = None
90155 found_mouse = False
156+ candidate_found = False
157+ hid_desc_len = 0
158+ endpoints = []
91159 while i < len (config_descriptor ):
92160 descriptor_len = config_descriptor [i ]
93161 descriptor_type = config_descriptor [i + 1 ]
162+
163+ # Found Interface
94164 if descriptor_type == DESC_INTERFACE :
95165 interface_number = config_descriptor [i + 2 ]
96166 interface_class = config_descriptor [i + 5 ]
97167 interface_subclass = config_descriptor [i + 6 ]
98168 interface_protocol = config_descriptor [i + 7 ]
169+
170+ # Reset checks
171+ candidate_found = False
172+ hid_desc_len = 0
173+
174+ # Found mouse or keyboard interface depending on what was requested
99175 if (
100176 interface_class == INTERFACE_HID
101- and interface_subclass == SUBCLASS_BOOT
102177 and interface_protocol == protocol_type
178+ and interface_subclass == SUBCLASS_BOOT
179+ and subclass == SUBCLASS_BOOT
103180 ):
104181 found_mouse = True
105182 mouse_interface_index = interface_number
183+
184+ # May be trackpad interface if it's not a keyboard and looking for mouse
185+ elif (
186+ interface_class == INTERFACE_HID
187+ and interface_protocol != PROTOCOL_KEYBOARD
188+ and protocol_type == PROTOCOL_MOUSE
189+ and subclass != SUBCLASS_BOOT
190+ ):
191+ candidate_found = True
192+
193+ # Found HID Descriptor (Contains Report Length)
194+ elif descriptor_type == DESC_HID and candidate_found :
195+ # The HID descriptor stores the Report Descriptor length at offset 7
196+ # Bytes: [Length, Type, BCD, BCD, Country, Count, ReportType, ReportLenL, ReportLenH]
197+ if descriptor_len >= 9 :
198+ hid_desc_len = config_descriptor [i + 7 ] + (config_descriptor [i + 8 ] << 8 )
106199
107200 elif descriptor_type == DESC_ENDPOINT :
108201 endpoint_address = config_descriptor [i + 2 ]
109202 if endpoint_address & _DIR_IN :
110203 if found_mouse :
111- return mouse_interface_index , endpoint_address
204+ endpoints .append ((mouse_interface_index , endpoint_address ))
205+ if len (endpoints ) == count :
206+ return endpoints
207+
208+ elif candidate_found :
209+ print (f"Checking Interface { interface_number } ..." )
210+
211+ # If it's Protocol 2, it's definitely a mouse (Standard).
212+ # If it's Protocol 0, we must check the descriptor.
213+ is_mouse = False
214+
215+ if hid_desc_len > 0 :
216+ rep_desc = get_report_descriptor (device , interface_number , hid_desc_len )
217+ if _is_confirmed_mouse (rep_desc ):
218+ is_mouse = True
219+ print (f" -> CONFIRMED: It is a Mouse/Trackpad (Usage 0x09 0x02)" )
220+ else :
221+ print (f" -> REJECTED: Generic HID, but not a mouse (Joystick/Ups?)" )
222+ else :
223+ # Fallback if we missed the HID descriptor, assume no if Candidate
224+ print (" -> Warning: Could not verify Usage, assuming no." )
225+ is_mouse = False
226+
227+ if is_mouse :
228+ endpoints .append ((interface_number , endpoint_address ))
229+ if len (endpoints ) == count :
230+ return endpoints
231+ else :
232+ candidate_found = False # Stop looking at this interface
233+
112234 i += descriptor_len
113- return None , None
235+ return [( None , None )]
114236
115237
116238def find_boot_mouse_endpoint (device ):
@@ -120,8 +242,16 @@ def find_boot_mouse_endpoint(device):
120242 :param device: The device to search within
121243 :return: mouse_interface_index, mouse_endpoint_address if found, or None, None otherwise
122244 """
123- return _find_boot_endpoint (device , PROTOCOL_MOUSE )
245+ return _find_endpoint (device , 1 , PROTOCOL_MOUSE , SUBCLASS_BOOT )[ 0 ]
124246
247+ def find_report_mouse_endpoint (device ):
248+ """
249+ Try to find a report mouse endpoint in the device and return its
250+ interface index, and endpoint address.
251+ :param device: The device to search within
252+ :return: mouse_interface_index, mouse_endpoint_address if found, or None, None otherwise
253+ """
254+ return _find_endpoint (device , 1 , PROTOCOL_MOUSE , SUBCLASS_REPORT )[0 ]
125255
126256def find_boot_keyboard_endpoint (device ):
127257 """
@@ -130,4 +260,5 @@ def find_boot_keyboard_endpoint(device):
130260 :param device: The device to search within
131261 :return: keyboard_interface_index, keyboard_endpoint_address if found, or None, None otherwise
132262 """
133- return _find_boot_endpoint (device , PROTOCOL_KEYBOARD )
263+ return _find_endpoint (device , 1 , PROTOCOL_KEYBOARD , SUBCLASS_BOOT )[0 ]
264+
0 commit comments