From 388842988dc927f440e12e701a8dab48840b2ded Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 8 Feb 2024 10:41:02 -0500 Subject: [PATCH] kbd improvements --- shared/keyboard.py | 21 ++++++++++++--------- shared/login.py | 6 +++--- shared/nfc.py | 10 ++-------- shared/numpad.py | 5 +++++ shared/scanner.py | 1 + shared/ux.py | 26 ++++++++++++++++++++++++++ shared/ux_mk4.py | 1 + shared/ux_q1.py | 17 ++++------------- 8 files changed, 54 insertions(+), 33 deletions(-) diff --git a/shared/keyboard.py b/shared/keyboard.py index 66057569..bea0619c 100644 --- a/shared/keyboard.py +++ b/shared/keyboard.py @@ -30,6 +30,7 @@ class FullKeyboard(NumpadBase): # after full scan, these flags are set for each key self.is_pressed = bytearray(NUM_ROWS * NUM_COLS) + self._char_reported = set() # what meta keys are currently pressed self.active_meta_keys = set() @@ -88,7 +89,6 @@ class FullKeyboard(NumpadBase): def _wait_any(self): # wait for any press. - #self.timer.deinit() self.waiting_for_any = True for r in self.rows: @@ -102,7 +102,7 @@ class FullKeyboard(NumpadBase): self._scan_count = 0 self.waiting_for_any = False - def _measure_irq(self, _timer): + def _measure_irq(self, _unused): # CHALLENGE: Called at high rate (61Hz), but can do memory alloc. # - sample all keys once, record any that are pressed if self.waiting_for_any: @@ -143,7 +143,7 @@ class FullKeyboard(NumpadBase): def process_chg_state(self, new_presses): # we're done a full scan (mulitple times: NUM_SAMPLES) # - convert that into ascii-like events in a Q for rest of system - # - not trying to support multiple presses, just one + # - during multiple presses, each reported once, then when "all up", another event shift_down = self.is_pressed[KEYNUM_SHIFT] symbol_down = self.is_pressed[KEYNUM_SYMBOL] status_chg = dict() @@ -175,15 +175,17 @@ class FullKeyboard(NumpadBase): continue # indicated key was found to be down and then back up - key = decoder[kn] - if key == '\0': + # - now it is a character, not a key anymore + ch = decoder[kn] + if ch == '\0': # dead/unused key: do nothing - like SYM+D #print("KEYNUM %d is no-op (in this state)" % kn) continue - if key != self.key_pressed: - #print("KEY: event=%d => %c=0x%x" % (kn, key, ord(key))) - self._key_event(key) + if ch not in self._char_reported: + #print("KEY: event=%d => %c=0x%x" % (kn, ch, ord(ch))) + self._char_reported.add(ch) + self._key_event(ch) self.lp_time = utime.ticks_ms() @@ -205,7 +207,8 @@ class FullKeyboard(NumpadBase): uasyncio.create_task(dis.async_draw_status(**status_chg)) if not any(self.is_pressed): - if self.key_pressed: + if self._char_reported: + self._char_reported.clear() self._key_event('') if utime.ticks_diff(utime.ticks_ms(), self.lp_time) > 250: diff --git a/shared/login.py b/shared/login.py index f183ec9d..1e5ebb98 100644 --- a/shared/login.py +++ b/shared/login.py @@ -4,7 +4,7 @@ # import pincodes, version, random from glob import dis -from ux import PressRelease, ux_wait_keyup, ux_show_story, ux_show_pin, ux_show_phish_words +from ux import ux_wait_keyup, ux_wait_keydown, ux_show_story, ux_show_pin, ux_show_phish_words from callgate import show_logout from pincodes import pa from uasyncio import sleep_ms @@ -76,9 +76,9 @@ class LoginUX: self.shuffle_keys() self.show_pin(True) - pr = PressRelease('0123456789y'+KEY_ENTER) while 1: - ch = await pr.wait() + ch = await ux_wait_keydown(None, 10000) + if ch is None: continue if has_qwerty and not self.is_setting and ch.upper() == self.kill_btn: # wipe the seed if they press a special key diff --git a/shared/nfc.py b/shared/nfc.py index 4d4d664b..ecf20750 100644 --- a/shared/nfc.py +++ b/shared/nfc.py @@ -14,7 +14,7 @@ from ustruct import pack, unpack from ubinascii import unhexlify as a2b_hex from ubinascii import b2a_base64, a2b_base64 -from ux import ux_show_story, ux_wait_keyup +from ux import ux_show_story, ux_wait_keydown from utils import B2A, problem_file_line, parse_addr_fmt_str from public_constants import AF_CLASSIC from charcodes import KEY_ENTER, KEY_CANCEL @@ -322,7 +322,6 @@ class NFCHandler: # - user can press OK during this period if they know they are done min_delay = (3000 if write_mode else 1000) - first = True while 1: if dis.has_lcd: phase = (phase + 1) % 2 @@ -334,12 +333,7 @@ class NFCHandler: dis.show() # wait for key or 250ms animation delay - try: - ch = await asyncio.wait_for_ms(ux_wait_keyup(flush=first), 250) - except asyncio.TimeoutError: - ch = None - - first = False + ch = await ux_wait_keydown(KEY_ENTER+KEY_CANCEL+'xy', 250) if self.last_edge: self.last_edge = 0 diff --git a/shared/numpad.py b/shared/numpad.py index 32cdd442..8b30eb50 100644 --- a/shared/numpad.py +++ b/shared/numpad.py @@ -43,6 +43,11 @@ class NumpadBase: self._changes.put_nowait(key) self._changes.put_nowait('') + def clear_pressed(self): + # clear any key that is down right now, but don't generate + # a key-up event for it either + self.key_pressed = '' + def _key_event(self, key): if key != self.key_pressed: # annouce change diff --git a/shared/scanner.py b/shared/scanner.py index d3370d5d..4b7a6aa8 100644 --- a/shared/scanner.py +++ b/shared/scanner.py @@ -206,6 +206,7 @@ class QRScanner: break except asyncio.CancelledError: + print('scan cancel') return None finally: # Problem: another valid scan can come in just as we are trying diff --git a/shared/ux.py b/shared/ux.py index 8edec0af..4bb01594 100644 --- a/shared/ux.py +++ b/shared/ux.py @@ -122,6 +122,32 @@ async def ux_wait_keyup(expected=None, flush=False): armed = ch +async def ux_wait_keydown(allowed, timeout_ms): + # Wait for PRESS (not press+release) of any key. Return it and arrange so + # that the later release doesn't cause confusion. + # - no key repeat here + from glob import numpad + + for t in range(timeout_ms): + if numpad.empty(): + await sleep_ms(1) + continue + + ch = numpad.get_nowait() + + if ch == numpad.ABORT_KEY: + raise AbortInteraction() + + if ch == '' or (allowed and (ch not in allowed)): + # keyup or unwanted + continue + + numpad.clear_pressed() + return ch + + # timeout + return None + def ux_poll_key(): # non-blocking check if any key is pressed # - responds to key down only diff --git a/shared/ux_mk4.py b/shared/ux_mk4.py index cbae2200..0f149164 100644 --- a/shared/ux_mk4.py +++ b/shared/ux_mk4.py @@ -59,6 +59,7 @@ class PressRelease: self.last_key = ch return ch + async def ux_confirm(msg): # confirmation screen, with stock title and Y=of course. from ux import ux_show_story diff --git a/shared/ux_q1.py b/shared/ux_q1.py index 8a5f01c8..ab9cebac 100644 --- a/shared/ux_q1.py +++ b/shared/ux_q1.py @@ -755,7 +755,7 @@ class QRScannerInteraction: # - CANCEL to abort # - returns a string, BBQr object or None. from glob import dis, SCAN - from ux import ux_wait_keyup + from ux import ux_wait_keydown frames = [ 1, 2, 3, 4, 5, 4, 3, 2 ] if not SCAN: @@ -780,10 +780,7 @@ class QRScannerInteraction: ph = (ph + 1) % len(frames) # wait for key or 250ms animation delay - try: - ch = await asyncio.wait_for_ms(ux_wait_keyup(), 250) - except asyncio.TimeoutError: - ch = None + ch = await ux_wait_keydown(KEY_CANCEL, 250) if ch == KEY_CANCEL: data = None @@ -1063,7 +1060,7 @@ async def show_bbqr_codes(type_code, data, msg, already_hex=False): # - TODO this code needs better home from bbqr import TYPE_LABELS, int2base36 from glob import PSRAM, dis - from ux import ux_wait_keyup + from ux import ux_wait_keyup, ux_wait_keydown import uqr PAYLOAD_PER_V40 = 2144 # if HEX encoded, active payload (max) per v40 QR @@ -1137,13 +1134,7 @@ async def show_bbqr_codes(type_code, data, msg, already_hex=False): return # wait for key or animation delay - try: - ch = await asyncio.wait_for_ms(ux_wait_keyup(flush=True), ms_per_each) - except asyncio.CancelledError: - # during testing - return - except asyncio.TimeoutError: - ch = None + ch = await ux_wait_keydown(None, ms_per_each) if ch: return