From b757bff18519e4171628dedb737cfdbe20272c80 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Tue, 25 Apr 2023 22:44:59 +0200 Subject: [PATCH] refactor(core): unify touch and button handling, enable usage of both in one model [no changelog] --- .../extmod/modtrezorio/modtrezorio-poll.h | 65 ++++++------ core/embed/extmod/modtrezorio/modtrezorio.c | 21 ++-- .../extmod/modtrezorutils/modtrezorutils.c | 12 +++ core/mocks/generated/trezorio/__init__.pyi | 3 +- core/mocks/generated/trezorutils.pyi | 2 + core/src/trezor/loop.py | 2 +- core/src/trezor/ui/__init__.py | 98 +++++++------------ core/src/trezor/ui/layouts/tr/__init__.py | 4 +- core/src/trezor/ui/layouts/tt_v2/__init__.py | 16 ++- core/src/trezor/utils.py | 2 + core/tests/production_tests/main.py | 8 +- 11 files changed, 116 insertions(+), 117 deletions(-) diff --git a/core/embed/extmod/modtrezorio/modtrezorio-poll.h b/core/embed/extmod/modtrezorio/modtrezorio-poll.h index da5b122477..0753dcf65a 100644 --- a/core/embed/extmod/modtrezorio/modtrezorio-poll.h +++ b/core/embed/extmod/modtrezorio/modtrezorio-poll.h @@ -25,8 +25,9 @@ #include "embed/extmod/trezorobj.h" #define USB_DATA_IFACE (253) -#define BUTTON_IFACE (254) -#define TOUCH_IFACE (255) +#define INPUT_IFACE (255) +#define TOUCH_INPUT_FLAG (0x40000000) +#define BUTTON_INPUT_FLAG (0x80000000) #define POLL_READ (0x0000) #define POLL_WRITE (0x0100) @@ -81,17 +82,36 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, #endif if (false) { - } -#if defined USE_TOUCH - else if (iface == TOUCH_IFACE) { - const uint32_t evt = touch_read(); + } else if (iface == INPUT_IFACE) { + uint32_t evt; +#ifdef USE_BUTTON + evt = button_read(); + if (evt & (BTN_EVT_DOWN | BTN_EVT_UP)) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); + uint32_t etype = + ((evt >> 24) & 0x3U) | BUTTON_INPUT_FLAG; // button down/up + uint32_t en = evt & 0xFFFF; // button number + if (display_orientation(-1) == 180) { + en = (en == BTN_LEFT) ? BTN_RIGHT : BTN_LEFT; + } + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(etype); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(en); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(0); + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = MP_OBJ_FROM_PTR(tuple); + return mp_const_true; + } +#endif +#ifdef USE_TOUCH + evt = touch_read(); if (evt) { mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); - const uint32_t etype = (evt >> 24) & 0xFFU; // event type - const uint32_t ex = (evt >> 12) & 0xFFFU; // x position - const uint32_t ey = evt & 0xFFFU; // y position - uint32_t exr; // rotated x position - uint32_t eyr; // rotated y position + const uint32_t etype = + ((evt >> 24) & 0xFFU) | TOUCH_INPUT_FLAG; // event type + const uint32_t ex = (evt >> 12) & 0xFFFU; // x position + const uint32_t ey = evt & 0xFFFU; // y position + uint32_t exr; // rotated x position + uint32_t eyr; // rotated y position switch (display_orientation(-1)) { case 90: exr = ey; @@ -117,6 +137,7 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, ret->items[1] = MP_OBJ_FROM_PTR(tuple); return mp_const_true; } +#endif } else if (iface == USB_DATA_IFACE) { bool usb_connected = usb_configured() == sectrue ? true : false; if (usb_connected != usb_connected_previously) { @@ -125,27 +146,7 @@ STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, ret->items[1] = usb_connected ? mp_const_true : mp_const_false; return mp_const_true; } - } -#endif -#if USE_BUTTON - else if (iface == BUTTON_IFACE) { - const uint32_t evt = button_read(); - if (evt & (BTN_EVT_DOWN | BTN_EVT_UP)) { - mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); - uint32_t etype = (evt >> 24) & 0x3U; // button down/up - uint32_t en = evt & 0xFFFF; // button number - if (display_orientation(-1) == 180) { - en = (en == BTN_LEFT) ? BTN_RIGHT : BTN_LEFT; - } - tuple->items[0] = MP_OBJ_NEW_SMALL_INT(etype); - tuple->items[1] = MP_OBJ_NEW_SMALL_INT(en); - ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); - ret->items[1] = MP_OBJ_FROM_PTR(tuple); - return mp_const_true; - } - } -#endif - else if (mode == POLL_READ) { + } else if (mode == POLL_READ) { if (sectrue == usb_hid_can_read(iface)) { uint8_t buf[64] = {0}; int len = usb_hid_read(iface, buf, sizeof(buf)); diff --git a/core/embed/extmod/modtrezorio/modtrezorio.c b/core/embed/extmod/modtrezorio/modtrezorio.c index 0874f166bc..a2e1645910 100644 --- a/core/embed/extmod/modtrezorio/modtrezorio.c +++ b/core/embed/extmod/modtrezorio/modtrezorio.c @@ -62,12 +62,12 @@ bool usb_connected_previously = true; /// POLL_READ: int # wait until interface is readable and return read data /// POLL_WRITE: int # wait until interface is writable /// -/// TOUCH: int # interface id of the touch events +/// INPUT: int # interface id of unified input events +/// /// TOUCH_START: int # event id of touch start event /// TOUCH_MOVE: int # event id of touch move event /// TOUCH_END: int # event id of touch end event -/// BUTTON: int # interface id of button events /// BUTTON_PRESSED: int # button down event /// BUTTON_RELEASED: int # button up event /// BUTTON_LEFT: int # button number of left button @@ -88,19 +88,20 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_fatfs), MP_ROM_PTR(&mod_trezorio_fatfs_module)}, {MP_ROM_QSTR(MP_QSTR_sdcard), MP_ROM_PTR(&mod_trezorio_sdcard_module)}, #endif - + {MP_ROM_QSTR(MP_QSTR_INPUT), MP_ROM_INT(INPUT_IFACE)}, #ifdef USE_TOUCH - {MP_ROM_QSTR(MP_QSTR_TOUCH), MP_ROM_INT(TOUCH_IFACE)}, - {MP_ROM_QSTR(MP_QSTR_TOUCH_START), MP_ROM_INT((TOUCH_START >> 24) & 0xFFU)}, - {MP_ROM_QSTR(MP_QSTR_TOUCH_MOVE), MP_ROM_INT((TOUCH_MOVE >> 24) & 0xFFU)}, - {MP_ROM_QSTR(MP_QSTR_TOUCH_END), MP_ROM_INT((TOUCH_END >> 24) & 0xFFU)}, + {MP_ROM_QSTR(MP_QSTR_TOUCH_START), + MP_ROM_INT(((TOUCH_START >> 24) & 0xFFU) | TOUCH_INPUT_FLAG)}, + {MP_ROM_QSTR(MP_QSTR_TOUCH_MOVE), + MP_ROM_INT(((TOUCH_MOVE >> 24) & 0xFFU) | TOUCH_INPUT_FLAG)}, + {MP_ROM_QSTR(MP_QSTR_TOUCH_END), + MP_ROM_INT(((TOUCH_END >> 24) & 0xFFU) | TOUCH_INPUT_FLAG)}, #endif #ifdef USE_BUTTON - {MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_INT(BUTTON_IFACE)}, {MP_ROM_QSTR(MP_QSTR_BUTTON_PRESSED), - MP_ROM_INT((BTN_EVT_DOWN >> 24) & 0x3U)}, + MP_ROM_INT(((BTN_EVT_DOWN >> 24) & 0x3U) | BUTTON_INPUT_FLAG)}, {MP_ROM_QSTR(MP_QSTR_BUTTON_RELEASED), - MP_ROM_INT((BTN_EVT_UP >> 24) & 0x3U)}, + MP_ROM_INT(((BTN_EVT_UP >> 24) & 0x3U) | BUTTON_INPUT_FLAG)}, {MP_ROM_QSTR(MP_QSTR_BUTTON_LEFT), MP_ROM_INT(BTN_LEFT)}, {MP_ROM_QSTR(MP_QSTR_BUTTON_RIGHT), MP_ROM_INT(BTN_RIGHT)}, #endif diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c index 0eecb3e5f0..0a9adfe4b9 100644 --- a/core/embed/extmod/modtrezorutils/modtrezorutils.c +++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c @@ -259,6 +259,8 @@ STATIC mp_obj_str_t mod_trezorutils_revision_obj = { /// VERSION_MINOR: int /// VERSION_PATCH: int /// USE_SD_CARD: bool +/// USE_TOUCH: bool +/// USE_BUTTON: bool /// MODEL: str /// INTERNAL_MODEL: str /// EMULATOR: bool @@ -289,6 +291,16 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_true}, #else {MP_ROM_QSTR(MP_QSTR_USE_SD_CARD), mp_const_false}, +#endif +#ifdef USE_TOUCH + {MP_ROM_QSTR(MP_QSTR_USE_TOUCH), mp_const_true}, +#else + {MP_ROM_QSTR(MP_QSTR_USE_TOUCH), mp_const_false}, +#endif +#ifdef USE_BUTTON + {MP_ROM_QSTR(MP_QSTR_USE_BUTTON), mp_const_true}, +#else + {MP_ROM_QSTR(MP_QSTR_USE_BUTTON), mp_const_false}, #endif {MP_ROM_QSTR(MP_QSTR_MODEL), MP_ROM_QSTR(MODEL_NAME_QSTR)}, {MP_ROM_QSTR(MP_QSTR_INTERNAL_MODEL), diff --git a/core/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi index e84de507c0..86b8b9d287 100644 --- a/core/mocks/generated/trezorio/__init__.pyi +++ b/core/mocks/generated/trezorio/__init__.pyi @@ -193,11 +193,10 @@ class WebUSB: from . import fatfs, sdcard POLL_READ: int # wait until interface is readable and return read data POLL_WRITE: int # wait until interface is writable -TOUCH: int # interface id of the touch events +INPUT: int # interface id of unified input events TOUCH_START: int # event id of touch start event TOUCH_MOVE: int # event id of touch move event TOUCH_END: int # event id of touch end event -BUTTON: int # interface id of button events BUTTON_PRESSED: int # button down event BUTTON_RELEASED: int # button up event BUTTON_LEFT: int # button number of left button diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi index 28c7250876..36b579af38 100644 --- a/core/mocks/generated/trezorutils.pyi +++ b/core/mocks/generated/trezorutils.pyi @@ -84,6 +84,8 @@ VERSION_MAJOR: int VERSION_MINOR: int VERSION_PATCH: int USE_SD_CARD: bool +USE_TOUCH: bool +USE_BUTTON: bool MODEL: str INTERNAL_MODEL: str EMULATOR: bool diff --git a/core/src/trezor/loop.py b/core/src/trezor/loop.py index 1595a5c44a..0abba8d9cc 100644 --- a/core/src/trezor/loop.py +++ b/core/src/trezor/loop.py @@ -233,7 +233,7 @@ class wait(Syscall): Example: >>> hid_report, = await loop.wait(0xABCD) # await USB HID report - >>> event, x, y = await loop.wait(io.TOUCH) # await touch event + >>> event, x, y = await loop.wait(io.INPUT) # await input event """ def __init__(self, msg_iface: int) -> None: diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py index 59ddcde710..7c2fd34158 100644 --- a/core/src/trezor/ui/__init__.py +++ b/core/src/trezor/ui/__init__.py @@ -118,46 +118,38 @@ class Component: def __init__(self) -> None: self.repaint = True - if utils.MODEL in ("T", "DISC1"): + def dispatch(self, event: int, x: int, y: int) -> None: + from trezor.utils import USE_TOUCH, USE_BUTTON - def dispatch(self, event: int, x: int, y: int) -> None: - if event is RENDER: - self.on_render() - elif event is io.TOUCH_START: - self.on_touch_start(x, y) - elif event is io.TOUCH_MOVE: - self.on_touch_move(x, y) - elif event is io.TOUCH_END: - self.on_touch_end(x, y) - elif event is REPAINT: - self.repaint = True + if event is RENDER: + self.on_render() + elif USE_BUTTON and event is io.BUTTON_PRESSED: + self.on_button_pressed(x) + elif USE_BUTTON and event is io.BUTTON_RELEASED: + self.on_button_released(x) + elif USE_TOUCH and event is io.TOUCH_START: + self.on_touch_start(x, y) + elif USE_TOUCH and event is io.TOUCH_MOVE: + self.on_touch_move(x, y) + elif USE_TOUCH and event is io.TOUCH_END: + self.on_touch_end(x, y) + elif event is REPAINT: + self.repaint = True - def on_touch_start(self, x: int, y: int) -> None: - pass + def on_touch_start(self, x: int, y: int) -> None: + pass - def on_touch_move(self, x: int, y: int) -> None: - pass + def on_touch_move(self, x: int, y: int) -> None: + pass - def on_touch_end(self, x: int, y: int) -> None: - pass + def on_touch_end(self, x: int, y: int) -> None: + pass - elif utils.MODEL in ("1", "R"): + def on_button_pressed(self, button_number: int) -> None: + pass - def dispatch(self, event: int, x: int, y: int) -> None: - if event is RENDER: - self.on_render() - elif event is io.BUTTON_PRESSED: - self.on_button_pressed(x) - elif event is io.BUTTON_RELEASED: - self.on_button_released(x) - elif event is REPAINT: - self.repaint = True - - def on_button_pressed(self, button_number: int) -> None: - pass - - def on_button_released(self, button_number: int) -> None: - pass + def on_button_released(self, button_number: int) -> None: + pass def on_render(self) -> None: pass @@ -253,33 +245,17 @@ class Layout(Component): Usually overridden to add another tasks to the list.""" return self.handle_input(), self.handle_rendering() - if utils.MODEL in ("T", "DISC1"): - - def handle_input(self) -> Generator: - """Task that is waiting for the user input.""" - touch = loop.wait(io.TOUCH) - while True: - # Using `yield` instead of `await` to avoid allocations. - event, x, y = yield touch - workflow.idle_timer.touch() - self.dispatch(event, x, y) - # We dispatch a render event right after the touch. Quick and dirty - # way to get the lowest input-to-render latency. - self.dispatch(RENDER, 0, 0) - - elif utils.MODEL in ("1", "R"): - - def handle_input(self) -> Generator: - """Task that is waiting for the user input.""" - button = loop.wait(io.BUTTON) - while True: - event, button_num = yield button - workflow.idle_timer.touch() - self.dispatch(event, button_num, 0) - self.dispatch(RENDER, 0, 0) - - else: - raise ValueError("Unknown Trezor model") + def handle_input(self) -> Generator: + """Task that is waiting for the user input.""" + input_ = loop.wait(io.INPUT) + while True: + # Using `yield` instead of `await` to avoid allocations. + event, x, y = yield input_ + workflow.idle_timer.touch() + self.dispatch(event, x, y) + # We dispatch a render event right after the input. Quick and dirty + # way to get the lowest input-to-render latency. + self.dispatch(RENDER, 0, 0) def _before_render(self) -> None: # Before the first render, we dim the display. diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index ccab93455f..d7c76a9256 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -218,7 +218,7 @@ class RustLayout(ui.Layout): def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator] from trezor import workflow - button = loop.wait(io.BUTTON) + button = loop.wait(io.INPUT) self._first_paint() while True: # Using `yield` instead of `await` to avoid allocations. @@ -226,7 +226,7 @@ class RustLayout(ui.Layout): workflow.idle_timer.touch() msg = None if event in (io.BUTTON_PRESSED, io.BUTTON_RELEASED): - msg = self.layout.button_event(event, button_num) + msg = self.layout.button_event(event & 0xFF, button_num) if msg is not None: raise ui.Result(msg) self._paint() diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index 30a2de76c8..256e241b18 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from trezor import io, loop, ui +from trezor import io, loop, ui, utils from trezor.enums import ButtonRequestType from trezor.wire import ActionCancelled @@ -187,15 +187,21 @@ class RustLayout(ui.Layout): def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator] from trezor import workflow - touch = loop.wait(io.TOUCH) + input = loop.wait(io.INPUT) self._first_paint() while True: # Using `yield` instead of `await` to avoid allocations. - event, x, y = yield touch + event, p0, p1 = yield input workflow.idle_timer.touch() msg = None - if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END): - msg = self.layout.touch_event(event, x, y) + if utils.USE_TOUCH and event in ( + io.TOUCH_START, + io.TOUCH_MOVE, + io.TOUCH_END, + ): + msg = self.layout.touch_event(event & 0xFF, p0, p1) + if utils.USE_BUTTON and event in (io.BUTTON_PRESSED, io.BUTTON_RELEASED): + msg = self.layout.button_event(event & 0xFF, p0) if msg is not None: raise ui.Result(msg) self._paint() diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index 243405fa8b..6731d0fe8b 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -6,7 +6,9 @@ from trezorutils import ( # noqa: F401 INTERNAL_MODEL, MODEL, SCM_REVISION, + USE_BUTTON, USE_SD_CARD, + USE_TOUCH, VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, diff --git a/core/tests/production_tests/main.py b/core/tests/production_tests/main.py index 90f106e74d..fab70bc32e 100644 --- a/core/tests/production_tests/main.py +++ b/core/tests/production_tests/main.py @@ -62,23 +62,23 @@ def test_touch(v): d.refresh() r = [0, 0] # flush all events - while io.poll([io.TOUCH], r, 10000): + while io.poll([io.INPUT], r, 10000): pass # wait for event touch = False while True: if not touch: - if io.poll([io.TOUCH], r, 10000) and r[0] == io.TOUCH and r[1][0] == io.TOUCH_START: + if io.poll([io.INPUT], r, 10000) and r[0] == io.INPUT and r[1][0] == io.TOUCH_START: touch = True else: - if io.poll([io.TOUCH], r, 10000) and r[0] == io.TOUCH and r[1][0] == io.TOUCH_END: + if io.poll([io.INPUT], r, 10000) and r[0] == io.INPUT and r[1][0] == io.TOUCH_END: print(f'OK {r[1][1]} {r[1][2]}') break if utime.ticks_us() > deadline: print('ERROR TIMEOUT') break # flush all events - while io.poll([io.TOUCH], r, 10000): + while io.poll([io.INPUT], r, 10000): pass d.clear() d.refresh()