Skip to content

Commit 9661aa8

Browse files
authored
Merge pull request #103 from adafruit/dont-crash-on-full
Act gracefully when more than 6 keys are reported at once
2 parents f49bbee + 3e86e82 commit 9661aa8

File tree

1 file changed

+29
-18
lines changed

1 file changed

+29
-18
lines changed

adafruit_hid/keyboard.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,12 @@
1111

1212
import time
1313
from micropython import const
14+
import usb_hid
1415

1516
from .keycode import Keycode
1617

1718
from . import find_device
1819

19-
try:
20-
from typing import Sequence
21-
import usb_hid
22-
except ImportError:
23-
pass
2420

2521
_MAX_KEYPRESSES = const(6)
2622

@@ -39,7 +35,7 @@ class Keyboard:
3935

4036
# No more than _MAX_KEYPRESSES regular keys may be pressed at once.
4137

42-
def __init__(self, devices: Sequence[usb_hid.Device]) -> None:
38+
def __init__(self, devices: list[usb_hid.Device]) -> None:
4339
"""Create a Keyboard object that will send keyboard HID reports.
4440
4541
Devices can be a sequence of devices that includes a keyboard device or a keyboard device
@@ -133,19 +129,22 @@ def _add_keycode_to_report(self, keycode: int) -> None:
133129
# Set bit for this modifier.
134130
self.report_modifier[0] |= modifier
135131
else:
132+
report_keys = self.report_keys
136133
# Don't press twice.
137-
# (I'd like to use 'not in self.report_keys' here, but that's not implemented.)
138134
for i in range(_MAX_KEYPRESSES):
139-
if self.report_keys[i] == keycode:
140-
# Already pressed.
135+
report_key = report_keys[i]
136+
if report_key == 0:
137+
# Put keycode in first empty slot. Since the report_keys
138+
# are compact and unique, this is not a repeated key
139+
report_keys[i] = keycode
141140
return
142-
# Put keycode in first empty slot.
143-
for i in range(_MAX_KEYPRESSES):
144-
if self.report_keys[i] == 0:
145-
self.report_keys[i] = keycode
141+
if report_key == keycode:
142+
# Already pressed.
146143
return
147-
# All slots are filled.
148-
raise ValueError("Trying to press more than six keys at once.")
144+
# All slots are filled. Shuffle down and reuse last slot
145+
for i in range(_MAX_KEYPRESSES - 1):
146+
report_keys[i] = report_keys[i + 1]
147+
report_keys[-1] = keycode
149148

150149
def _remove_keycode_from_report(self, keycode: int) -> None:
151150
"""Remove a single keycode from the report."""
@@ -154,10 +153,22 @@ def _remove_keycode_from_report(self, keycode: int) -> None:
154153
# Turn off the bit for this modifier.
155154
self.report_modifier[0] &= ~modifier
156155
else:
157-
# Check all the slots, just in case there's a duplicate. (There should not be.)
156+
report_keys = self.report_keys
157+
# Clear the at most one matching slot and move remaining keys down
158+
j = 0
158159
for i in range(_MAX_KEYPRESSES):
159-
if self.report_keys[i] == keycode:
160-
self.report_keys[i] = 0
160+
pressed = report_keys[i]
161+
if not pressed:
162+
break # Handled all used report slots
163+
if pressed == keycode:
164+
continue # Remove this entry
165+
if i != j:
166+
report_keys[j] = report_keys[i]
167+
j += 1
168+
# Clear any remaining slots
169+
while j < _MAX_KEYPRESSES and report_keys[j]:
170+
report_keys[j] = 0
171+
j += 1
161172

162173
@property
163174
def led_status(self) -> bytes:

0 commit comments

Comments
 (0)