Skip to content

Commit 84138ca

Browse files
authored
Merge pull request #9 from RetiredWizard/trackpad
Trackpad
2 parents 5b7eedc + e2e6652 commit 84138ca

File tree

1 file changed

+122
-4
lines changed

1 file changed

+122
-4
lines changed

adafruit_usb_host_descriptors.py

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import struct
1414

15+
import usb
1516
from micropython import const
1617

1718
try:
@@ -34,18 +35,36 @@
3435

3536
_REQ_GET_DESCRIPTOR = const(6)
3637

38+
_RECIP_INTERFACE = const(1)
39+
3740
# No const because these are public
3841
DESC_DEVICE = 0x01
3942
DESC_CONFIGURATION = 0x02
4043
DESC_STRING = 0x03
4144
DESC_INTERFACE = 0x04
4245
DESC_ENDPOINT = 0x05
46+
DESC_HID = 0x21
47+
DESC_REPORT = 0x22
4348

4449
INTERFACE_HID = 0x03
4550
SUBCLASS_BOOT = 0x01
51+
SUBCLASS_RESERVED = 0x00
4652
PROTOCOL_MOUSE = 0x02
4753
PROTOCOL_KEYBOARD = 0x01
4854

55+
# --- HID Report Descriptor Item Tags (The "Command") ---
56+
HID_TAG_USAGE_PAGE = 0x05 # Defines the category (e.g., Generic Desktop, Game Controls)
57+
HID_TAG_USAGE = 0x09 # Defines the specific item (e.g., Mouse, Joystick)
58+
59+
# --- Usage Page IDs (Values for 0x05) ---
60+
USAGE_PAGE_GENERIC_DESKTOP = 0x01
61+
62+
# --- Usage IDs (Values for 0x09, inside Generic Desktop) ---
63+
USAGE_MOUSE = 0x02
64+
USAGE_JOYSTICK = 0x04
65+
USAGE_GAMEPAD = 0x05
66+
USAGE_KEYBOARD = 0x06
67+
4968

5069
def get_descriptor(device, desc_type, index, buf, language_id=0):
5170
"""Fetch the descriptor from the device into buf."""
@@ -83,32 +102,121 @@ def get_configuration_descriptor(device, index):
83102
return full_buf
84103

85104

86-
def _find_boot_endpoint(device, protocol_type: Literal[PROTOCOL_MOUSE, PROTOCOL_KEYBOARD]):
105+
def get_report_descriptor(device, interface_num, length):
106+
"""
107+
Fetches the HID Report Descriptor.
108+
This tells us what the device actually IS (Mouse vs Joystick).
109+
"""
110+
if length < 1:
111+
return None
112+
113+
buf = bytearray(length)
114+
try:
115+
# 0x81 = Dir: IN | Type: Standard | Recipient: Interface
116+
# wValue = 0x2200 (Report Descriptor)
117+
device.ctrl_transfer(
118+
_RECIP_INTERFACE | _REQ_TYPE_STANDARD | _DIR_IN,
119+
_REQ_GET_DESCRIPTOR,
120+
DESC_REPORT << 8,
121+
interface_num,
122+
buf,
123+
)
124+
return buf
125+
except usb.core.USBError as e:
126+
print(f"Failed to read Report Descriptor: {e}")
127+
return None
128+
129+
130+
def _is_confirmed_mouse(report_desc):
131+
"""
132+
Scans the raw descriptor bytes for:
133+
Usage Page (Generic Desktop) = 0x05, 0x01
134+
Usage (Mouse) = 0x09, 0x02
135+
"""
136+
if not report_desc:
137+
return False
138+
139+
# Simple byte scan check
140+
# We look for Usage Page Generic Desktop (0x05 0x01)
141+
has_generic_desktop = False
142+
for i in range(len(report_desc) - 1):
143+
if (
144+
report_desc[i] == HID_TAG_USAGE_PAGE
145+
and report_desc[i + 1] == USAGE_PAGE_GENERIC_DESKTOP
146+
):
147+
has_generic_desktop = True
148+
149+
# We look for Usage Mouse (0x09 0x02)
150+
has_mouse_usage = False
151+
for i in range(len(report_desc) - 1):
152+
if report_desc[i] == HID_TAG_USAGE and report_desc[i + 1] == USAGE_MOUSE:
153+
has_mouse_usage = True
154+
155+
return has_generic_desktop and has_mouse_usage
156+
157+
158+
def _find_endpoint(device, protocol_type: Literal[PROTOCOL_MOUSE, PROTOCOL_KEYBOARD], subclass):
87159
config_descriptor = get_configuration_descriptor(device, 0)
88160
i = 0
89161
mouse_interface_index = None
90162
found_mouse = False
163+
candidate_found = False
164+
hid_desc_len = 0
91165
while i < len(config_descriptor):
92166
descriptor_len = config_descriptor[i]
93167
descriptor_type = config_descriptor[i + 1]
168+
169+
# Found Interface
94170
if descriptor_type == DESC_INTERFACE:
95171
interface_number = config_descriptor[i + 2]
96172
interface_class = config_descriptor[i + 5]
97173
interface_subclass = config_descriptor[i + 6]
98174
interface_protocol = config_descriptor[i + 7]
175+
176+
# Reset checks
177+
candidate_found = False
178+
hid_desc_len = 0
179+
180+
# Found mouse or keyboard interface depending on what was requested
99181
if (
100182
interface_class == INTERFACE_HID
101-
and interface_subclass == SUBCLASS_BOOT
102183
and interface_protocol == protocol_type
184+
and interface_subclass == SUBCLASS_BOOT
185+
and subclass == SUBCLASS_BOOT
103186
):
104187
found_mouse = True
105188
mouse_interface_index = interface_number
106189

190+
# May be trackpad interface if it's not a keyboard and looking for mouse
191+
elif (
192+
interface_class == INTERFACE_HID
193+
and interface_protocol != PROTOCOL_KEYBOARD
194+
and protocol_type == PROTOCOL_MOUSE
195+
and subclass == SUBCLASS_RESERVED
196+
):
197+
candidate_found = True
198+
mouse_interface_index = interface_number
199+
200+
# Found HID Descriptor (Contains Report Length)
201+
elif descriptor_type == DESC_HID and candidate_found:
202+
# The HID descriptor stores the Report Descriptor length at offset 7
203+
# Bytes: [Length, Type, BCD, BCD, Country, Count, ReportType, ReportLenL, ReportLenH]
204+
if descriptor_len >= 9:
205+
hid_desc_len = config_descriptor[i + 7] + (config_descriptor[i + 8] << 8)
206+
107207
elif descriptor_type == DESC_ENDPOINT:
108208
endpoint_address = config_descriptor[i + 2]
109209
if endpoint_address & _DIR_IN:
110210
if found_mouse:
111211
return mouse_interface_index, endpoint_address
212+
213+
elif candidate_found:
214+
rep_desc = get_report_descriptor(device, mouse_interface_index, hid_desc_len)
215+
if _is_confirmed_mouse(rep_desc):
216+
return mouse_interface_index, endpoint_address
217+
218+
candidate_found = False # Stop looking at this interface
219+
112220
i += descriptor_len
113221
return None, None
114222

@@ -120,7 +228,17 @@ def find_boot_mouse_endpoint(device):
120228
:param device: The device to search within
121229
:return: mouse_interface_index, mouse_endpoint_address if found, or None, None otherwise
122230
"""
123-
return _find_boot_endpoint(device, PROTOCOL_MOUSE)
231+
return _find_endpoint(device, PROTOCOL_MOUSE, SUBCLASS_BOOT)
232+
233+
234+
def find_report_mouse_endpoint(device):
235+
"""
236+
Try to find a report mouse endpoint in the device and return its
237+
interface index, and endpoint address.
238+
:param device: The device to search within
239+
:return: mouse_interface_index, mouse_endpoint_address if found, or None, None otherwise
240+
"""
241+
return _find_endpoint(device, PROTOCOL_MOUSE, SUBCLASS_RESERVED)
124242

125243

126244
def find_boot_keyboard_endpoint(device):
@@ -130,4 +248,4 @@ def find_boot_keyboard_endpoint(device):
130248
:param device: The device to search within
131249
:return: keyboard_interface_index, keyboard_endpoint_address if found, or None, None otherwise
132250
"""
133-
return _find_boot_endpoint(device, PROTOCOL_KEYBOARD)
251+
return _find_endpoint(device, PROTOCOL_KEYBOARD, SUBCLASS_BOOT)

0 commit comments

Comments
 (0)