mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-14 03:30:02 +00:00
wip
This commit is contained in:
parent
695daab14e
commit
b02c7c4895
2
core/src/all_modules.py
generated
2
core/src/all_modules.py
generated
@ -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
|
||||
|
@ -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))#
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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
|
||||
|
@ -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
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user