1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-14 03:30:02 +00:00
This commit is contained in:
matejcik 2024-08-06 16:20:13 +02:00 committed by M1nd3r
parent 695daab14e
commit b02c7c4895
13 changed files with 603 additions and 1078 deletions

View File

@ -169,8 +169,6 @@ trezor.ui.layouts.mercury
import trezor.ui.layouts.mercury
trezor.ui.layouts.mercury.fido
import trezor.ui.layouts.mercury.fido
trezor.ui.layouts.mercury.homescreen
import trezor.ui.layouts.mercury.homescreen
trezor.ui.layouts.mercury.recovery
import trezor.ui.layouts.mercury.recovery
trezor.ui.layouts.mercury.reset

View File

@ -58,6 +58,9 @@ if __debug__:
def wait_until_layout_is_running(tries: int | None = _DEADLOCK_WAIT_TRIES) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
counter = 0
while ui.CURRENT_LAYOUT is None:
# TODO modify this so that we can detect a Rust layout in transition:
# ui.CURRENT_LAYOUT is not None
# but something like layout.is_rust_ready_for_events() is False
counter += 1
if tries is not None and counter > tries:
raise wire.FirmwareError(
@ -104,9 +107,9 @@ if __debug__:
async def _layout_click(x: int, y: int, hold_ms: int = 0) -> None:
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
msg = ui.CURRENT_LAYOUT.layout.touch_event(io.TOUCH_START, x, y)
ui.CURRENT_LAYOUT._emit_message(msg)
ui.CURRENT_LAYOUT._paint()
ui.CURRENT_LAYOUT._event(
ui.CURRENT_LAYOUT.layout.touch_event, io.TOUCH_START, x, y
)
if hold_ms:
await loop.sleep(hold_ms)
@ -114,9 +117,9 @@ if __debug__:
if not isinstance(ui.CURRENT_LAYOUT, ui.Layout):
return
msg = ui.CURRENT_LAYOUT.layout.touch_event(io.TOUCH_END, x, y)
ui.CURRENT_LAYOUT._emit_message(msg)
ui.CURRENT_LAYOUT._paint()
ui.CURRENT_LAYOUT._event(
ui.CURRENT_LAYOUT.layout.touch_event, io.TOUCH_END, x, y
)
async def _layout_press_button(
debug_btn: DebugPhysicalButton, hold_ms: int = 0
@ -135,9 +138,9 @@ if __debug__:
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
for btn in buttons:
msg = ui.CURRENT_LAYOUT.layout.button_event(io.BUTTON_PRESSED, btn)
ui.CURRENT_LAYOUT._emit_message(msg)
ui.CURRENT_LAYOUT._paint()
ui.CURRENT_LAYOUT._event(
ui.CURRENT_LAYOUT.layout.button_event, io.BUTTON_PRESSED, btn
)
if hold_ms:
await loop.sleep(hold_ms)
@ -146,9 +149,9 @@ if __debug__:
if not isinstance(ui.CURRENT_LAYOUT, ui.Layout):
return
for btn in buttons:
msg = ui.CURRENT_LAYOUT.layout.button_event(io.BUTTON_RELEASED, btn)
ui.CURRENT_LAYOUT._emit_message(msg)
ui.CURRENT_LAYOUT._paint()
ui.CURRENT_LAYOUT._event(
ui.CURRENT_LAYOUT.layout.button_event, io.BUTTON_RELEASED, btn
)
if utils.USE_TOUCH:
@ -169,9 +172,9 @@ if __debug__:
(io.TOUCH_MOVE, orig_x + 1 * off_x, orig_y + 1 * off_y),
(io.TOUCH_END, orig_x + 2 * off_x, orig_y + 2 * off_y),
):
msg = ui.CURRENT_LAYOUT.layout.touch_event(event, x, y)
ui.CURRENT_LAYOUT._emit_message(msg)
ui.CURRENT_LAYOUT._paint()
ui.CURRENT_LAYOUT._event(
ui.CURRENT_LAYOUT.layout.touch_event, event, x, y
)
elif utils.USE_BUTTON:
@ -213,6 +216,10 @@ if __debug__:
x = msg.x # local_cache_attribute
y = msg.y # local_cache_attribute
# TODO
# if ui.CURRENT_LAYOUT is None:
# await layout_change_chan
await wait_until_layout_is_running()
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
layout_change_chan.clear()
@ -278,6 +285,11 @@ if __debug__:
# default behavior: msg.wait_layout == DebugWaitType.CURRENT_LAYOUT
if not isinstance(ui.CURRENT_LAYOUT, ui.Layout):
# TODO
# if Rust layout is animating, we _could_ get an animation step here
# because the event which triggered the animation was "already processed"
# but the layout is still in the process of updating itself.
# We don't have a clear information that the layout is ready to "be read".
return await return_layout_change(DEBUG_CONTEXT, detect_deadlock=True)
else:
return _state()
@ -429,4 +441,4 @@ if __debug__:
def boot() -> None:
import usb
loop.schedule(handle_session(usb.iface_debug))
loop.schedule(handle_session(usb.iface_debug))#

View File

@ -1,16 +1,19 @@
# pylint: disable=wrong-import-position
import utime
import trezorui2
from micropython import const
from trezorui import Display
from typing import TYPE_CHECKING
from trezor import io, loop, utils, workflow
from trezorui2 import AttachType, BacklightLevels
from trezor import io, log, loop, utils, workflow
from trezor.messages import ButtonAck, ButtonRequest
from trezor.wire import context
from trezorui2 import BacklightLevels
if TYPE_CHECKING:
from typing import Any, Callable, Generator, Generic, Iterator, TypeVar
from trezorui2 import LayoutObj, UiResult # noqa: F401
from trezorui2 import AttachType, LayoutObj, UiResult # noqa: F401
T = TypeVar("T", covariant=True)
@ -19,6 +22,10 @@ else:
Generic = {T: object}
if __debug__:
trezorui2.disable_animation(bool(utils.DISABLE_ANIMATION))
# all rendering is done through a singleton of `Display`
display = Display()
@ -133,8 +140,6 @@ class Layout(Generic[T]):
[docs/core/misc/layout-lifecycle.md] for details.
"""
BACKLIGHT_LEVEL = BacklightLevels.NORMAL
if __debug__:
@staticmethod
@ -156,7 +161,10 @@ class Layout(Generic[T]):
self.tasks: set[loop.Task] = set()
self.timers: dict[int, loop.Task] = {}
self.result_box = loop.mailbox()
self.button_request_box = loop.mailbox()
self.transition_out: AttachType | None = None
self.backlight_level = BacklightLevels.NORMAL
self.context: context.Context | None = None
def is_ready(self) -> bool:
"""True if the layout is in READY state."""
@ -197,14 +205,17 @@ class Layout(Generic[T]):
assert CURRENT_LAYOUT is None
set_current_layout(self)
# save context
self.context = context.CURRENT_CONTEXT
# attach a timer callback and paint self
self.layout.attach_timer_fn(self._set_timer, transition_in)
self._event(self.layout.attach_timer_fn, self._set_timer, transition_in)
# self.layout.attach_timer_fn(self._set_timer, transition_in)
self._first_paint()
# spawn all tasks
for task in self.create_tasks():
self.tasks.add(task)
loop.schedule(task)
self._start_task(task)
def stop(self, _kill_taker: bool = True) -> None:
"""Stop the layout, moving out of RUNNING state and unsetting self as the
@ -247,6 +258,8 @@ class Layout(Generic[T]):
self.start()
# else we are (a) still running or (b) already stopped
try:
if self.context is not None and self.result_box.is_empty():
self._start_task(self._handle_usb_iface())
return await self.result_box
finally:
self.stop()
@ -256,12 +269,37 @@ class Layout(Generic[T]):
msg = self.layout.request_complete_repaint()
assert msg is None
def _event(self, event_call: Callable[..., object], *args: Any) -> None:
"""Process an event coming out of the Rust layout. Set is as a result and shut
down the layout if appropriate, do nothing otherwise."""
msg = event_call(*args)
self._emit_message(msg)
self._button_request()
if self.layout.paint():
refresh()
def _button_request(self) -> None:
"""Process a button request coming out of the Rust layout."""
if __debug__ and not self.button_request_box.is_empty():
raise RuntimeError # button request already pending
if self.context is None:
return
res = self.layout.button_request()
if res is None:
return
# in production, we don't want this to fail, hence replace=True
self.button_request_box.put(res, replace=True)
def _paint(self) -> None:
"""Paint the layout and ensure that homescreen cache is properly invalidated."""
import storage.cache as storage_cache
painted = self.layout.paint()
refresh()
if painted:
refresh()
if storage_cache.homescreen_shown is not None and painted:
storage_cache.homescreen_shown = None
@ -276,7 +314,7 @@ class Layout(Generic[T]):
self._paint()
# Turn the brightness on.
backlight_fade(self.BACKLIGHT_LEVEL)
backlight_fade(self.backlight_level)
def _set_timer(self, token: int, duration: int) -> None:
"""Timer callback for Rust layouts."""
@ -305,6 +343,13 @@ class Layout(Generic[T]):
down the layout if appropriate, do nothing otherwise."""
if msg is None:
return
# XXX DOES NOT WORK YET
if isinstance(msg, ButtonRequest):
# FIXME: special return value for "layout has changed"
# and the buttonrequest is an attribute on that value
from apps.debug import notify_layout_change
notify_layout_change(self)
# when emitting a message, there should not be another one already waiting
assert self.result_box.is_empty()
@ -336,15 +381,61 @@ class Layout(Generic[T]):
# Using `yield` instead of `await` to avoid allocations.
event = yield touch
workflow.idle_timer.touch()
msg = event_call(*event)
self._emit_message(msg)
self.layout.paint()
refresh()
self._event(event_call, *event)
except Shutdown:
return
finally:
touch.close()
async def _handle_usb_iface(self) -> None:
if self.context is None:
return
read_and_raise = self.context.read(())
while True:
try:
br_code, br_name = await loop.race(
read_and_raise, self.button_request_box
)
await self.context.call(
ButtonRequest(
code=br_code, pages=self.layout.page_count(), name=br_name
),
ButtonAck,
)
except Exception:
print(self._trace(self.layout))
raise
def _task_finalizer(self, task: loop.Task, value: Any) -> None:
if value is None:
# all is good
if __debug__:
log.debug(__name__, "UI task exited by itself: %s", task)
return
if isinstance(value, GeneratorExit):
if __debug__:
log.debug(__name__, "UI task was stopped: %s", task)
return
if isinstance(value, BaseException):
if __debug__ and value.__class__.__name__ != "UnexpectedMessage":
log.error(
__name__, "UI task died: %s (%s)", task, value.__class__.__name__
)
try:
self._emit_message(value)
except Shutdown:
pass
if __debug__:
log.error(__name__, "UI task returned non-None: %s (%s)", task, value)
def _start_task(self, task: loop.Task) -> None:
self.tasks.add(task)
loop.schedule(task, finalizer=self._task_finalizer)
def __del__(self) -> None:
self.layout.__del__()
@ -379,8 +470,8 @@ class ProgressLayout:
msg = self.layout.progress_event(value, description or "")
assert msg is None
self.layout.paint()
refresh()
if self.layout.paint():
refresh()
def start(self) -> None:
global CURRENT_LAYOUT
@ -392,9 +483,10 @@ class ProgressLayout:
CURRENT_LAYOUT = self
self.layout.request_complete_repaint()
self.layout.paint()
painted = self.layout.paint()
backlight_fade(BacklightLevels.NONE)
refresh()
if painted:
refresh()
def stop(self) -> None:
global CURRENT_LAYOUT

View File

@ -1,13 +1,13 @@
from typing import TYPE_CHECKING
import trezorui2
from trezor import ui, workflow
from trezor import ui, utils, workflow
from trezor.enums import ButtonRequestType
from trezor.messages import ButtonAck, ButtonRequest
from trezor.wire import ActionCancelled, context
if TYPE_CHECKING:
from typing import Awaitable, Callable, TypeVar
from typing import Any, Awaitable, Callable, TypeVar
PropertyType = tuple[str | None, str | bytes | None]
ExceptionType = BaseException | type[BaseException]
@ -43,7 +43,7 @@ async def interact(
if br_name is not None:
await _button_request(br_name, br_code, layout_obj.page_count())
# wait for the layout result
result = await context.wait(layout.get_result())
result = await layout.get_result()
# raise an exception if the user cancelled the action
if raise_on_cancel is not None and result is trezorui2.CANCELLED:
raise raise_on_cancel
@ -58,3 +58,41 @@ def raise_if_not_confirmed(
) -> Awaitable[None]:
action = interact(layout_obj, br_name, br_code, exc)
return action # type: ignore [Type cannot be assigned to type "None"]
async def with_info(
main_layout: ui.LayoutObj[ui.UiResult],
info_layout: ui.LayoutObj[Any],
br_name: str,
br_code: ButtonRequestType,
) -> None:
send_button_request = True
while True:
result = await interact(
main_layout, br_name if send_button_request else None, br_code
)
# raises on cancel
send_button_request = False
if result is trezorui2.CONFIRMED:
return
elif result is trezorui2.INFO:
await interact(info_layout, None, raise_on_cancel=None)
continue
else:
raise RuntimeError # unexpected result
def draw_simple(layout: trezorui2.LayoutObj[Any]) -> None:
# Simple drawing not supported for layouts that set timers.
def dummy_set_timer(token: int, duration: int) -> None:
raise RuntimeError
layout.attach_timer_fn(dummy_set_timer, None)
if utils.USE_BACKLIGHT:
ui.backlight_fade(ui.BacklightLevels.DIM)
if layout.paint():
ui.refresh()
if utils.USE_BACKLIGHT:
ui.backlight_fade(ui.BacklightLevels.NORMAL)

View File

@ -15,10 +15,11 @@ class HomescreenBase(ui.Layout):
def __init__(self, layout: Any) -> None:
super().__init__(layout=layout)
self.context = None
def _paint(self) -> None:
self.layout.paint()
ui.refresh()
if self.layout.paint():
ui.refresh()
def _first_paint(self) -> None:
if storage_cache.homescreen_shown is not self.RENDER_INDICATOR:
@ -40,7 +41,7 @@ class Homescreen(HomescreenBase):
) -> None:
level = 1
if notification is not None:
notification = notification.rstrip("!")
notification = notification.rstrip("!") # TODO handle TS5 that doesn't have it
if notification == TR.homescreen__title_coinjoin_authorized:
level = 3
elif notification == TR.homescreen__title_experimental_mode:
@ -65,9 +66,7 @@ class Homescreen(HomescreenBase):
usbcheck = loop.wait(io.USB_CHECK)
while True:
is_connected = await usbcheck
self.layout.usb_event(is_connected)
self.layout.paint()
ui.refresh()
self._event(self.layout.usb_event, is_connected)
def create_tasks(self) -> Iterator[loop.Task]:
yield from super().create_tasks()
@ -76,7 +75,6 @@ class Homescreen(HomescreenBase):
class Lockscreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
BACKLIGHT_LEVEL = ui.BacklightLevels.LOW
def __init__(
self,
@ -85,8 +83,9 @@ class Lockscreen(HomescreenBase):
coinjoin_authorized: bool = False,
) -> None:
self.bootscreen = bootscreen
self.backlight_level = ui.BacklightLevels.LOW
if bootscreen:
self.BACKLIGHT_LEVEL = ui.BacklightLevels.NORMAL
self.backlight_level = ui.BacklightLevels.NORMAL
skip = (
not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,15 @@
from typing import TYPE_CHECKING
import trezorui2
from trezor import ui
from trezor.enums import ButtonRequestType
from ..common import interact
from . import RustLayout
if TYPE_CHECKING:
from trezor.loop import AwaitableTask
if __debug__:
from trezor import io, ui
from ... import Result
class _RustFidoLayoutImpl(RustLayout):
def create_tasks(self) -> tuple[AwaitableTask, ...]:
return (
self.handle_input_and_rendering(),
self.handle_timers(),
self.handle_swipe(),
self.handle_debug_confirm(),
)
async def handle_debug_confirm(self) -> None:
from apps.debug import result_signal
_event_id, result = await result_signal()
if result is not trezorui2.CONFIRMED:
raise Result(result)
for event, x, y in (
(io.TOUCH_START, 220, 220),
(io.TOUCH_END, 220, 220),
):
msg = self.layout.touch_event(event, x, y)
if self.layout.paint():
ui.refresh()
if msg is not None:
raise Result(msg)
_RustFidoLayout = _RustFidoLayoutImpl
else:
_RustFidoLayout = RustLayout
async def confirm_fido(
header: str,
app_name: str,
@ -54,16 +17,30 @@ async def confirm_fido(
accounts: list[str | None],
) -> int:
"""Webauthn confirmation for one or more credentials."""
confirm = _RustFidoLayout(
trezorui2.confirm_fido(
title=header,
app_name=app_name,
icon_name=icon_name,
accounts=accounts,
)
confirm = trezorui2.confirm_fido(
title=header,
app_name=app_name,
icon_name=icon_name,
accounts=accounts,
)
result = await interact(confirm, "confirm_fido", ButtonRequestType.Other)
if __debug__ and result is trezorui2.CONFIRMED:
# debuglink will directly inject a CONFIRMED message which we need to handle
# by playing back a click to the Rust layout and getting out the selected number
# that way
from trezor import io
msg = confirm.touch_event(io.TOUCH_START, 220, 220)
assert msg is None
if confirm.paint():
ui.refresh()
msg = confirm.touch_event(io.TOUCH_END, 220, 220)
if confirm.paint():
ui.refresh()
assert isinstance(msg, int)
return msg
# The Rust side returns either an int or `CANCELLED`. We detect the int situation
# and assume cancellation otherwise.
if isinstance(result, int):
@ -78,7 +55,7 @@ async def confirm_fido(
async def confirm_fido_reset() -> bool:
from trezor import TR
confirm = RustLayout(
confirm = ui.Layout(
trezorui2.confirm_action(
title=TR.fido__title_reset,
action=TR.fido__erase_credentials,
@ -87,4 +64,4 @@ async def confirm_fido_reset() -> bool:
prompt_screen=True,
)
)
return (await confirm) is trezorui2.CONFIRMED
return (await confirm.get_result()) is trezorui2.CONFIRMED

View File

@ -1,149 +0,0 @@
from typing import TYPE_CHECKING
import storage.cache as storage_cache
import trezorui2
from trezor import TR, ui
from . import RustLayout
if TYPE_CHECKING:
from typing import Any, Tuple
from trezor import loop
class HomescreenBase(RustLayout):
RENDER_INDICATOR: object | None = None
def __init__(self, layout: Any) -> None:
super().__init__(layout=layout)
def _attach(self) -> None:
if storage_cache.homescreen_shown is self.RENDER_INDICATOR:
self.layout.attach_timer_fn(self.set_timer, ui.AttachType.RESUME)
else:
self.layout.attach_timer_fn(self.set_timer, ui.LAST_TRANSITION_OUT)
def _paint(self) -> None:
if self.layout.paint():
ui.refresh()
def _first_paint(self) -> None:
if storage_cache.homescreen_shown is not self.RENDER_INDICATOR:
super()._first_paint()
storage_cache.homescreen_shown = self.RENDER_INDICATOR
else:
self._paint()
if __debug__:
# In __debug__ mode, ignore {confirm,swipe,input}_signal.
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return (
self.handle_input_and_rendering(),
self.handle_timers(),
self.handle_click_signal(), # so we can receive debug events
)
class Homescreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.HOMESCREEN_ON
def __init__(
self,
label: str | None,
notification: str | None,
notification_is_error: bool,
hold_to_lock: bool,
) -> None:
level = 1
if notification is not None:
if notification == TR.homescreen__title_coinjoin_authorized:
level = 3
elif notification == TR.homescreen__title_experimental_mode:
level = 2
elif notification_is_error:
level = 0
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
super().__init__(
layout=trezorui2.show_homescreen(
label=label,
notification=notification,
notification_level=level,
hold=hold_to_lock,
skip_first_paint=skip,
),
)
async def usb_checker_task(self) -> None:
from trezor import io, loop
usbcheck = loop.wait(io.USB_CHECK)
while True:
is_connected = await usbcheck
self.layout.usb_event(is_connected)
if self.layout.paint():
ui.refresh()
def create_tasks(self) -> Tuple[loop.AwaitableTask, ...]:
return super().create_tasks() + (self.usb_checker_task(),)
class Lockscreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
def __init__(
self,
label: str | None,
bootscreen: bool = False,
coinjoin_authorized: bool = False,
) -> None:
self.bootscreen = bootscreen
self.backlight_level = ui.BacklightLevels.LOW
if bootscreen:
self.backlight_level = ui.BacklightLevels.NORMAL
skip = (
not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR
)
super().__init__(
layout=trezorui2.show_lockscreen(
label=label,
bootscreen=bootscreen,
skip_first_paint=skip,
coinjoin_authorized=coinjoin_authorized,
),
)
async def __iter__(self) -> Any:
result = await super().__iter__()
if self.bootscreen:
self.request_complete_repaint()
return result
class Busyscreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.BUSYSCREEN_ON
def __init__(self, delay_ms: int) -> None:
from trezor import TR
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
super().__init__(
layout=trezorui2.show_progress_coinjoin(
title=TR.coinjoin__waiting_for_others,
indeterminate=True,
time_ms=delay_ms,
skip_first_paint=skip,
)
)
async def __iter__(self) -> Any:
from apps.base import set_homescreen
# Handle timeout.
result = await super().__iter__()
assert result == trezorui2.CANCELLED
storage_cache.delete(storage_cache.APP_COMMON_BUSY_DEADLINE_MS)
set_homescreen()
return result

View File

@ -1,42 +1,64 @@
from typing import Callable, Iterable
import trezorui2
from trezor import TR
from trezor import TR, ui
from trezor.enums import ButtonRequestType, RecoveryType
from ..common import interact
from . import RustLayout, raise_if_not_confirmed
from . import raise_if_not_confirmed
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
CANCELLED = trezorui2.CANCELLED # global_import_cache
INFO = trezorui2.INFO # global_import_cache
async def _is_confirmed_info(
dialog: ui.LayoutObj[ui.UiResult],
info_func: Callable,
br_name: str,
br_code: ButtonRequestType,
) -> bool:
send_button_request = True
while True:
result = await interact(
dialog, br_name if send_button_request else None, br_code
)
if result is trezorui2.INFO:
await info_func()
else:
return result is CONFIRMED
async def request_word_count(recovery_type: RecoveryType) -> int:
selector = RustLayout(trezorui2.select_word_count(recovery_type=recovery_type))
selector = trezorui2.select_word_count(recovery_type=recovery_type)
count = await interact(selector, "word_count", ButtonRequestType.MnemonicWordCount)
return int(count)
async def request_word(
word_index: int, word_count: int, is_slip39: bool, prefill_word: str = ""
word_index: int,
word_count: int,
is_slip39: bool,
send_button_request: bool,
prefill_word: str = "",
) -> str:
prompt = TR.recovery__word_x_of_y_template.format(word_index + 1, word_count)
can_go_back = word_index > 0
if is_slip39:
keyboard = RustLayout(
trezorui2.request_slip39(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
keyboard = trezorui2.request_slip39(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
else:
keyboard = RustLayout(
trezorui2.request_bip39(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
keyboard = trezorui2.request_bip39(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
word: str = await keyboard
word: str = await interact(
keyboard,
"mnemonic" if send_button_request else None,
ButtonRequestType.MnemonicInput,
)
return word
@ -100,23 +122,29 @@ async def continue_recovery(
recovery_type: RecoveryType,
show_info: bool = False,
) -> bool:
# TODO: info_func should be changed to return data to be shown (and not show
# them) so that individual models can implement showing logic on their own.
# T3T1 should move the data to `flow_continue_recovery` and hide them
# in the context menu
if show_info:
# Show this just one-time
description = TR.recovery__enter_each_word
else:
description = subtext or ""
# NOTE: show_info can be understood as first screen before any shares
# NOTE: button request sent from the flow
homepage = RustLayout(
trezorui2.flow_continue_recovery(
first_screen=show_info,
recovery_type=recovery_type,
text=text,
subtext=subtext,
)
homepage = trezorui2.confirm_recovery(
title=text,
description=description,
button=button_label,
info_button=info_func is not None,
recovery_type=recovery_type,
)
result = await homepage
return result is CONFIRMED
if info_func is not None:
return await _is_confirmed_info(
homepage, info_func, "recovery", ButtonRequestType.RecoveryHomepage
)
else:
result = await interact(
homepage, "recovery", ButtonRequestType.RecoveryHomepage
)
return result is CONFIRMED
async def show_recovery_warning(
@ -128,16 +156,12 @@ async def show_recovery_warning(
) -> None:
button = button or TR.buttons__try_again # def_arg
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.show_warning(
title=content or TR.words__warning,
value=subheader or "",
button=button,
description="",
)
),
br_name,
br_code,
)
trezorui2.show_warning(
title=content or TR.words__warning,
value=subheader or "",
button=button,
description="",
),
br_name,
br_code,
)

View File

@ -1,27 +1,22 @@
from typing import TYPE_CHECKING
from typing import Awaitable, Callable, Sequence
import trezorui2
from trezor import TR
from trezor import TR, ui
from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled
from ..common import interact
from . import RustLayout, raise_if_not_confirmed, show_success
if TYPE_CHECKING:
pass
from typing import Callable, Sequence
from . import raise_if_not_confirmed, show_success
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
async def show_share_words(
def show_share_words(
share_words: Sequence[str],
share_index: int | None = None,
group_index: int | None = None,
) -> None:
) -> Awaitable[None]:
title = TR.reset__recovery_wallet_backup_title
highlight_repeated = True
if share_index is None:
@ -44,7 +39,7 @@ async def show_share_words(
text_info.append(TR.reset__repeat_for_all_shares)
text_confirm = TR.reset__words_written_down_template.format(words_count)
result = await RustLayout(
return raise_if_not_confirmed(
trezorui2.flow_show_share_words(
title=title,
subtitle=subtitle,
@ -53,12 +48,10 @@ async def show_share_words(
text_info=text_info,
text_confirm=text_confirm,
highlight_repeated=highlight_repeated,
)
),
None,
)
if result != CONFIRMED:
raise ActionCancelled
async def select_word(
words: Sequence[str],
@ -83,14 +76,15 @@ async def select_word(
while len(words) < 3:
words.append(words[-1])
result = await RustLayout(
result = await interact(
trezorui2.select_word(
title=title,
description=TR.reset__select_word_x_of_y_template.format(
checked_index + 1, count
),
words=(words[0], words[1], words[2]),
)
),
None,
)
if __debug__ and isinstance(result, str):
return result
@ -162,8 +156,7 @@ async def _prompt_number(
max_count: int,
br_name: str,
) -> int:
result = await RustLayout(
result = await interact(
trezorui2.flow_request_number(
title=title,
description=description,
@ -173,7 +166,8 @@ async def _prompt_number(
info=info,
br_code=ButtonRequestType.ResetDevice,
br_name=br_name,
)
),
None,
)
if __debug__:
@ -189,9 +183,9 @@ async def _prompt_number(
raise ActionCancelled # user cancelled request number prompt
async def slip39_prompt_threshold(
def slip39_prompt_threshold(
num_of_shares: int, group_id: int | None = None
) -> int:
) -> Awaitable[int]:
count = num_of_shares // 2 + 1
# min value of share threshold is 2 unless the number of shares is 1
# number of shares 1 is possible in advanced slip39
@ -213,7 +207,7 @@ async def slip39_prompt_threshold(
)
)
return await _prompt_number(
return _prompt_number(
TR.reset__title_set_threshold,
description,
info,
@ -307,8 +301,8 @@ async def show_intro_backup(single_share: bool, num_of_words: int | None) -> Non
)
async def show_warning_backup() -> None:
result = await interact(
def show_warning_backup() -> Awaitable[ui.UiResult]:
return interact(
trezorui2.show_warning(
title=TR.words__important,
value=TR.reset__never_make_digital_copy,
@ -318,25 +312,23 @@ async def show_warning_backup() -> None:
"backup_warning",
ButtonRequestType.ResetDevice,
)
if result != CONFIRMED:
raise ActionCancelled
async def show_success_backup() -> None:
await show_success(
def show_success_backup() -> Awaitable[None]:
return show_success(
"success_backup",
TR.backup__title_backup_completed,
)
async def show_reset_warning(
def show_reset_warning(
br_name: str,
content: str,
subheader: str | None = None,
button: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> None:
await interact(
) -> Awaitable[None]:
return raise_if_not_confirmed(
trezorui2.show_warning(
title=subheader or "",
description=content,
@ -382,11 +374,11 @@ async def show_share_confirmation_success(
)
)
return await show_success("success_recovery", title, subheader=footer_description)
await show_success("success_recovery", title, subheader=footer_description)
async def show_share_confirmation_failure() -> None:
await show_reset_warning(
def show_share_confirmation_failure() -> Awaitable[None]:
return show_reset_warning(
"warning_backup_check",
TR.words__try_again,
TR.reset__incorrect_word_selected,

View File

@ -5,7 +5,7 @@ from trezor import TR, ui, utils
from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled
from ..common import interact, raise_if_not_confirmed
from ..common import interact, raise_if_not_confirmed, draw_simple
if TYPE_CHECKING:
from typing import Any, Awaitable, Iterable, NoReturn, Sequence
@ -20,22 +20,6 @@ INFO = trezorui2.INFO
BR_CODE_OTHER = ButtonRequestType.Other # global_import_cache
if __debug__:
from trezor.utils import DISABLE_ANIMATION
trezorui2.disable_animation(bool(DISABLE_ANIMATION))
def draw_simple(layout: trezorui2.LayoutObj[Any]) -> None:
# Simple drawing not supported for layouts that set timers.
def dummy_set_timer(token: int, duration: int) -> None:
raise RuntimeError
layout.attach_timer_fn(dummy_set_timer, None)
layout.paint()
ui.refresh()
# Temporary function, so we know where it is used
# Should be gradually replaced by custom designs/layouts
def _placeholder_confirm(

View File

@ -5,10 +5,10 @@ from trezor import TR, ui, utils
from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled
from ..common import interact, raise_if_not_confirmed
from ..common import interact, raise_if_not_confirmed, with_info, draw_simple
if TYPE_CHECKING:
from typing import Any, Awaitable, Iterable, NoReturn, Sequence
from typing import Awaitable, Iterable, NoReturn, Sequence
from ..common import ExceptionType, PropertyType
@ -20,24 +20,6 @@ CANCELLED = trezorui2.CANCELLED
INFO = trezorui2.INFO
if __debug__:
from trezor.utils import DISABLE_ANIMATION
trezorui2.disable_animation(bool(DISABLE_ANIMATION))
def draw_simple(layout: trezorui2.LayoutObj[Any]) -> None:
# Simple drawing not supported for layouts that set timers.
def dummy_set_timer(token: int, duration: int) -> None:
raise RuntimeError
layout.attach_timer_fn(dummy_set_timer, None)
ui.backlight_fade(ui.BacklightLevels.DIM)
layout.paint()
ui.refresh()
ui.backlight_fade(ui.BacklightLevels.NORMAL)
def confirm_action(
br_name: str,
title: str,
@ -927,30 +909,6 @@ async def confirm_modify_output(
return
async def with_info(
main_layout: ui.LayoutObj[ui.UiResult],
info_layout: ui.LayoutObj[Any],
br_name: str,
br_code: ButtonRequestType,
) -> None:
send_button_request = True
while True:
result = await interact(
main_layout, br_name if send_button_request else None, br_code
)
# raises on cancel
send_button_request = False
if result is CONFIRMED:
return
elif result is INFO:
await interact(info_layout, None, raise_on_cancel=None)
continue
else:
raise RuntimeError # unexpected result
def confirm_modify_fee(
title: str,
sign: int,

View File

@ -28,11 +28,11 @@ async def confirm_fido(
msg = confirm.touch_event(io.TOUCH_START, 220, 220)
assert msg is None
confirm.paint()
ui.refresh()
if confirm.paint():
ui.refresh()
msg = confirm.touch_event(io.TOUCH_END, 220, 220)
confirm.paint()
ui.refresh()
if confirm.paint():
ui.refresh()
assert isinstance(msg, int)
return msg