From b5807737bd60602d3198fcb0036e7e565c785a4b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 21 Dec 2022 09:56:46 -0600 Subject: [PATCH] Add code for mouse support in NeXT keyboard --- CircuitPython_NeXT_Keyboard_RP2040/code.py | 67 ++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/CircuitPython_NeXT_Keyboard_RP2040/code.py b/CircuitPython_NeXT_Keyboard_RP2040/code.py index e78701f91..ac488eea9 100644 --- a/CircuitPython_NeXT_Keyboard_RP2040/code.py +++ b/CircuitPython_NeXT_Keyboard_RP2040/code.py @@ -10,6 +10,7 @@ from keypad import Keys from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard import Keycode +from adafruit_hid.mouse import Mouse from adafruit_pioasm import Program from adafruit_ticks import ticks_add, ticks_less, ticks_ms from next_keycode import ( @@ -21,6 +22,14 @@ from next_keycode import ( shift_modifiers, ) + +# Compared to a modern mouse, the DPI of the NeXT mouse is low. Increasing this +# number makes the pointer move further faster, but it also makes moves chunky. +# Customize this number according to the trade-off you want, but also check +# whether your operating system can assign a higher "sensitivity" or +# "acceleration" for the mouse. +MOUSE_SCALE = 8 + # Customize the power key's keycode. You can change it to `Keycode.POWER` if # you really want to accidentally power off your computer! POWER_KEY_SENDS = Keycode.F1 @@ -79,7 +88,8 @@ def set_leds(i): return pack_message_str(f"0000000001110{i:02b}0000000") -QUERY = pack_message_str("000001000", 1) +QUERY = pack_message_str("000001000", True) +MOUSEQUERY = pack_message_str("010001000", True) RESET = pack_message_str("0111101111110000000000") BIT_BREAK = 1 << 11 @@ -94,12 +104,19 @@ def is_mod_report(report): return not bool(report & BIT_MOD) +def extract_bits(report, *positions): + result = 0 + for p in positions: + result = (result << 1) + if report & (1 << p): + result |= 1 + #result = (result << 1) | bool(report & (1<> 12:07b}" - b = "".join(reversed(b)) - return int(b, 2) + return extract_bits(report, 12, 13, 14, 15, 16, 17, 18) def modifiers(report): @@ -122,11 +139,18 @@ sm = rp2pio.StateMachine( ) +def signfix(num, sign_pos): + """Fix a signed number if the bit with weight `sign_pos` is actually the sign bit""" + if num & sign_pos: + return num - 2*sign_pos + return num + class KeyboardHandler: def __init__(self): self.old_modifiers = 0 self.cc = ConsumerControl(usb_hid.devices) self.kbd = Keyboard(usb_hid.devices) + self.mouse = Mouse(usb_hid.devices) def set_key_state(self, key, state): if state: @@ -144,6 +168,27 @@ class KeyboardHandler: else: self.kbd.release(key) + def handle_mouse_report(self, report): + if report == 1536: # the "nothing happened" report + return + + dx = extract_bits(report, 11,12,13,14,15,16,17) + dx = -signfix(dx, 64) + dy = extract_bits(report, 0,1,2,3,4,5,6) + dy = -signfix(dy, 64) + b1 = not extract_bits(report, 18) + b2 = not extract_bits(report, 7) + + self.mouse.report[0] = ( + Mouse.MIDDLE_BUTTON if (b1 and b2) else + Mouse.LEFT_BUTTON if b1 else + Mouse.RIGHT_BUTTON if b2 + else 0) + if dx or dy: + self.mouse.move(dx * MOUSE_SCALE, dy * MOUSE_SCALE) + else: + self.mouse._send_no_move() # pylint: disable=protected-access + def handle_report(self, report_value): if report_value == 1536: # the "nothing happened" report return @@ -205,5 +250,19 @@ try: sm.restart() sm.write(RESET) time.sleep(0.1) + + sm.write(MOUSEQUERY) + deadline = ticks_add(ticks_ms(), 100) + while ticks_less(ticks_ms(), deadline): + if sm.in_waiting: + sm.readinto(recv_buf) + value = recv_buf[0] + handler.handle_mouse_report(value) + break + else: + print("keyboard did not respond - resetting") + sm.restart() + sm.write(RESET) + time.sleep(0.1) finally: # Release all keys before e.g., code is reloaded handler.kbd.release_all()