|
|
|
@ -1,10 +1,9 @@
|
|
|
|
|
# pylint: disable=wrong-import-position
|
|
|
|
|
import utime
|
|
|
|
|
from micropython import const
|
|
|
|
|
from trezorui import Display
|
|
|
|
|
from typing import TYPE_CHECKING, Any, Awaitable, Generator
|
|
|
|
|
|
|
|
|
|
from trezor import io, loop, utils, workflow
|
|
|
|
|
from trezor import loop, utils
|
|
|
|
|
|
|
|
|
|
# all rendering is done through a singleton of `Display`
|
|
|
|
|
display = Display()
|
|
|
|
@ -46,9 +45,6 @@ if utils.EMULATOR or utils.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
|
|
|
|
# import style later to avoid circular dep
|
|
|
|
|
from trezor.ui import style # isort:skip
|
|
|
|
|
|
|
|
|
|
# import style definitions into namespace
|
|
|
|
|
from trezor.ui.style import * # isort:skip # noqa: F401,F403
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _alert(count: int) -> None:
|
|
|
|
|
short_sleep = loop.sleep(20)
|
|
|
|
@ -93,84 +89,6 @@ 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
|
|
|
|
|
|
|
|
|
|
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
elif utils.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
|
|
|
|
|
|
|
|
|
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_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
|
|
|
|
@ -195,7 +113,7 @@ class Cancelled(Exception):
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Layout(Component):
|
|
|
|
|
class Layout:
|
|
|
|
|
"""
|
|
|
|
|
Abstract class.
|
|
|
|
|
|
|
|
|
@ -205,9 +123,6 @@ class Layout(Component):
|
|
|
|
|
raised, usually from some of the child components.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
BACKLIGHT_LEVEL = style.BACKLIGHT_NORMAL
|
|
|
|
|
RENDER_SLEEP: loop.Syscall = loop.sleep(_RENDER_DELAY_MS)
|
|
|
|
|
|
|
|
|
|
async def __iter__(self) -> Any:
|
|
|
|
|
"""
|
|
|
|
|
Run the layout and wait until it completes. Returns the result value.
|
|
|
|
@ -253,72 +168,13 @@ class Layout(Component):
|
|
|
|
|
returns, the others are closed and `create_tasks` is called again.
|
|
|
|
|
|
|
|
|
|
Usually overridden to add another tasks to the list."""
|
|
|
|
|
return self.handle_input(), self.handle_rendering()
|
|
|
|
|
|
|
|
|
|
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
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.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
if __debug__:
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError("Unknown Trezor model")
|
|
|
|
|
|
|
|
|
|
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 read_content_into(self, content_store: list[str]) -> None:
|
|
|
|
|
content_store.clear()
|
|
|
|
|
content_store.append(self.__class__.__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def wait_until_layout_is_running() -> Awaitable[None]: # type: ignore [awaitable-is-generator]
|
|
|
|
|