refactor(core): unify touch and button handling, enable usage of both in one model

[no changelog]
matejcik/global-layout-only
tychovrahe 1 year ago committed by matejcik
parent d472c1306f
commit 912c88e252

@ -666,8 +666,10 @@ if FROZEN:
bitcoin_only=BITCOIN_ONLY,
backlight='backlight' in FEATURES_AVAILABLE,
optiga='optiga' in FEATURES_AVAILABLE,
use_button='button' in FEATURES_AVAILABLE,
use_touch='touch' in FEATURES_AVAILABLE,
ui_layout=UI_LAYOUT,
)
)
source_mpyc = env.FrozenCFile(
target='frozen_mpy.c', source=source_mpy, qstr_header=qstr_preprocessed)

@ -7,7 +7,6 @@ import tools
BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0')
EVERYTHING = BITCOIN_ONLY != '1'
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
DMA2D = TREZOR_MODEL in ('T', )
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
if TREZOR_MODEL in ('DISC1', ):
@ -25,6 +24,13 @@ FEATURE_FLAGS = {
"SECP256K1_ZKP": True, # required for trezor.crypto.curve.bip340 (BIP340/Taproot)
}
if TREZOR_MODEL in ('1', ):
FEATURES_AVAILABLE = ['button']
elif TREZOR_MODEL in ('R', ):
FEATURES_AVAILABLE = ['button', 'sbu', 'optiga']
elif TREZOR_MODEL in ('T', ):
FEATURES_AVAILABLE = ['touch', 'sbu', 'sd_card', 'dma2d']
CCFLAGS_MOD = ''
CPPPATH_MOD = []
CPPDEFINES_MOD = []
@ -174,7 +180,7 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
SOURCE_MOD += [
'embed/extmod/modtrezorio/modtrezorio.c',
]
if TREZOR_MODEL in ('T',):
if 'sd_card' in FEATURES_AVAILABLE:
SOURCE_MOD += [
'embed/extmod/modtrezorio/ff.c',
'embed/extmod/modtrezorio/ffunicode.c',
@ -384,17 +390,26 @@ SOURCE_UNIX = [
'vendor/micropython/ports/unix/input.c',
'vendor/micropython/ports/unix/unix_mphal.c',
]
if TREZOR_MODEL in ('T', 'R'):
if 'sbu' in FEATURES_AVAILABLE:
SOURCE_UNIX += [
'embed/trezorhal/unix/sbu.c',
]
if TREZOR_MODEL == 'R':
if 'sd_card' in FEATURES_AVAILABLE:
SOURCE_UNIX += [
'embed/trezorhal/unix/sdcard.c',
]
if 'optiga' in FEATURES_AVAILABLE:
CPPDEFINES_MOD += [
('USE_OPTIGA', '1'),
]
SOURCE_UNIX += [
'embed/trezorhal/unix/optiga.c',
]
if DMA2D:
if 'dma2d' in FEATURES_AVAILABLE:
CPPDEFINES_MOD += [
'USE_DMA2D',
]
@ -430,14 +445,6 @@ elif TREZOR_MODEL in ('1', 'R'):
else:
raise ValueError('Unknown Trezor model')
if TREZOR_MODEL in ('T',):
SDCARD = True
SOURCE_UNIX += [
'embed/trezorhal/unix/sdcard.c',
]
else:
SDCARD = False
env.Tool('micropython')
env.Replace(
@ -614,7 +621,7 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/*.py',
exclude=[
SOURCE_PY_DIR + 'trezor/sdcard.py',
] if not SDCARD else []
] if 'sd_card' not in FEATURES_AVAILABLE else []
))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/crypto/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/*.py'))
@ -645,7 +652,7 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py',
exclude=[
SOURCE_PY_DIR + 'storage/sd_salt.py',
] if not SDCARD else []
] if 'sd_card' not in FEATURES_AVAILABLE else []
))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py'))
@ -669,14 +676,14 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/common/*.py',
exclude=[
SOURCE_PY_DIR + 'apps/common/sdcard.py',
] if not SDCARD else []
] if 'sd_card' not in FEATURES_AVAILABLE else []
))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py',
exclude=[
SOURCE_PY_DIR + 'apps/management/sd_protect.py',
] if not SDCARD else [] + [
] if 'sd_card' not in FEATURES_AVAILABLE else [] + [
SOURCE_PY_DIR + 'apps/management/authenticate_device.py',
] if TREZOR_MODEL not in ('R',) else [])
)
@ -743,9 +750,11 @@ if FROZEN:
source_dir=SOURCE_PY_DIR,
bitcoin_only=BITCOIN_ONLY,
backlight=TREZOR_MODEL in ('T',),
optiga=TREZOR_MODEL in ('R',),
optiga='optiga' in FEATURES_AVAILABLE,
use_button='button' in FEATURES_AVAILABLE,
use_touch='touch' in FEATURES_AVAILABLE,
ui_layout=UI_LAYOUT,
)
)
source_mpyc = env.FrozenCFile(
target='frozen_mpy.c', source=source_mpy, qstr_header=qstr_preprocessed)
@ -786,14 +795,8 @@ def cargo_build():
features.append('ui')
if PYOPT == '0':
features.append('debug')
if DMA2D:
features.append('dma2d')
if TREZOR_MODEL in ('T',):
features.append('touch')
features.append('sd_card')
if TREZOR_MODEL in ('R', '1'):
features.append('button')
features.extend(FEATURES_AVAILABLE)
env.get('ENV')['TREZOR_MODEL'] = TREZOR_MODEL

@ -384,6 +384,10 @@ STATIC mp_obj_str_t mod_trezorutils_full_name_obj = {
/// """Whether the hardware supports backlight brightness control."""
/// USE_OPTIGA: bool
/// """Whether the hardware supports Optiga secure element."""
/// USE_TOUCH: bool
/// """Whether the hardware supports touch screen."""
/// USE_BUTTON: bool
/// """Whether the hardware supports two-button input."""
/// MODEL: str
/// """Model name."""
/// MODEL_FULL_NAME: str
@ -436,6 +440,16 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_USE_OPTIGA), mp_const_true},
#else
{MP_ROM_QSTR(MP_QSTR_USE_OPTIGA), 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_PTR(&mod_trezorutils_model_name_obj)},
{MP_ROM_QSTR(MP_QSTR_MODEL_FULL_NAME),

@ -21,10 +21,4 @@
#include "display-unix.h"
#ifdef TREZOR_MODEL_R
#define USE_BUTTON 1
#elif TREZOR_MODEL_T
#define USE_TOUCH 1
#endif
#endif //_BOARD_UNIX_H

@ -118,6 +118,10 @@ USE_BACKLIGHT: bool
"""Whether the hardware supports backlight brightness control."""
USE_OPTIGA: bool
"""Whether the hardware supports Optiga secure element."""
USE_TOUCH: bool
"""Whether the hardware supports touch screen."""
USE_BUTTON: bool
"""Whether the hardware supports two-button input."""
MODEL: str
"""Model name."""
MODEL_FULL_NAME: str

@ -41,6 +41,8 @@ def generate(env):
optiga = env["optiga"]
layout_tt = env["ui_layout"] == "UI_LAYOUT_TT"
layout_tr = env["ui_layout"] == "UI_LAYOUT_TR"
touch = env["use_touch"]
button = env["use_button"]
interim = f"{target[:-4]}.i" # replace .mpy with .i
sed_scripts = " ".join(
[
@ -50,6 +52,8 @@ def generate(env):
rf"-e 's/utils\.USE_OPTIGA/{optiga}/g'",
rf"-e 's/utils\.UI_LAYOUT == \"TT\"/{layout_tt}/g'",
rf"-e 's/utils\.UI_LAYOUT == \"TR\"/{layout_tr}/g'",
rf"-e 's/utils\.USE_BUTTON/{button}/g'",
rf"-e 's/utils\.USE_TOUCH/{touch}/g'",
r"-e 's/if TYPE_CHECKING/if False/'",
r"-e 's/import typing/# \0/'",
r"-e '/from typing import (/,/^\s*)/ {s/^/# /}'",

@ -167,11 +167,11 @@ if __debug__:
# Incrementing the counter for last events so we know what to await
debug_events.last_event += 1
# TT click on specific coordinates, with possible hold
if x is not None and y is not None and utils.INTERNAL_MODEL in ("T2T1", "D001"):
# click on specific coordinates, with possible hold
if x is not None and y is not None:
click_chan.publish((debug_events.last_event, x, y, msg.hold_ms))
# TR press specific button
elif msg.physical_button is not None and utils.INTERNAL_MODEL in ("T2B1",):
# press specific button
elif msg.physical_button is not None:
button_chan.publish(
(debug_events.last_event, msg.physical_button, msg.hold_ms)
)

@ -89,6 +89,74 @@ def backlight_fade(val: int, delay: int = 14000, step: int = 15) -> None:
display.backlight(val)
# Component events. Should be different from `io.TOUCH_*` events.
# Event dispatched when components should draw to the display, if they are
# marked for re-paint.
RENDER = const(-255)
# Event dispatched when components should mark themselves for re-painting.
REPAINT = const(-256)
# How long, in milliseconds, should the layout rendering task sleep between
# the render calls.
_RENDER_DELAY_MS = const(10)
class Component:
"""
Abstract class.
Components are GUI classes that inherit `Component` and form a tree, with a
`Layout` at the root, and other components underneath. Components that
have children, and therefore need to dispatch events to them, usually
override the `dispatch` method. Leaf components usually override the event
methods (`on_*`). Components signal a completion to the layout by raising
an instance of `Result`.
"""
def __init__(self) -> None:
self.repaint = True
def dispatch(self, event: int, x: int, y: int) -> None:
if event is RENDER:
self.on_render()
elif utils.USE_BUTTON and event is io.BUTTON_PRESSED:
self.on_button_pressed(x)
elif utils.USE_BUTTON and event is io.BUTTON_RELEASED:
self.on_button_released(x)
elif utils.USE_TOUCH and event is io.TOUCH_START:
self.on_touch_start(x, y)
elif utils.USE_TOUCH and event is io.TOUCH_MOVE:
self.on_touch_move(x, y)
elif utils.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_move(self, x: int, y: int) -> None:
pass
def on_touch_end(self, x: int, y: int) -> None:
pass
def on_button_pressed(self, button_number: int) -> None:
pass
def on_button_released(self, button_number: int) -> None:
pass
def on_render(self) -> None:
pass
if __debug__:
def read_content_into(self, content_store: list[str]) -> None:
content_store.clear()
content_store.append(self.__class__.__name__)
class Result(Exception):
"""
When components want to trigger layout completion, they do so through
@ -168,13 +236,70 @@ class Layout:
returns, the others are closed and `create_tasks` is called again.
Usually overridden to add another tasks to the list."""
raise NotImplementedError
if __debug__:
def read_content_into(self, content_store: list[str]) -> None:
content_store.clear()
content_store.append(self.__class__.__name__)
tasks = (self.handle_rendering(),)
if utils.USE_BUTTON:
tasks = tasks + (self.handle_button(),)
if utils.USE_TOUCH:
tasks = tasks + (self.handle_touch(),)
return tasks
def handle_touch(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)
def handle_button(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)
def _before_render(self) -> None:
# Before the first render, we dim the display.
backlight_fade(style.BACKLIGHT_NONE)
# Clear the screen of any leftovers, make sure everything is marked for
# repaint (we can be running the same layout instance multiple times)
# and paint it.
display.clear()
self.dispatch(REPAINT, 0, 0)
self.dispatch(RENDER, 0, 0)
if __debug__ and self.should_notify_layout_change:
from apps.debug import notify_layout_change
# notify about change and do not notify again until next await.
# (handle_rendering might be called multiple times in a single await,
# because of the endless loop in __iter__)
self.should_notify_layout_change = False
notify_layout_change(self)
# Display is usually refreshed after every loop step, but here we are
# rendering everything synchronously, so refresh it manually and turn
# the brightness on again.
refresh()
backlight_fade(self.BACKLIGHT_LEVEL)
def handle_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
"""Task that is rendering the layout in a busy loop."""
self._before_render()
sleep = self.RENDER_SLEEP
while True:
# Wait for a couple of ms and render the layout again. Because
# components use re-paint marking, they do not really draw on the
# display needlessly. Using `yield` instead of `await` to avoid allocations.
# TODO: remove the busy loop
yield sleep
self.dispatch(RENDER, 0, 0)
def wait_until_layout_is_running() -> Awaitable[None]: # type: ignore [awaitable-is-generator]

@ -57,7 +57,7 @@ class RustLayout(ui.Layout):
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return (
self.handle_input_and_rendering(),
self.handle_button(),
self.handle_timers(),
self.handle_swipe_signal(),
self.handle_button_signal(),
@ -191,7 +191,7 @@ class RustLayout(ui.Layout):
else:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return self.handle_timers(), self.handle_input_and_rendering()
return self.handle_timers(), self.handle_button()
def _first_paint(self) -> None:
self._paint()
@ -215,7 +215,7 @@ class RustLayout(ui.Layout):
notify_layout_change(self, event_id)
def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
def handle_button(self) -> loop.Task: # type: ignore [awaitable-is-generator]
from trezor import workflow
button = loop.wait(io.BUTTON)

@ -1,7 +1,6 @@
from typing import TYPE_CHECKING
import trezorui2
from trezor import io, loop, ui
from trezor import io, loop, ui, utils
from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled
from trezor.wire.context import wait as ctx_wait
@ -57,13 +56,17 @@ class RustLayout(ui.Layout):
if __debug__:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return (
tasks = (
self.handle_timers(),
self.handle_input_and_rendering(),
self.handle_swipe(),
self.handle_click_signal(),
self.handle_result_signal(),
)
if utils.USE_TOUCH:
tasks = tasks + (self.handle_touch(),)
if utils.USE_BUTTON:
tasks = tasks + (self.handle_button(),)
return tasks
async def handle_result_signal(self) -> None:
"""Enables sending arbitrary input - ui.Result.
@ -159,7 +162,12 @@ class RustLayout(ui.Layout):
else:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return self.handle_timers(), self.handle_input_and_rendering()
tasks = (self.handle_timers(),)
if utils.USE_BUTTON:
tasks = tasks + (self.handle_button(),)
if utils.USE_TOUCH:
tasks = tasks + (self.handle_touch(),)
return tasks
def _first_paint(self) -> None:
ui.backlight_fade(ui.style.BACKLIGHT_NONE)
@ -187,7 +195,7 @@ class RustLayout(ui.Layout):
# Turn the brightness on again.
ui.backlight_fade(self.BACKLIGHT_LEVEL)
def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
def handle_touch(self) -> loop.Task: # type: ignore [awaitable-is-generator]
from trezor import workflow
touch = loop.wait(io.TOUCH)
@ -203,6 +211,22 @@ class RustLayout(ui.Layout):
raise ui.Result(msg)
self._paint()
def handle_button(self) -> loop.Task: # type: ignore [awaitable-is-generator]
from trezor import workflow
button = loop.wait(io.BUTTON)
self._first_paint()
while True:
# Using `yield` instead of `await` to avoid allocations.
event, button_num = yield button
workflow.idle_timer.touch()
msg = None
if event in (io.BUTTON_PRESSED, io.BUTTON_RELEASED):
msg = self.layout.button_event(event, button_num)
if msg is not None:
raise ui.Result(msg)
self._paint()
def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator]
while True:
# Using `yield` instead of `await` to avoid allocations.

@ -17,12 +17,18 @@ if __debug__:
class _RustFidoLayoutImpl(RustLayout):
def create_tasks(self) -> tuple[AwaitableTask, ...]:
return (
from trezor import utils
tasks = (
self.handle_timers(),
self.handle_input_and_rendering(),
self.handle_swipe(),
self.handle_debug_confirm(),
)
if utils.USE_TOUCH:
tasks = tasks + (self.handle_touch(),)
if utils.USE_BUTTON:
tasks = tasks + (self.handle_button(),)
return tasks
async def handle_debug_confirm(self) -> None:
from apps.debug import result_signal

@ -1,6 +1,8 @@
from typing import TYPE_CHECKING
import storage.cache as storage_cache
from trezor import ui, utils
import trezorui2
from trezor import ui
@ -32,11 +34,15 @@ class HomescreenBase(RustLayout):
if __debug__:
# In __debug__ mode, ignore {confirm,swipe,input}_signal.
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return (
tasks = (
self.handle_timers(),
self.handle_input_and_rendering(),
self.handle_click_signal(), # so we can receive debug events
)
if utils.USE_TOUCH:
tasks = tasks + (self.handle_touch(),)
if utils.USE_BUTTON:
tasks = tasks + (self.handle_button(),)
return tasks
class Homescreen(HomescreenBase):

@ -9,8 +9,10 @@ from trezorutils import ( # noqa: F401
SCM_REVISION,
UI_LAYOUT,
USE_BACKLIGHT,
USE_BUTTON,
USE_OPTIGA,
USE_SD_CARD,
USE_TOUCH,
VERSION_MAJOR,
VERSION_MINOR,
VERSION_PATCH,

Loading…
Cancel
Save