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

[no changelog]
matejcik/global-layout-only2
tychovrahe 1 year ago committed by matejcik
parent 48de0cb469
commit a98d84060d

@ -685,8 +685,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', 'T3T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
if TREZOR_MODEL in ('DISC1', 'DISC2'):
@ -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 = []
@ -177,7 +183,7 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
SOURCE_MOD += [
'embed/extmod/modtrezorio/modtrezorio.c',
]
if TREZOR_MODEL in ('T', 'T3T1'):
if 'sd_card' in FEATURES_AVAILABLE:
SOURCE_MOD += [
'embed/extmod/modtrezorio/ff.c',
'embed/extmod/modtrezorio/ffunicode.c',
@ -393,17 +399,26 @@ SOURCE_UNIX = [
'vendor/micropython/ports/unix/input.c',
'vendor/micropython/ports/unix/unix_mphal.c',
]
if TREZOR_MODEL in ('T', 'R', 'T3T1'):
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',
]
@ -439,14 +454,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(
@ -631,7 +638,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'))
@ -662,7 +669,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'))
@ -687,14 +694,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 [])
)
@ -765,9 +772,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)
@ -809,14 +818,8 @@ def cargo_build():
features.append('translations')
if PYOPT == '0':
features.append('debug')
if DMA2D:
features.append('dma2d')
if TREZOR_MODEL in ('T', 'T3T1'):
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

@ -393,6 +393,10 @@ STATIC mp_obj_tuple_t mod_trezorutils_version_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
@ -445,6 +449,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),

@ -28,10 +28,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

@ -121,6 +121,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

@ -47,6 +47,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(
[
@ -56,6 +58,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/^/# /; }'",

@ -170,11 +170,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)
)

@ -100,6 +100,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
@ -179,13 +247,70 @@ class Layout(Generic[T]):
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]

@ -63,7 +63,7 @@ class RustLayout(LayoutParentType[T]):
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(),
@ -197,7 +197,7 @@ class RustLayout(LayoutParentType[T]):
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()
@ -221,7 +221,7 @@ class RustLayout(LayoutParentType[T]):
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 TR, io, loop, ui, utils
from trezor import TR, io, loop, ui, utils, utils
from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled
from trezor.wire.context import wait as ctx_wait
@ -63,13 +62,17 @@ class RustLayout(LayoutParentType[T]):
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.
@ -165,7 +168,12 @@ class RustLayout(LayoutParentType[T]):
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)
@ -193,7 +201,7 @@ class RustLayout(LayoutParentType[T]):
# 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)
@ -209,6 +217,22 @@ class RustLayout(LayoutParentType[T]):
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 TR, 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,
bootloader_locked,
check_firmware_header,

Loading…
Cancel
Save