mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 22:38:08 +00:00
feat(core): unify RustLayout, implement single global layout
This commit is contained in:
parent
df368413c6
commit
0e8dcbb498
@ -51,7 +51,7 @@ message DebugLinkDecision {
|
||||
|
||||
optional uint32 x = 4; // touch X coordinate
|
||||
optional uint32 y = 5; // touch Y coordinate
|
||||
optional bool wait = 6; // wait for layout change
|
||||
optional bool wait = 6 [deprecated=true]; // wait for layout change
|
||||
optional uint32 hold_ms = 7; // touch hold duration
|
||||
optional DebugPhysicalButton physical_button = 8; // physical button press
|
||||
}
|
||||
@ -61,6 +61,7 @@ message DebugLinkDecision {
|
||||
* @end
|
||||
*/
|
||||
message DebugLinkLayout {
|
||||
option deprecated = true;
|
||||
repeated string tokens = 1;
|
||||
}
|
||||
|
||||
@ -89,9 +90,26 @@ message DebugLinkRecordScreen {
|
||||
* @next DebugLinkState
|
||||
*/
|
||||
message DebugLinkGetState {
|
||||
optional bool wait_word_list = 1; // Trezor T only - wait until mnemonic words are shown
|
||||
optional bool wait_word_pos = 2; // Trezor T only - wait until reset word position is requested
|
||||
optional bool wait_layout = 3; // wait until current layout changes
|
||||
/// Wait behavior of the call.
|
||||
enum DebugWaitType {
|
||||
/// Respond immediately. If no layout is currently displayed, the layout
|
||||
/// response will be empty.
|
||||
IMMEDIATE = 0;
|
||||
/// Wait for next layout. If a layout is displayed, waits for it to change.
|
||||
/// If no layout is displayed, waits for one to come up.
|
||||
NEXT_LAYOUT = 1;
|
||||
/// Return current layout. If no layout is currently displayed, waits for
|
||||
/// one to come up.
|
||||
CURRENT_LAYOUT = 2;
|
||||
}
|
||||
|
||||
// Trezor T < 2.6.0 only - wait until mnemonic words are shown
|
||||
optional bool wait_word_list = 1 [deprecated=true];
|
||||
// Trezor T < 2.6.0 only - wait until reset word position is requested
|
||||
optional bool wait_word_pos = 2 [deprecated=true];
|
||||
// trezor-core only - wait until current layout changes
|
||||
// changed in 2.6.4: multiple wait types instead of true/false.
|
||||
optional DebugWaitType wait_layout = 3 [default=IMMEDIATE];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,6 +210,7 @@ message DebugLinkEraseSdCard {
|
||||
* @next Success
|
||||
*/
|
||||
message DebugLinkWatchLayout {
|
||||
option deprecated = true;
|
||||
optional bool watch = 1; // if true, start watching layout.
|
||||
// if false, stop.
|
||||
}
|
||||
@ -203,6 +222,7 @@ message DebugLinkWatchLayout {
|
||||
* @next Success
|
||||
*/
|
||||
message DebugLinkResetDebugEvents {
|
||||
option deprecated = true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -311,17 +311,11 @@ impl SwipeFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/// ObjComponent implementation for SwipeFlow.
|
||||
/// Layout implementation for SwipeFlow.
|
||||
///
|
||||
/// Instead of using the generic `impl ObjComponent for ComponentMsgObj`, we
|
||||
/// provide our own short-circuit implementation for `SwipeFlow`. This way we
|
||||
/// can completely avoid implementing `Component`. That also allows us to pass
|
||||
/// around concrete Renderers instead of having to conform to `Component`'s
|
||||
/// not-object-safe interface.
|
||||
///
|
||||
/// This implementation relies on the fact that swipe components always return
|
||||
/// `FlowMsg` as their `Component::Msg` (provided by `impl FlowComponentTrait`
|
||||
/// earlier in this file).
|
||||
/// This way we can completely avoid implementing `Component`. That also allows
|
||||
/// us to pass around concrete Renderers instead of having to conform to
|
||||
/// `Component`'s not-object-safe interface.
|
||||
impl Layout<Result<Obj, Error>> for SwipeFlow {
|
||||
fn place(&mut self) {
|
||||
for elem in self.store.iter_mut() {
|
||||
|
@ -12,7 +12,6 @@ pub enum LayoutState {
|
||||
}
|
||||
|
||||
pub trait Layout<T> {
|
||||
//fn attach(&mut self, ctx: &mut EventCtx, attach_type: AttachType);
|
||||
fn place(&mut self);
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<LayoutState>;
|
||||
fn value(&self) -> Option<&T>;
|
||||
|
@ -1662,7 +1662,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Attach a timer setter function.
|
||||
///
|
||||
/// The layout object can call the timer setter with two arguments,
|
||||
/// `token` and `duration`. When `duration` elapses, the layout object
|
||||
/// `token` and `duration_ms`. When `duration_ms` elapses, the layout object
|
||||
/// expects a callback to `self.timer(token)`.
|
||||
/// """
|
||||
///
|
||||
|
@ -1091,10 +1091,10 @@ class LayoutObj(Generic[T]):
|
||||
"""Representation of a Rust-based layout object.
|
||||
see `trezor::ui::layout::obj::LayoutObj`.
|
||||
"""
|
||||
def attach_timer_fn(self, fn: Callable[[int, int], None], attach_type: AttachType | None) -> None:
|
||||
def attach_timer_fn(self, fn: Callable[[int, int], None], attach_type: AttachType | None) -> LayoutState | None:
|
||||
"""Attach a timer setter function.
|
||||
The layout object can call the timer setter with two arguments,
|
||||
`token` and `duration`. When `duration` elapses, the layout object
|
||||
`token` and `duration_ms`. When `duration_ms` elapses, the layout object
|
||||
expects a callback to `self.timer(token)`.
|
||||
"""
|
||||
if utils.USE_TOUCH:
|
||||
@ -1136,7 +1136,6 @@ class LayoutObj(Generic[T]):
|
||||
"""Return (code, type) of button request made during the last event or timer pass."""
|
||||
def get_transition_out(self) -> AttachType:
|
||||
"""Return the transition type."""
|
||||
|
||||
def return_value(self) -> T:
|
||||
"""Retrieve the return value of the layout object."""
|
||||
def __del__(self) -> None:
|
||||
|
8
core/src/all_modules.py
generated
8
core/src/all_modules.py
generated
@ -107,6 +107,8 @@ trezor.enums.DebugPhysicalButton
|
||||
import trezor.enums.DebugPhysicalButton
|
||||
trezor.enums.DebugSwipeDirection
|
||||
import trezor.enums.DebugSwipeDirection
|
||||
trezor.enums.DebugWaitType
|
||||
import trezor.enums.DebugWaitType
|
||||
trezor.enums.DecredStakingSpendType
|
||||
import trezor.enums.DecredStakingSpendType
|
||||
trezor.enums.FailureType
|
||||
@ -167,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
|
||||
@ -183,8 +183,6 @@ trezor.ui.layouts.tr
|
||||
import trezor.ui.layouts.tr
|
||||
trezor.ui.layouts.tr.fido
|
||||
import trezor.ui.layouts.tr.fido
|
||||
trezor.ui.layouts.tr.homescreen
|
||||
import trezor.ui.layouts.tr.homescreen
|
||||
trezor.ui.layouts.tr.recovery
|
||||
import trezor.ui.layouts.tr.recovery
|
||||
trezor.ui.layouts.tr.reset
|
||||
@ -193,8 +191,6 @@ trezor.ui.layouts.tt
|
||||
import trezor.ui.layouts.tt
|
||||
trezor.ui.layouts.tt.fido
|
||||
import trezor.ui.layouts.tt.fido
|
||||
trezor.ui.layouts.tt.homescreen
|
||||
import trezor.ui.layouts.tt.homescreen
|
||||
trezor.ui.layouts.tt.recovery
|
||||
import trezor.ui.layouts.tt.recovery
|
||||
trezor.ui.layouts.tt.reset
|
||||
|
@ -20,7 +20,6 @@ from ..keychain import address_n_to_name
|
||||
if TYPE_CHECKING:
|
||||
from trezor.enums import AmountUnit
|
||||
from trezor.messages import TxAckPaymentRequest, TxOutput
|
||||
from trezor.ui.layouts import LayoutType
|
||||
|
||||
from apps.common.coininfo import CoinInfo
|
||||
from apps.common.paths import Bip32Path
|
||||
@ -73,7 +72,7 @@ async def confirm_output(
|
||||
assert data is not None
|
||||
if omni.is_valid(data):
|
||||
# OMNI transaction
|
||||
layout: LayoutType = confirm_metadata(
|
||||
layout = confirm_metadata(
|
||||
"omni_transaction",
|
||||
"OMNI transaction",
|
||||
omni.parse(data),
|
||||
|
@ -122,10 +122,6 @@ class Progress:
|
||||
self.progress_layout = progress_layout(text)
|
||||
|
||||
def report(self) -> None:
|
||||
from trezor import utils
|
||||
|
||||
if utils.DISABLE_ANIMATION:
|
||||
return
|
||||
p = int(1000 * self.progress / self.steps)
|
||||
self.progress_layout.report(p)
|
||||
|
||||
|
@ -7,7 +7,7 @@ from . import backup_types
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.enums import BackupType
|
||||
from trezor.ui.layouts.common import ProgressLayout
|
||||
from trezor.ui import ProgressLayout
|
||||
|
||||
|
||||
def get() -> tuple[bytes | None, BackupType]:
|
||||
|
@ -1,12 +1,7 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor import TR, io, utils, wire
|
||||
from trezor.ui.layouts import confirm_action, show_error_and_raise
|
||||
from trezor.utils import sd_hotswap_enabled
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.ui.layouts.common import ProgressLayout
|
||||
|
||||
|
||||
class SdCardUnavailable(wire.ProcessError):
|
||||
pass
|
||||
@ -102,6 +97,7 @@ async def ensure_sdcard(ensure_filesystem: bool = True) -> None:
|
||||
mounted.
|
||||
"""
|
||||
from trezor import sdcard
|
||||
from trezor.ui.layouts.progress import progress
|
||||
|
||||
while not sdcard.is_present():
|
||||
await _confirm_retry_insert_card()
|
||||
@ -125,11 +121,12 @@ async def ensure_sdcard(ensure_filesystem: bool = True) -> None:
|
||||
|
||||
# Proceed to formatting. Failure is caught by the outside OSError handler
|
||||
with sdcard.filesystem(mounted=False):
|
||||
_start_progress()
|
||||
fatfs.mkfs(_render_progress)
|
||||
progress_obj = progress()
|
||||
progress_obj.start()
|
||||
fatfs.mkfs(progress_obj.report)
|
||||
fatfs.mount()
|
||||
fatfs.setlabel("TREZOR")
|
||||
_finish_progress()
|
||||
progress_obj.stop()
|
||||
|
||||
# format and mount succeeded
|
||||
return
|
||||
@ -157,30 +154,3 @@ async def request_sd_salt() -> bytearray | None:
|
||||
# In either case, there is no good way to recover. If the user clicks Retry,
|
||||
# we will try again.
|
||||
await confirm_retry_sd()
|
||||
|
||||
|
||||
_progress_obj: ProgressLayout | None = None
|
||||
|
||||
|
||||
def _start_progress() -> None:
|
||||
from trezor import workflow
|
||||
from trezor.ui.layouts.progress import progress
|
||||
|
||||
global _progress_obj
|
||||
|
||||
if not utils.DISABLE_ANIMATION:
|
||||
# Because we are drawing to the screen manually, without a layout, we
|
||||
# should make sure that no other layout is running.
|
||||
workflow.close_others()
|
||||
_progress_obj = progress()
|
||||
|
||||
|
||||
def _render_progress(progress: int) -> None:
|
||||
global _progress_obj
|
||||
if _progress_obj is not None:
|
||||
_progress_obj.report(progress)
|
||||
|
||||
|
||||
def _finish_progress() -> None:
|
||||
global _progress_obj
|
||||
_progress_obj = None
|
||||
|
@ -4,20 +4,21 @@ if not __debug__:
|
||||
halt("debug mode inactive")
|
||||
|
||||
if __debug__:
|
||||
import utime
|
||||
from micropython import const
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import trezorui2
|
||||
from storage import debug as storage
|
||||
from storage.debug import debug_events
|
||||
from trezor import log, loop, utils, wire
|
||||
from trezor.enums import MessageType
|
||||
from trezor.messages import DebugLinkLayout, Success
|
||||
from trezor import io, log, loop, ui, utils, wire, workflow
|
||||
from trezor.enums import DebugWaitType, MessageType
|
||||
from trezor.messages import Success
|
||||
from trezor.ui import display
|
||||
from trezor.wire import context
|
||||
|
||||
from apps import workflow_handlers
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
from trezor.enums import DebugButton, DebugPhysicalButton, DebugSwipeDirection
|
||||
from trezor.messages import (
|
||||
DebugLinkDecision,
|
||||
DebugLinkEraseSdCard,
|
||||
@ -25,33 +26,22 @@ if __debug__:
|
||||
DebugLinkOptigaSetSecMax,
|
||||
DebugLinkRecordScreen,
|
||||
DebugLinkReseedRandom,
|
||||
DebugLinkResetDebugEvents,
|
||||
DebugLinkState,
|
||||
DebugLinkWatchLayout,
|
||||
)
|
||||
from trezor.ui import Layout
|
||||
from trezor.wire import WireInterface, context
|
||||
|
||||
swipe_chan = loop.chan()
|
||||
result_chan = loop.chan()
|
||||
button_chan = loop.chan()
|
||||
click_chan = loop.chan()
|
||||
swipe_signal = swipe_chan.take
|
||||
result_signal = result_chan.take
|
||||
button_signal = button_chan.take
|
||||
click_signal = click_chan.take
|
||||
Handler = Callable[[Any], Awaitable[Any]]
|
||||
|
||||
debuglink_decision_chan = loop.chan()
|
||||
|
||||
layout_change_chan = loop.chan()
|
||||
layout_change_box = loop.mailbox()
|
||||
|
||||
DEBUG_CONTEXT: context.Context | None = None
|
||||
|
||||
LAYOUT_WATCHER_NONE = 0
|
||||
LAYOUT_WATCHER_STATE = 1
|
||||
LAYOUT_WATCHER_LAYOUT = 2
|
||||
|
||||
REFRESH_INDEX = 0
|
||||
|
||||
_DEADLOCK_SLEEP_MS = const(3000)
|
||||
_DEADLOCK_DETECT_SLEEP = loop.sleep(_DEADLOCK_SLEEP_MS)
|
||||
|
||||
def screenshot() -> bool:
|
||||
if storage.save_screen:
|
||||
# Starting with "refresh00", allowing for 100 emulator restarts
|
||||
@ -62,173 +52,242 @@ if __debug__:
|
||||
return True
|
||||
return False
|
||||
|
||||
def notify_layout_change(layout: Layout, event_id: int | None = None) -> None:
|
||||
layout.read_content_into(storage.current_content_tokens)
|
||||
if storage.watch_layout_changes or layout_change_chan.takers:
|
||||
payload = (event_id, storage.current_content_tokens)
|
||||
layout_change_chan.publish(payload)
|
||||
def notify_layout_change(layout: Layout | None) -> None:
|
||||
layout_change_box.put(layout, replace=True)
|
||||
|
||||
async def _dispatch_debuglink_decision(
|
||||
event_id: int | None, msg: DebugLinkDecision
|
||||
def layout_is_ready() -> bool:
|
||||
layout = ui.CURRENT_LAYOUT
|
||||
return isinstance(layout, ui.Layout) and layout.is_layout_attached()
|
||||
|
||||
def wait_until_layout_is_running(timeout: int | None = _DEADLOCK_SLEEP_MS) -> Awaitable[None]: # type: ignore [awaitable-return-type]
|
||||
start = utime.ticks_ms()
|
||||
layout_change_box.clear()
|
||||
while not layout_is_ready():
|
||||
yield layout_change_box # type: ignore [awaitable-return-type]
|
||||
now = utime.ticks_ms()
|
||||
if timeout and utime.ticks_diff(now, start) > timeout:
|
||||
raise wire.FirmwareError(
|
||||
"layout deadlock detected (did you send a ButtonAck?)"
|
||||
)
|
||||
|
||||
async def return_layout_change(
|
||||
ctx: wire.context.Context, detect_deadlock: bool = False
|
||||
) -> None:
|
||||
# set up the wait
|
||||
storage.layout_watcher = True
|
||||
|
||||
# wait for layout change
|
||||
while True:
|
||||
if not detect_deadlock or not layout_change_box.is_empty():
|
||||
# short-circuit if there is a result already waiting
|
||||
next_layout = await layout_change_box
|
||||
else:
|
||||
next_layout = await loop.race(layout_change_box, _DEADLOCK_DETECT_SLEEP)
|
||||
|
||||
if isinstance(next_layout, int):
|
||||
# sleep result from the deadlock detector
|
||||
raise wire.FirmwareError("layout deadlock detected")
|
||||
|
||||
if next_layout is None:
|
||||
# we are reading the "layout ended" event, spin once more to grab the
|
||||
# "new layout started" event
|
||||
continue
|
||||
|
||||
if layout_is_ready():
|
||||
break
|
||||
|
||||
assert ui.CURRENT_LAYOUT is next_layout
|
||||
|
||||
# send the message and reset the wait
|
||||
storage.layout_watcher = False
|
||||
await ctx.write(_state())
|
||||
|
||||
async def _layout_click(x: int, y: int, hold_ms: int = 0) -> None:
|
||||
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
|
||||
ui.CURRENT_LAYOUT._event(
|
||||
ui.CURRENT_LAYOUT.layout.touch_event, io.TOUCH_START, x, y
|
||||
)
|
||||
|
||||
if hold_ms:
|
||||
await loop.sleep(hold_ms)
|
||||
workflow.idle_timer.touch()
|
||||
|
||||
if not layout_is_ready():
|
||||
return
|
||||
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
|
||||
) -> None:
|
||||
from trezor.enums import DebugPhysicalButton
|
||||
|
||||
buttons = []
|
||||
|
||||
if debug_btn == DebugPhysicalButton.LEFT_BTN:
|
||||
buttons.append(io.BUTTON_LEFT)
|
||||
elif debug_btn == DebugPhysicalButton.RIGHT_BTN:
|
||||
buttons.append(io.BUTTON_RIGHT)
|
||||
elif debug_btn == DebugPhysicalButton.MIDDLE_BTN:
|
||||
buttons.append(io.BUTTON_LEFT)
|
||||
buttons.append(io.BUTTON_RIGHT)
|
||||
|
||||
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
|
||||
for btn in buttons:
|
||||
ui.CURRENT_LAYOUT._event(
|
||||
ui.CURRENT_LAYOUT.layout.button_event, io.BUTTON_PRESSED, btn
|
||||
)
|
||||
|
||||
if hold_ms:
|
||||
await loop.sleep(hold_ms)
|
||||
workflow.idle_timer.touch()
|
||||
|
||||
if not layout_is_ready():
|
||||
return
|
||||
for btn in buttons:
|
||||
ui.CURRENT_LAYOUT._event(
|
||||
ui.CURRENT_LAYOUT.layout.button_event, io.BUTTON_RELEASED, btn
|
||||
)
|
||||
|
||||
if utils.USE_TOUCH:
|
||||
|
||||
async def _layout_swipe(direction: DebugSwipeDirection) -> None: # type: ignore [obscured by a declaration of the same name]
|
||||
from trezor.enums import DebugSwipeDirection
|
||||
|
||||
orig_x = orig_y = 120
|
||||
off_x, off_y = {
|
||||
DebugSwipeDirection.UP: (0, -30),
|
||||
DebugSwipeDirection.DOWN: (0, 30),
|
||||
DebugSwipeDirection.LEFT: (-30, 0),
|
||||
DebugSwipeDirection.RIGHT: (30, 0),
|
||||
}[direction]
|
||||
|
||||
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
|
||||
for event, x, y in (
|
||||
(io.TOUCH_START, orig_x, orig_y),
|
||||
(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),
|
||||
):
|
||||
ui.CURRENT_LAYOUT._event(
|
||||
ui.CURRENT_LAYOUT.layout.touch_event, event, x, y
|
||||
)
|
||||
|
||||
elif utils.USE_BUTTON:
|
||||
|
||||
def _layout_swipe(direction: DebugSwipeDirection) -> Awaitable[None]:
|
||||
from trezor.enums import DebugPhysicalButton, DebugSwipeDirection
|
||||
|
||||
if direction == DebugSwipeDirection.UP:
|
||||
button = DebugPhysicalButton.RIGHT_BTN
|
||||
elif direction == DebugSwipeDirection.DOWN:
|
||||
button = DebugPhysicalButton.LEFT_BTN
|
||||
else:
|
||||
raise RuntimeError # unsupported swipe direction on TR
|
||||
|
||||
return _layout_press_button(button)
|
||||
|
||||
else:
|
||||
raise RuntimeError # No way to swipe with no buttons and no touches
|
||||
|
||||
async def _layout_event(button: DebugButton) -> None:
|
||||
from trezor.enums import DebugButton
|
||||
|
||||
if msg.button is not None:
|
||||
if msg.button == DebugButton.NO:
|
||||
await result_chan.put((event_id, trezorui2.CANCELLED))
|
||||
elif msg.button == DebugButton.YES:
|
||||
await result_chan.put((event_id, trezorui2.CONFIRMED))
|
||||
elif msg.button == DebugButton.INFO:
|
||||
await result_chan.put((event_id, trezorui2.INFO))
|
||||
else:
|
||||
raise RuntimeError(f"Invalid msg.button - {msg.button}")
|
||||
elif msg.input is not None:
|
||||
await result_chan.put((event_id, msg.input))
|
||||
elif msg.swipe is not None:
|
||||
await swipe_chan.put((event_id, msg.swipe))
|
||||
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
|
||||
if button == DebugButton.NO:
|
||||
ui.CURRENT_LAYOUT._emit_message(trezorui2.CANCELLED)
|
||||
elif button == DebugButton.YES:
|
||||
ui.CURRENT_LAYOUT._emit_message(trezorui2.CONFIRMED)
|
||||
elif button == DebugButton.INFO:
|
||||
ui.CURRENT_LAYOUT._emit_message(trezorui2.INFO)
|
||||
else:
|
||||
# Sanity check. The message will be visible in terminal.
|
||||
raise RuntimeError("Invalid DebugLinkDecision message")
|
||||
raise RuntimeError("Invalid DebugButton")
|
||||
|
||||
async def debuglink_decision_dispatcher() -> None:
|
||||
while True:
|
||||
event_id, msg = await debuglink_decision_chan.take()
|
||||
await _dispatch_debuglink_decision(event_id, msg)
|
||||
|
||||
async def get_layout_change_content() -> list[str]:
|
||||
awaited_event_id = debug_events.awaited_event
|
||||
last_result_id = debug_events.last_result
|
||||
|
||||
if awaited_event_id is not None and awaited_event_id == last_result_id:
|
||||
# We are awaiting the event that just happened - return current state
|
||||
return storage.current_content_tokens
|
||||
|
||||
while True:
|
||||
event_id, content = await layout_change_chan.take()
|
||||
if awaited_event_id is None or event_id is None:
|
||||
# Not waiting for anything or event does not have ID
|
||||
break
|
||||
elif event_id == awaited_event_id:
|
||||
# We found what we were waiting for
|
||||
debug_events.awaited_event = None
|
||||
break
|
||||
elif event_id > awaited_event_id:
|
||||
# Sanity check
|
||||
pass
|
||||
# TODO: find out why this sometimes happens on TR when running tests with
|
||||
# "physical" emulator (./emu.py)
|
||||
# raise RuntimeError(
|
||||
# f"Waiting for event that already happened - {event_id} > {awaited_event_id}"
|
||||
# )
|
||||
|
||||
if awaited_event_id is not None:
|
||||
# Updating last result
|
||||
debug_events.last_result = awaited_event_id
|
||||
|
||||
return content
|
||||
|
||||
async def return_layout_change() -> None: # type: ignore [Return type of async generator]
|
||||
content_tokens = await get_layout_change_content()
|
||||
|
||||
# spin for a bit until DEBUG_CONTEXT becomes available
|
||||
while not isinstance(DEBUG_CONTEXT, context.Context):
|
||||
yield # type: ignore [Return type of async generator]
|
||||
|
||||
if storage.layout_watcher is LAYOUT_WATCHER_LAYOUT:
|
||||
await DEBUG_CONTEXT.write(DebugLinkLayout(tokens=content_tokens))
|
||||
else:
|
||||
from trezor.messages import DebugLinkState
|
||||
|
||||
await DEBUG_CONTEXT.write(DebugLinkState(tokens=content_tokens))
|
||||
storage.layout_watcher = LAYOUT_WATCHER_NONE
|
||||
|
||||
async def dispatch_DebugLinkWatchLayout(msg: DebugLinkWatchLayout) -> Success:
|
||||
from trezor import ui
|
||||
|
||||
layout_change_chan.putters.clear()
|
||||
if msg.watch:
|
||||
await ui.wait_until_layout_is_running()
|
||||
storage.watch_layout_changes = bool(msg.watch)
|
||||
log.debug(__name__, "Watch layout changes: %s", storage.watch_layout_changes)
|
||||
return Success()
|
||||
|
||||
async def dispatch_DebugLinkResetDebugEvents(
|
||||
msg: DebugLinkResetDebugEvents,
|
||||
) -> Success:
|
||||
# Resetting the debug events makes sure that the previous
|
||||
# events/layouts are not mixed with the new ones.
|
||||
storage.reset_debug_events()
|
||||
return Success()
|
||||
|
||||
async def dispatch_DebugLinkDecision(msg: DebugLinkDecision) -> None:
|
||||
from trezor import workflow
|
||||
async def dispatch_DebugLinkDecision(
|
||||
msg: DebugLinkDecision,
|
||||
) -> DebugLinkState | None:
|
||||
from trezor import ui, workflow
|
||||
|
||||
workflow.idle_timer.touch()
|
||||
|
||||
if debuglink_decision_chan.putters:
|
||||
log.warning(__name__, "DebugLinkDecision queue is not empty")
|
||||
|
||||
x = msg.x # local_cache_attribute
|
||||
y = msg.y # local_cache_attribute
|
||||
|
||||
# Incrementing the counter for last events so we know what to await
|
||||
debug_events.last_event += 1
|
||||
await wait_until_layout_is_running()
|
||||
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
|
||||
layout_change_box.clear()
|
||||
|
||||
# Touchscreen devices click on specific coordinates, with possible hold
|
||||
if (
|
||||
x is not None
|
||||
and y is not None
|
||||
and utils.INTERNAL_MODEL # pylint: disable=internal-model-tuple-comparison
|
||||
in ("T2T1", "T3T1", "D001")
|
||||
):
|
||||
click_chan.publish((debug_events.last_event, x, y, msg.hold_ms))
|
||||
# Button devices press specific button
|
||||
elif (
|
||||
msg.physical_button is not None
|
||||
and utils.INTERNAL_MODEL # pylint: disable=internal-model-tuple-comparison
|
||||
in ("T2B1", "T3B1")
|
||||
):
|
||||
button_chan.publish(
|
||||
(debug_events.last_event, msg.physical_button, msg.hold_ms)
|
||||
)
|
||||
else:
|
||||
# Will get picked up by _dispatch_debuglink_decision eventually
|
||||
debuglink_decision_chan.publish((debug_events.last_event, msg))
|
||||
try:
|
||||
# click on specific coordinates, with possible hold
|
||||
if x is not None and y is not None:
|
||||
await _layout_click(x, y, msg.hold_ms or 0)
|
||||
# press specific button
|
||||
elif msg.physical_button is not None:
|
||||
await _layout_press_button(msg.physical_button, msg.hold_ms or 0)
|
||||
elif msg.swipe is not None:
|
||||
await _layout_swipe(msg.swipe)
|
||||
elif msg.button is not None:
|
||||
await _layout_event(msg.button)
|
||||
elif msg.input is not None:
|
||||
ui.CURRENT_LAYOUT._emit_message(msg.input)
|
||||
else:
|
||||
raise RuntimeError("Invalid DebugLinkDecision message")
|
||||
|
||||
if msg.wait:
|
||||
# We wait for all the previously sent events
|
||||
debug_events.awaited_event = debug_events.last_event
|
||||
storage.layout_watcher = LAYOUT_WATCHER_LAYOUT
|
||||
loop.schedule(return_layout_change())
|
||||
except ui.Shutdown:
|
||||
# Shutdown should be raised if the layout is supposed to stop after
|
||||
# processing the event. In that case, we need to yield to give the layout
|
||||
# callers time to finish their jobs. We want to make sure that the handling
|
||||
# does not continue until the event is truly processed.
|
||||
result = await layout_change_box
|
||||
assert result is None
|
||||
|
||||
async def dispatch_DebugLinkGetState(
|
||||
msg: DebugLinkGetState,
|
||||
) -> DebugLinkState | None:
|
||||
# If no exception was raised, the layout did not shut down. That means that it
|
||||
# just updated itself. The update is already live for the caller to retrieve.
|
||||
|
||||
def _state() -> DebugLinkState:
|
||||
from trezor.messages import DebugLinkState
|
||||
|
||||
from apps.common import mnemonic, passphrase
|
||||
|
||||
m = DebugLinkState()
|
||||
m.mnemonic_secret = mnemonic.get_secret()
|
||||
m.mnemonic_type = mnemonic.get_type()
|
||||
m.passphrase_protection = passphrase.is_enabled()
|
||||
m.reset_entropy = storage.reset_internal_entropy
|
||||
tokens = []
|
||||
|
||||
if msg.wait_layout:
|
||||
if not storage.watch_layout_changes:
|
||||
raise wire.ProcessError("Layout is not watched")
|
||||
storage.layout_watcher = LAYOUT_WATCHER_STATE
|
||||
# We wait for the last previously sent event to finish
|
||||
debug_events.awaited_event = debug_events.last_event
|
||||
loop.schedule(return_layout_change())
|
||||
return None
|
||||
def callback(*args: str) -> None:
|
||||
tokens.extend(args)
|
||||
|
||||
if ui.CURRENT_LAYOUT is not None:
|
||||
ui.CURRENT_LAYOUT.layout.trace(callback)
|
||||
|
||||
return DebugLinkState(
|
||||
mnemonic_secret=mnemonic.get_secret(),
|
||||
mnemonic_type=mnemonic.get_type(),
|
||||
passphrase_protection=passphrase.is_enabled(),
|
||||
reset_entropy=storage.reset_internal_entropy,
|
||||
tokens=tokens,
|
||||
)
|
||||
|
||||
async def dispatch_DebugLinkGetState(
|
||||
msg: DebugLinkGetState,
|
||||
) -> DebugLinkState | None:
|
||||
if msg.wait_layout == DebugWaitType.IMMEDIATE:
|
||||
return _state()
|
||||
|
||||
assert DEBUG_CONTEXT is not None
|
||||
if msg.wait_layout == DebugWaitType.NEXT_LAYOUT:
|
||||
layout_change_box.clear()
|
||||
return await return_layout_change(DEBUG_CONTEXT, detect_deadlock=False)
|
||||
|
||||
# default behavior: msg.wait_layout == DebugWaitType.CURRENT_LAYOUT
|
||||
if not layout_is_ready():
|
||||
return await return_layout_change(DEBUG_CONTEXT, detect_deadlock=True)
|
||||
else:
|
||||
m.tokens = storage.current_content_tokens
|
||||
|
||||
return m
|
||||
return _state()
|
||||
|
||||
async def dispatch_DebugLinkRecordScreen(msg: DebugLinkRecordScreen) -> Success:
|
||||
if msg.target_directory:
|
||||
# Ensure we consistently start at a layout, instead of randomly sometimes
|
||||
# hitting the pause between layouts and rendering the "upcoming" one.
|
||||
await wait_until_layout_is_running()
|
||||
|
||||
# In case emulator is restarted but we still want to record screenshots
|
||||
# into the same directory as before, we need to increment the refresh index,
|
||||
# so that the screenshots are not overwritten.
|
||||
@ -236,6 +295,14 @@ if __debug__:
|
||||
REFRESH_INDEX = msg.refresh_index
|
||||
storage.save_screen_directory = msg.target_directory
|
||||
storage.save_screen = True
|
||||
|
||||
# force repaint current layout, in order to take an initial screenshot
|
||||
# (doing it this way also clears the red square, because the repaint is
|
||||
# happening with screenshotting already enabled)
|
||||
assert isinstance(ui.CURRENT_LAYOUT, ui.Layout)
|
||||
ui.CURRENT_LAYOUT.request_complete_repaint()
|
||||
ui.CURRENT_LAYOUT._paint()
|
||||
|
||||
else:
|
||||
storage.save_screen = False
|
||||
display.clear_save() # clear C buffers
|
||||
@ -271,26 +338,6 @@ if __debug__:
|
||||
sdcard.power_off()
|
||||
return Success()
|
||||
|
||||
def boot() -> None:
|
||||
register = workflow_handlers.register # local_cache_attribute
|
||||
|
||||
register(MessageType.DebugLinkDecision, dispatch_DebugLinkDecision) # type: ignore [Argument of type "(msg: DebugLinkDecision) -> Coroutine[Any, Any, None]" cannot be assigned to parameter "handler" of type "Handler[Msg@register]" in function "register"]
|
||||
register(MessageType.DebugLinkGetState, dispatch_DebugLinkGetState) # type: ignore [Argument of type "(msg: DebugLinkGetState) -> Coroutine[Any, Any, DebugLinkState | None]" cannot be assigned to parameter "handler" of type "Handler[Msg@register]" in function "register"]
|
||||
register(MessageType.DebugLinkReseedRandom, dispatch_DebugLinkReseedRandom)
|
||||
register(MessageType.DebugLinkRecordScreen, dispatch_DebugLinkRecordScreen)
|
||||
register(MessageType.DebugLinkEraseSdCard, dispatch_DebugLinkEraseSdCard)
|
||||
register(MessageType.DebugLinkWatchLayout, dispatch_DebugLinkWatchLayout)
|
||||
register(
|
||||
MessageType.DebugLinkResetDebugEvents, dispatch_DebugLinkResetDebugEvents
|
||||
)
|
||||
register(
|
||||
MessageType.DebugLinkOptigaSetSecMax, dispatch_DebugLinkOptigaSetSecMax
|
||||
)
|
||||
|
||||
loop.schedule(debuglink_decision_dispatcher())
|
||||
if storage.layout_watcher is not LAYOUT_WATCHER_NONE:
|
||||
loop.schedule(return_layout_change())
|
||||
|
||||
async def dispatch_DebugLinkOptigaSetSecMax(
|
||||
msg: DebugLinkOptigaSetSecMax,
|
||||
) -> Success:
|
||||
@ -301,3 +348,89 @@ if __debug__:
|
||||
return Success()
|
||||
else:
|
||||
raise wire.UnexpectedMessage("Optiga not supported")
|
||||
|
||||
async def _no_op(_msg: Any) -> Success:
|
||||
return Success()
|
||||
|
||||
WIRE_BUFFER_DEBUG = bytearray(1024)
|
||||
|
||||
async def handle_session(iface: WireInterface) -> None:
|
||||
from trezor import protobuf, wire
|
||||
from trezor.wire import codec_v1, context
|
||||
|
||||
global DEBUG_CONTEXT
|
||||
|
||||
DEBUG_CONTEXT = ctx = context.Context(iface, 0, WIRE_BUFFER_DEBUG)
|
||||
|
||||
if storage.layout_watcher:
|
||||
try:
|
||||
await return_layout_change(ctx)
|
||||
except Exception as e:
|
||||
log.exception(__name__, e)
|
||||
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
msg = await ctx.read_from_wire()
|
||||
except codec_v1.CodecError as exc:
|
||||
log.exception(__name__, exc)
|
||||
await ctx.write(wire.failure(exc))
|
||||
continue
|
||||
|
||||
req_type = None
|
||||
try:
|
||||
req_type = protobuf.type_for_wire(msg.type)
|
||||
msg_type = req_type.MESSAGE_NAME
|
||||
except Exception:
|
||||
msg_type = f"{msg.type} - unknown message type"
|
||||
log.debug(
|
||||
__name__,
|
||||
"%s:%x receive: <%s>",
|
||||
ctx.iface.iface_num(),
|
||||
ctx.sid,
|
||||
msg_type,
|
||||
)
|
||||
|
||||
if msg.type not in WORKFLOW_HANDLERS:
|
||||
await ctx.write(wire.unexpected_message())
|
||||
continue
|
||||
|
||||
elif req_type is None:
|
||||
# Message type is in workflow handlers but not in protobuf
|
||||
# definitions. This indicates a deprecated message.
|
||||
# We put a no-op handler for those messages.
|
||||
# XXX return a Failure here?
|
||||
await ctx.write(Success())
|
||||
continue
|
||||
|
||||
req_msg = wire.wrap_protobuf_load(msg.data, req_type)
|
||||
try:
|
||||
res_msg = await WORKFLOW_HANDLERS[msg.type](req_msg)
|
||||
except Exception as exc:
|
||||
# Log and ignore, never die.
|
||||
log.exception(__name__, exc)
|
||||
res_msg = wire.failure(exc)
|
||||
|
||||
if res_msg is not None:
|
||||
await ctx.write(res_msg)
|
||||
|
||||
except Exception as exc:
|
||||
# Log and try again. This should only happen for USB errors and we
|
||||
# try to stay robust in such case.
|
||||
log.exception(__name__, exc)
|
||||
|
||||
WORKFLOW_HANDLERS: dict[int, Handler] = {
|
||||
MessageType.DebugLinkDecision: dispatch_DebugLinkDecision,
|
||||
MessageType.DebugLinkGetState: dispatch_DebugLinkGetState,
|
||||
MessageType.DebugLinkReseedRandom: dispatch_DebugLinkReseedRandom,
|
||||
MessageType.DebugLinkRecordScreen: dispatch_DebugLinkRecordScreen,
|
||||
MessageType.DebugLinkEraseSdCard: dispatch_DebugLinkEraseSdCard,
|
||||
MessageType.DebugLinkOptigaSetSecMax: dispatch_DebugLinkOptigaSetSecMax,
|
||||
MessageType.DebugLinkWatchLayout: _no_op,
|
||||
MessageType.DebugLinkResetDebugEvents: _no_op,
|
||||
}
|
||||
|
||||
def boot() -> None:
|
||||
import usb
|
||||
|
||||
loop.schedule(handle_session(usb.iface_debug))
|
||||
|
@ -20,7 +20,6 @@ if TYPE_CHECKING:
|
||||
EthereumTokenInfo,
|
||||
EthereumTxAck,
|
||||
)
|
||||
from trezor.ui.layouts.common import ProgressLayout
|
||||
|
||||
from apps.common.keychain import Keychain
|
||||
|
||||
@ -40,7 +39,9 @@ async def sign_tx(
|
||||
keychain: Keychain,
|
||||
defs: Definitions,
|
||||
) -> EthereumTxRequest:
|
||||
from trezor import TR
|
||||
from trezor.crypto.hashlib import sha3_256
|
||||
from trezor.ui.layouts.progress import progress
|
||||
from trezor.utils import HashWriter
|
||||
|
||||
from apps.common import paths
|
||||
@ -69,9 +70,8 @@ async def sign_tx(
|
||||
)
|
||||
await confirm_tx_data(msg, defs, address_bytes, maximum_fee, fee_items, data_total)
|
||||
|
||||
_start_progress()
|
||||
|
||||
_render_progress(30)
|
||||
progress_obj = progress(title=TR.progress__signing_transaction)
|
||||
progress_obj.report(30)
|
||||
|
||||
# sign
|
||||
data = bytearray()
|
||||
@ -95,7 +95,7 @@ async def sign_tx(
|
||||
rlp.write_header(sha, data_total, rlp.STRING_HEADER_BYTE, data)
|
||||
sha.extend(data)
|
||||
|
||||
_render_progress(60)
|
||||
progress_obj.report(60)
|
||||
|
||||
while data_left > 0:
|
||||
resp = await send_request_chunk(data_left)
|
||||
@ -110,7 +110,7 @@ async def sign_tx(
|
||||
digest = sha.get_digest()
|
||||
result = _sign_digest(msg, keychain, digest)
|
||||
|
||||
_finish_progress()
|
||||
progress_obj.stop()
|
||||
|
||||
return result
|
||||
|
||||
@ -396,30 +396,3 @@ async def _handle_staking_tx_claim(
|
||||
await require_confirm_claim(
|
||||
staking_addr, msg.address_n, maximum_fee, fee_items, network, chunkify
|
||||
)
|
||||
|
||||
|
||||
_progress_obj: ProgressLayout | None = None
|
||||
|
||||
|
||||
def _start_progress() -> None:
|
||||
from trezor import TR, workflow
|
||||
from trezor.ui.layouts.progress import progress
|
||||
|
||||
global _progress_obj
|
||||
|
||||
if not utils.DISABLE_ANIMATION:
|
||||
# Because we are drawing to the screen manually, without a layout, we
|
||||
# should make sure that no other layout is running.
|
||||
workflow.close_others()
|
||||
_progress_obj = progress(title=TR.progress__signing_transaction)
|
||||
|
||||
|
||||
def _render_progress(progress: int) -> None:
|
||||
global _progress_obj
|
||||
if _progress_obj is not None:
|
||||
_progress_obj.report(progress)
|
||||
|
||||
|
||||
def _finish_progress() -> None:
|
||||
global _progress_obj
|
||||
_progress_obj = None
|
||||
|
@ -14,7 +14,7 @@ from apps.common.authorization import is_set_any_session
|
||||
async def busyscreen() -> None:
|
||||
obj = Busyscreen(busy_expiry_ms())
|
||||
try:
|
||||
await obj
|
||||
await obj.get_result()
|
||||
finally:
|
||||
obj.__del__()
|
||||
|
||||
@ -53,7 +53,7 @@ async def homescreen() -> None:
|
||||
hold_to_lock=config.has_pin(),
|
||||
)
|
||||
try:
|
||||
await obj
|
||||
await obj.get_result()
|
||||
finally:
|
||||
obj.__del__()
|
||||
|
||||
@ -72,7 +72,7 @@ async def _lockscreen(screensaver: bool = False) -> None:
|
||||
coinjoin_authorized=is_set_any_session(MessageType.AuthorizeCoinJoin),
|
||||
)
|
||||
try:
|
||||
await obj
|
||||
await obj.get_result()
|
||||
finally:
|
||||
obj.__del__()
|
||||
# Otherwise proceed directly to unlock() call. If the device is already unlocked,
|
||||
|
@ -8,7 +8,7 @@ if TYPE_CHECKING:
|
||||
from typing import Callable
|
||||
|
||||
from trezor.messages import ChangeLanguage, Success
|
||||
from trezor.ui.layouts.common import ProgressLayout
|
||||
from trezor.ui import ProgressLayout
|
||||
|
||||
_CHUNK_SIZE = const(1024)
|
||||
|
||||
|
@ -23,12 +23,11 @@ if TYPE_CHECKING:
|
||||
async def request_mnemonic(
|
||||
word_count: int, backup_type: BackupType | None
|
||||
) -> str | None:
|
||||
from trezor.ui.layouts.common import button_request
|
||||
from trezor.ui.layouts.recovery import request_word
|
||||
|
||||
from . import word_validity
|
||||
|
||||
await button_request("mnemonic", code=ButtonRequestType.MnemonicInput)
|
||||
send_button_request = True
|
||||
|
||||
# Pre-allocate the list to enable going back and overwriting words.
|
||||
words: list[str] = [""] * word_count
|
||||
@ -43,8 +42,10 @@ async def request_mnemonic(
|
||||
i,
|
||||
word_count,
|
||||
is_slip39=backup_types.is_slip39_word_count(word_count),
|
||||
send_button_request=send_button_request,
|
||||
prefill_word=words[i],
|
||||
)
|
||||
send_button_request = False
|
||||
|
||||
if not word:
|
||||
# User has decided to go back
|
||||
|
@ -2,9 +2,6 @@ from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.messages import FirmwareHash, GetFirmwareHash
|
||||
from trezor.ui.layouts.common import ProgressLayout
|
||||
|
||||
_progress_obj: ProgressLayout | None = None
|
||||
|
||||
|
||||
async def get_firmware_hash(msg: GetFirmwareHash) -> FirmwareHash:
|
||||
@ -14,20 +11,14 @@ async def get_firmware_hash(msg: GetFirmwareHash) -> FirmwareHash:
|
||||
from trezor.utils import firmware_hash
|
||||
|
||||
workflow.close_others()
|
||||
global _progress_obj
|
||||
_progress_obj = progress()
|
||||
progress_obj = progress()
|
||||
|
||||
def report(progress: int, total: int) -> None:
|
||||
progress_obj.report(1000 * progress // total)
|
||||
|
||||
try:
|
||||
hash = firmware_hash(msg.challenge, _render_progress)
|
||||
hash = firmware_hash(msg.challenge, report)
|
||||
except ValueError as e:
|
||||
raise wire.DataError(str(e))
|
||||
finally:
|
||||
_progress_obj = None
|
||||
|
||||
return FirmwareHash(hash=hash)
|
||||
|
||||
|
||||
def _render_progress(progress: int, total: int) -> None:
|
||||
global _progress_obj
|
||||
if _progress_obj is not None:
|
||||
_progress_obj.report(1000 * progress // total)
|
||||
|
@ -8,7 +8,8 @@ import storage.device as storage_device
|
||||
from trezor import TR, config, io, log, loop, utils, wire, workflow
|
||||
from trezor.crypto import hashlib
|
||||
from trezor.crypto.curve import nist256p1
|
||||
from trezor.ui.layouts import show_error_popup
|
||||
from trezor.ui import Layout
|
||||
from trezor.ui.layouts import error_popup
|
||||
|
||||
from apps.base import set_homescreen
|
||||
from apps.common import cbor
|
||||
@ -615,16 +616,36 @@ async def _confirm_fido(title: str, credential: Credential) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
async def _show_error_popup(
|
||||
title: str,
|
||||
description: str,
|
||||
subtitle: str | None = None,
|
||||
description_param: str = "",
|
||||
*,
|
||||
button: str = "",
|
||||
timeout_ms: int = 0,
|
||||
) -> None:
|
||||
popup = error_popup(
|
||||
title,
|
||||
description,
|
||||
subtitle,
|
||||
description_param,
|
||||
button=button,
|
||||
timeout_ms=timeout_ms,
|
||||
)
|
||||
await Layout(popup).get_result()
|
||||
|
||||
|
||||
async def _confirm_bogus_app(title: str) -> None:
|
||||
if _last_auth_valid:
|
||||
await show_error_popup(
|
||||
await _show_error_popup(
|
||||
title,
|
||||
TR.fido__device_already_registered,
|
||||
TR.fido__already_registered,
|
||||
timeout_ms=_POPUP_TIMEOUT_MS,
|
||||
)
|
||||
else:
|
||||
await show_error_popup(
|
||||
await _show_error_popup(
|
||||
title,
|
||||
TR.fido__device_not_registered,
|
||||
TR.fido__not_registered,
|
||||
@ -841,7 +862,7 @@ class Fido2ConfirmExcluded(Fido2ConfirmMakeCredential):
|
||||
await send_cmd(cmd, self.iface)
|
||||
self.finished = True
|
||||
|
||||
await show_error_popup(
|
||||
await _show_error_popup(
|
||||
TR.fido__title_register,
|
||||
TR.fido__device_already_registered_with_template,
|
||||
TR.fido__already_registered,
|
||||
@ -924,7 +945,7 @@ class Fido2ConfirmNoPin(State):
|
||||
await send_cmd(cmd, self.iface)
|
||||
self.finished = True
|
||||
|
||||
await show_error_popup(
|
||||
await _show_error_popup(
|
||||
TR.fido__title_verify_user,
|
||||
TR.fido__please_enable_pin_protection,
|
||||
TR.fido__unable_to_verify_user,
|
||||
@ -947,7 +968,7 @@ class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion):
|
||||
await send_cmd(cmd, self.iface)
|
||||
self.finished = True
|
||||
|
||||
await show_error_popup(
|
||||
await _show_error_popup(
|
||||
TR.fido__title_authenticate,
|
||||
TR.fido__not_registered_with_template,
|
||||
TR.fido__not_registered,
|
||||
@ -1059,6 +1080,7 @@ class DialogManager:
|
||||
|
||||
try:
|
||||
while self.result is _RESULT_NONE:
|
||||
workflow.close_others()
|
||||
result = await self.state.confirm_dialog()
|
||||
if isinstance(result, State):
|
||||
self.state = result
|
||||
|
@ -68,7 +68,7 @@ async def bootscreen() -> None:
|
||||
lockscreen = Lockscreen(
|
||||
label=storage.device.get_label(), bootscreen=True
|
||||
)
|
||||
await lockscreen
|
||||
await lockscreen.get_result()
|
||||
lockscreen.__del__()
|
||||
await verify_user_pin()
|
||||
storage.init_unlocked()
|
||||
|
@ -7,28 +7,6 @@ if __debug__:
|
||||
save_screen = False
|
||||
save_screen_directory = "."
|
||||
|
||||
current_content_tokens: list[str] = [""] * 60
|
||||
current_content_tokens.clear()
|
||||
|
||||
watch_layout_changes = False
|
||||
layout_watcher = 0
|
||||
layout_watcher = False
|
||||
|
||||
reset_internal_entropy: bytes = b""
|
||||
|
||||
class DebugEvents:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self) -> None:
|
||||
self.last_event = 0
|
||||
self.last_result: int | None = None
|
||||
self.awaited_event: int | None = None
|
||||
|
||||
debug_events = DebugEvents()
|
||||
|
||||
def reset_debug_events() -> None:
|
||||
debug_events.reset()
|
||||
|
||||
# Event resulted in the layout change, call
|
||||
# notify_layout_change with this ID in first_paint of next layout.
|
||||
new_layout_event_id: int | None = None
|
||||
|
7
core/src/trezor/enums/DebugWaitType.py
generated
Normal file
7
core/src/trezor/enums/DebugWaitType.py
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
# isort:skip_file
|
||||
|
||||
IMMEDIATE = 0
|
||||
NEXT_LAYOUT = 1
|
||||
CURRENT_LAYOUT = 2
|
5
core/src/trezor/enums/__init__.py
generated
5
core/src/trezor/enums/__init__.py
generated
@ -270,6 +270,11 @@ if TYPE_CHECKING:
|
||||
MIDDLE_BTN = 1
|
||||
RIGHT_BTN = 2
|
||||
|
||||
class DebugWaitType(IntEnum):
|
||||
IMMEDIATE = 0
|
||||
NEXT_LAYOUT = 1
|
||||
CURRENT_LAYOUT = 2
|
||||
|
||||
class EthereumDefinitionType(IntEnum):
|
||||
NETWORK = 0
|
||||
TOKEN = 1
|
||||
|
@ -671,24 +671,3 @@ class spawn(Syscall):
|
||||
is True, it would be calling close on self, which will result in a ValueError.
|
||||
"""
|
||||
return self.task is this_task
|
||||
|
||||
|
||||
class Timer(Syscall):
|
||||
def __init__(self) -> None:
|
||||
self.task: Task | None = None
|
||||
# Event::Attach is evaluated before task is set. Use this list to
|
||||
# buffer timers until task is set.
|
||||
self.before_task: list[tuple[int, Any]] = []
|
||||
|
||||
def handle(self, task: Task) -> None:
|
||||
self.task = task
|
||||
for deadline, value in self.before_task:
|
||||
schedule(self.task, value, deadline)
|
||||
self.before_task.clear()
|
||||
|
||||
def schedule(self, deadline: int, value: Any) -> None:
|
||||
deadline = utime.ticks_add(utime.ticks_ms(), deadline)
|
||||
if self.task is not None:
|
||||
schedule(self.task, value, deadline)
|
||||
else:
|
||||
self.before_task.append((deadline, value))
|
||||
|
45
core/src/trezor/messages.py
generated
45
core/src/trezor/messages.py
generated
@ -40,6 +40,7 @@ if TYPE_CHECKING:
|
||||
from trezor.enums import DebugButton # noqa: F401
|
||||
from trezor.enums import DebugPhysicalButton # noqa: F401
|
||||
from trezor.enums import DebugSwipeDirection # noqa: F401
|
||||
from trezor.enums import DebugWaitType # noqa: F401
|
||||
from trezor.enums import DecredStakingSpendType # noqa: F401
|
||||
from trezor.enums import EthereumDataType # noqa: F401
|
||||
from trezor.enums import EthereumDefinitionType # noqa: F401
|
||||
@ -2809,7 +2810,6 @@ if TYPE_CHECKING:
|
||||
input: "str | None"
|
||||
x: "int | None"
|
||||
y: "int | None"
|
||||
wait: "bool | None"
|
||||
hold_ms: "int | None"
|
||||
physical_button: "DebugPhysicalButton | None"
|
||||
|
||||
@ -2821,7 +2821,6 @@ if TYPE_CHECKING:
|
||||
input: "str | None" = None,
|
||||
x: "int | None" = None,
|
||||
y: "int | None" = None,
|
||||
wait: "bool | None" = None,
|
||||
hold_ms: "int | None" = None,
|
||||
physical_button: "DebugPhysicalButton | None" = None,
|
||||
) -> None:
|
||||
@ -2831,20 +2830,6 @@ if TYPE_CHECKING:
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkDecision"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class DebugLinkLayout(protobuf.MessageType):
|
||||
tokens: "list[str]"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tokens: "list[str] | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkLayout"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class DebugLinkReseedRandom(protobuf.MessageType):
|
||||
value: "int | None"
|
||||
|
||||
@ -2876,16 +2861,12 @@ if TYPE_CHECKING:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class DebugLinkGetState(protobuf.MessageType):
|
||||
wait_word_list: "bool | None"
|
||||
wait_word_pos: "bool | None"
|
||||
wait_layout: "bool | None"
|
||||
wait_layout: "DebugWaitType"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
wait_word_list: "bool | None" = None,
|
||||
wait_word_pos: "bool | None" = None,
|
||||
wait_layout: "bool | None" = None,
|
||||
wait_layout: "DebugWaitType | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@ -3031,26 +3012,6 @@ if TYPE_CHECKING:
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkEraseSdCard"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class DebugLinkWatchLayout(protobuf.MessageType):
|
||||
watch: "bool | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
watch: "bool | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkWatchLayout"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class DebugLinkResetDebugEvents(protobuf.MessageType):
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkResetDebugEvents"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class DebugLinkOptigaSetSecMax(protobuf.MessageType):
|
||||
|
||||
@classmethod
|
||||
|
@ -5,7 +5,7 @@ from . import config
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Container
|
||||
|
||||
from trezor.ui.layouts.common import ProgressLayout
|
||||
from trezor.ui import ProgressLayout
|
||||
|
||||
_previous_seconds: int | None = None
|
||||
_previous_remaining: str | None = None
|
||||
|
@ -1,21 +1,30 @@
|
||||
# pylint: disable=wrong-import-position
|
||||
import utime
|
||||
from micropython import const
|
||||
from trezorui import Display
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Generator
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor import loop, utils
|
||||
from trezorui2 import AttachType, BacklightLevels
|
||||
import trezorui2
|
||||
from trezor import io, log, loop, utils, wire, workflow
|
||||
from trezor.messages import ButtonAck, ButtonRequest
|
||||
from trezor.wire import context
|
||||
from trezorui2 import BacklightLevels, LayoutState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Any, Callable, Generator, Generic, Iterator, TypeVar
|
||||
|
||||
from trezorui2 import UiResult # noqa: F401
|
||||
from trezorui2 import AttachType, LayoutObj, UiResult # noqa: F401
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar("T", covariant=True)
|
||||
|
||||
else:
|
||||
Generic = [object]
|
||||
T = 0
|
||||
Generic = {T: object}
|
||||
|
||||
|
||||
if __debug__:
|
||||
trezorui2.disable_animation(bool(utils.DISABLE_ANIMATION))
|
||||
|
||||
|
||||
# all rendering is done through a singleton of `Display`
|
||||
display = Display()
|
||||
@ -28,15 +37,14 @@ MONO: int = Display.FONT_MONO
|
||||
WIDTH: int = Display.WIDTH
|
||||
HEIGHT: int = Display.HEIGHT
|
||||
|
||||
# channel used to cancel layouts, see `Cancelled` exception
|
||||
layout_chan = loop.chan()
|
||||
_REQUEST_ANIMATION_FRAME = const(1)
|
||||
"""Animation frame timer token.
|
||||
See `trezor::ui::layout::base::EventCtx::ANIM_FRAME_TIMER`.
|
||||
"""
|
||||
|
||||
# allow only one alert at a time to avoid alerts overlapping
|
||||
_alert_in_progress = False
|
||||
|
||||
# storing last transition type, so that next layout can continue nicely
|
||||
LAST_TRANSITION_OUT: AttachType | None = None
|
||||
|
||||
# in debug mode, display an indicator in top right corner
|
||||
if __debug__:
|
||||
|
||||
@ -100,102 +108,406 @@ def backlight_fade(val: int, delay: int = 14000, step: int = 15) -> None:
|
||||
display.backlight(val)
|
||||
|
||||
|
||||
class Result(Exception):
|
||||
class Shutdown(Exception):
|
||||
pass
|
||||
|
||||
|
||||
SHUTDOWN = Shutdown()
|
||||
|
||||
CURRENT_LAYOUT: "Layout | ProgressLayout | None" = None
|
||||
|
||||
|
||||
def set_current_layout(layout: "Layout | ProgressLayout | None") -> None:
|
||||
"""Set the current global layout.
|
||||
|
||||
All manipulation of the global `CURRENT_LAYOUT` MUST go through this function.
|
||||
It ensures that the transitions are always to/from None (so that there are never
|
||||
two layouts in RUNNING state), and that the debug UI is notified of the change.
|
||||
"""
|
||||
When components want to trigger layout completion, they do so through
|
||||
raising an instance of `Result`.
|
||||
global CURRENT_LAYOUT
|
||||
|
||||
See `Layout.__iter__` for details.
|
||||
"""
|
||||
# all transitions must be to/from None
|
||||
assert (CURRENT_LAYOUT is None) == (layout is not None)
|
||||
|
||||
def __init__(self, value: Any) -> None:
|
||||
super().__init__()
|
||||
self.value = value
|
||||
|
||||
|
||||
class Cancelled(Exception):
|
||||
"""
|
||||
Layouts can be explicitly cancelled. This usually happens when another
|
||||
layout starts, because only one layout can be running at the same time,
|
||||
and is done by raising `Cancelled` on the cancelled layout. Layouts
|
||||
should always re-raise such exceptions.
|
||||
|
||||
See `Layout.__iter__` for details.
|
||||
"""
|
||||
CURRENT_LAYOUT = layout
|
||||
|
||||
|
||||
class Layout(Generic[T]):
|
||||
"""Python-side handler and runner for the Rust based layouts.
|
||||
|
||||
Wrap a `LayoutObj` instance in `Layout` to be able to display the layout, run its
|
||||
event loop, and take part in global layout management. See
|
||||
[docs/core/misc/layout-lifecycle.md] for details.
|
||||
"""
|
||||
Abstract class.
|
||||
|
||||
Layouts are top-level components. Only one layout can be running at the
|
||||
same time. Layouts provide asynchronous interface, so a running task can
|
||||
wait for the layout to complete. Layouts complete when a `Result` is
|
||||
raised, usually from some of the child components.
|
||||
"""
|
||||
|
||||
def finalize(self) -> None:
|
||||
"""
|
||||
Called when the layout is done. Usually overridden to allow cleanup or storing context.
|
||||
"""
|
||||
pass
|
||||
|
||||
async def __iter__(self) -> T:
|
||||
"""
|
||||
Run the layout and wait until it completes. Returns the result value.
|
||||
Usually not overridden.
|
||||
"""
|
||||
if __debug__:
|
||||
# we want to call notify_layout_change() when the rendering is done;
|
||||
# but only the first time the layout is awaited. Here we indicate that we
|
||||
# are being awaited, and in handle_rendering() we send the appropriate event
|
||||
self.should_notify_layout_change = True
|
||||
|
||||
value = None
|
||||
try:
|
||||
# If any other layout is running (waiting on the layout channel),
|
||||
# we close it with the Cancelled exception, and wait until it is
|
||||
# closed, just to be sure.
|
||||
if layout_chan.takers:
|
||||
await layout_chan.put(Cancelled())
|
||||
# Now, no other layout should be running. In a loop, we create new
|
||||
# layout tasks and execute them in parallel, while waiting on the
|
||||
# layout channel. This allows other layouts to cancel us, and the
|
||||
# layout tasks to trigger restart by exiting (new tasks are created
|
||||
# and we continue, because we are in a loop).
|
||||
while True:
|
||||
await loop.race(layout_chan.take(), *self.create_tasks())
|
||||
except Result as result:
|
||||
# Result exception was raised, this means this layout is complete.
|
||||
value = result.value
|
||||
finally:
|
||||
self.finalize()
|
||||
return value
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def __await__(self) -> Generator[Any, Any, T]:
|
||||
return self.__iter__() # type: ignore [Coroutine[Any, Any, T@Layout]" is incompatible with "Generator[Any, Any, T@Layout]"]
|
||||
|
||||
else:
|
||||
__await__ = __iter__
|
||||
|
||||
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
||||
"""
|
||||
Called from `__iter__`. Creates and returns a sequence of tasks that
|
||||
run this layout. Tasks are executed in parallel. When one of them
|
||||
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__)
|
||||
@staticmethod
|
||||
def _trace(layout: LayoutObj) -> str:
|
||||
tokens = []
|
||||
|
||||
def callback(*args: str) -> None:
|
||||
tokens.extend(args)
|
||||
|
||||
layout.trace(callback)
|
||||
return "".join(tokens)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{repr(self)}({self._trace(self.layout)[:150]})"
|
||||
|
||||
@staticmethod
|
||||
def notify_debuglink(layout: "Layout | None") -> None:
|
||||
from apps.debug import notify_layout_change
|
||||
|
||||
notify_layout_change(layout)
|
||||
|
||||
def __init__(self, layout: LayoutObj[T]) -> None:
|
||||
"""Set up a layout."""
|
||||
self.layout = layout
|
||||
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
|
||||
self.state: LayoutState = LayoutState.INITIAL
|
||||
|
||||
def is_ready(self) -> bool:
|
||||
"""True if the layout is in READY state."""
|
||||
return CURRENT_LAYOUT is not self and self.result_box.is_empty()
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""True if the layout is in RUNNING state."""
|
||||
return CURRENT_LAYOUT is self
|
||||
|
||||
def is_stopped(self) -> bool:
|
||||
"""True if the layout is in STOPPED state."""
|
||||
return CURRENT_LAYOUT is not self and not self.result_box.is_empty()
|
||||
|
||||
def is_layout_attached(self) -> bool:
|
||||
return self.state is LayoutState.ATTACHED
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the layout, stopping any other RUNNING layout.
|
||||
|
||||
If the layout is already RUNNING, do nothing. If the layout is STOPPED, fail.
|
||||
"""
|
||||
global CURRENT_LAYOUT
|
||||
|
||||
# do nothing if we are already running
|
||||
if self.is_running():
|
||||
return
|
||||
|
||||
# make sure we are not restarted before picking the previous result
|
||||
assert self.is_ready()
|
||||
|
||||
transition_in = None
|
||||
|
||||
# set up the global layout, shutting down any competitors
|
||||
# (caller should still call `workflow.close_others()` to ensure that someone
|
||||
# else will not just shut us down immediately)
|
||||
if CURRENT_LAYOUT is not None:
|
||||
prev_layout = CURRENT_LAYOUT
|
||||
prev_layout.stop()
|
||||
transition_in = prev_layout.transition_out
|
||||
|
||||
assert CURRENT_LAYOUT is None
|
||||
# do not notify debuglink, we will do it when we receive an ATTACHED event
|
||||
set_current_layout(self)
|
||||
|
||||
# save context
|
||||
self.context = context.CURRENT_CONTEXT
|
||||
|
||||
# attach a timer callback and paint self
|
||||
self._event(self.layout.attach_timer_fn, self._set_timer, transition_in)
|
||||
|
||||
# spawn all tasks
|
||||
for task in self.create_tasks():
|
||||
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
|
||||
current layout.
|
||||
|
||||
The resulting state is either READY (if there is no result to be picked up) or
|
||||
STOPPED.
|
||||
|
||||
When called externally, this kills any tasks that wait for the result, assuming
|
||||
that the external `stop()` is a kill. When called internally, `_kill_taker` is
|
||||
set to False to indicate that a result became available and that the taker
|
||||
should be allowed to pick it up.
|
||||
"""
|
||||
global CURRENT_LAYOUT
|
||||
|
||||
# stop all running timers and spawned tasks
|
||||
for timer in self.timers.values():
|
||||
loop.close(timer)
|
||||
for task in self.tasks:
|
||||
if task != loop.this_task:
|
||||
loop.close(task)
|
||||
self.timers.clear()
|
||||
self.tasks.clear()
|
||||
|
||||
self.transition_out = self.layout.get_transition_out()
|
||||
|
||||
# shut down anyone who is waiting for the result
|
||||
if _kill_taker:
|
||||
self.result_box.maybe_close()
|
||||
|
||||
if CURRENT_LAYOUT is self:
|
||||
# fade to black -- backlight is off while no layout is running
|
||||
backlight_fade(BacklightLevels.NONE)
|
||||
|
||||
set_current_layout(None)
|
||||
if __debug__:
|
||||
self.notify_debuglink(None)
|
||||
|
||||
async def get_result(self) -> T:
|
||||
"""Wait for, and return, the result of this UI layout."""
|
||||
if self.is_ready():
|
||||
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()
|
||||
|
||||
def request_complete_repaint(self) -> None:
|
||||
"""Request a complete repaint of the layout."""
|
||||
msg = self.layout.request_complete_repaint()
|
||||
assert msg is None
|
||||
|
||||
def _event(self, event_call: Callable[..., LayoutState | None], *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."""
|
||||
if __debug__ and CURRENT_LAYOUT is not self:
|
||||
raise wire.FirmwareError("layout received an event but it is not running")
|
||||
|
||||
state = event_call(*args)
|
||||
self.transition_out = self.layout.get_transition_out()
|
||||
|
||||
if state is LayoutState.DONE:
|
||||
self._emit_message(self.layout.return_value())
|
||||
|
||||
elif state is LayoutState.ATTACHED:
|
||||
self._button_request()
|
||||
if __debug__:
|
||||
self.notify_debuglink(self)
|
||||
|
||||
if state is not None:
|
||||
self.state = state
|
||||
|
||||
if state is LayoutState.ATTACHED:
|
||||
self._first_paint()
|
||||
else:
|
||||
self._paint()
|
||||
|
||||
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 wire.FirmwareError(
|
||||
"button request already pending -- "
|
||||
"don't forget to yield your input flow from time to time ^_^"
|
||||
)
|
||||
|
||||
res = self.layout.button_request()
|
||||
if res is None:
|
||||
return
|
||||
|
||||
if self.context 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()
|
||||
if painted:
|
||||
refresh()
|
||||
if storage_cache.homescreen_shown is not None and painted:
|
||||
storage_cache.homescreen_shown = None
|
||||
|
||||
def _first_paint(self) -> None:
|
||||
"""Paint the layout for the first time after starting it.
|
||||
|
||||
This is a separate call in order for homescreens to be able to override and not
|
||||
paint when the screen contents are still valid.
|
||||
"""
|
||||
# Clear the screen of any leftovers.
|
||||
self.request_complete_repaint()
|
||||
self._paint()
|
||||
|
||||
# Turn the brightness on.
|
||||
backlight_fade(self.backlight_level)
|
||||
|
||||
def _set_timer(self, token: int, duration_ms: int) -> None:
|
||||
"""Timer callback for Rust layouts."""
|
||||
|
||||
async def timer_task() -> None:
|
||||
self.timers.pop(token)
|
||||
try:
|
||||
self._event(self.layout.timer, token)
|
||||
except Shutdown:
|
||||
pass
|
||||
|
||||
if token == _REQUEST_ANIMATION_FRAME and token in self.timers:
|
||||
# do not schedule another animation frame if one is already scheduled
|
||||
return
|
||||
|
||||
assert token not in self.timers
|
||||
task = timer_task()
|
||||
self.timers[token] = task
|
||||
deadline = utime.ticks_add(utime.ticks_ms(), duration_ms)
|
||||
loop.schedule(task, deadline=deadline)
|
||||
|
||||
def _emit_message(self, msg: Any) -> None:
|
||||
"""Process a message coming out of the Rust layout. Set is as a result and shut
|
||||
down the layout if appropriate, do nothing otherwise."""
|
||||
# when emitting a message, there should not be another one already waiting
|
||||
assert self.result_box.is_empty()
|
||||
self.stop(_kill_taker=False)
|
||||
self.result_box.put(msg)
|
||||
raise SHUTDOWN
|
||||
|
||||
def create_tasks(self) -> Iterator[loop.Task]:
|
||||
"""Set up background tasks for a layout.
|
||||
|
||||
Called from `start()`. Creates and yields a list of background tasks, typically
|
||||
event handlers for different interfaces.
|
||||
|
||||
Override and then `yield from super().create_tasks()` to add more tasks."""
|
||||
if utils.USE_BUTTON:
|
||||
yield self._handle_input_iface(io.BUTTON, self.layout.button_event)
|
||||
if utils.USE_TOUCH:
|
||||
yield self._handle_input_iface(io.TOUCH, self.layout.touch_event)
|
||||
|
||||
def _handle_input_iface(
|
||||
self, iface: int, event_call: Callable[..., LayoutState | None]
|
||||
) -> Generator:
|
||||
"""Task that is waiting for the user input."""
|
||||
touch = loop.wait(iface)
|
||||
try:
|
||||
while True:
|
||||
# Using `yield` instead of `await` to avoid allocations.
|
||||
event = yield touch
|
||||
workflow.idle_timer.touch()
|
||||
self._event(event_call, *event)
|
||||
except Shutdown:
|
||||
return
|
||||
finally:
|
||||
touch.close()
|
||||
|
||||
async def _handle_usb_iface(self) -> None:
|
||||
if self.context is None:
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
br_code, br_name = await loop.race(
|
||||
self.context.read(()),
|
||||
self.button_request_box,
|
||||
)
|
||||
|
||||
await self.context.call(
|
||||
ButtonRequest(
|
||||
code=br_code, pages=self.layout.page_count(), name=br_name
|
||||
),
|
||||
ButtonAck,
|
||||
)
|
||||
except Exception:
|
||||
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__()
|
||||
|
||||
|
||||
def wait_until_layout_is_running() -> Awaitable[None]: # type: ignore [awaitable-return-type]
|
||||
while not layout_chan.takers:
|
||||
yield # type: ignore [awaitable-return-type]
|
||||
class ProgressLayout:
|
||||
"""Progress layout.
|
||||
|
||||
Simplified version of the general Layout object, for the purpose of showing spinners
|
||||
and loaders that are shown "in the background" of a running workflow. Does not run
|
||||
background tasks, does not respond to timers.
|
||||
|
||||
Participates in global layout management. This is to track whether the progress bar
|
||||
is currently displayed, who needs to redraw and when.
|
||||
"""
|
||||
|
||||
def __init__(self, layout: LayoutObj[UiResult]) -> None:
|
||||
self.layout = layout
|
||||
self.transition_out = None
|
||||
|
||||
def is_layout_attached(self) -> bool:
|
||||
return True
|
||||
|
||||
def report(self, value: int, description: str | None = None) -> None:
|
||||
"""Report a progress step.
|
||||
|
||||
Starts the layout if it is not running.
|
||||
|
||||
`value` can be in range from 0 to 1000.
|
||||
"""
|
||||
if CURRENT_LAYOUT is not self:
|
||||
self.start()
|
||||
|
||||
if utils.DISABLE_ANIMATION:
|
||||
return
|
||||
|
||||
msg = self.layout.progress_event(value, description or "")
|
||||
assert msg is None
|
||||
if self.layout.paint():
|
||||
refresh()
|
||||
|
||||
def start(self) -> None:
|
||||
global CURRENT_LAYOUT
|
||||
|
||||
if CURRENT_LAYOUT is not self and CURRENT_LAYOUT is not None:
|
||||
CURRENT_LAYOUT.stop()
|
||||
|
||||
assert CURRENT_LAYOUT is None
|
||||
set_current_layout(self)
|
||||
|
||||
self.layout.request_complete_repaint()
|
||||
painted = self.layout.paint()
|
||||
backlight_fade(BacklightLevels.NORMAL)
|
||||
if painted:
|
||||
refresh()
|
||||
|
||||
def stop(self) -> None:
|
||||
global CURRENT_LAYOUT
|
||||
|
||||
if CURRENT_LAYOUT is self:
|
||||
set_current_layout(None)
|
||||
|
@ -1,42 +1,98 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor import workflow
|
||||
import trezorui2
|
||||
from trezor import ui, utils, workflow
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.messages import ButtonAck, ButtonRequest
|
||||
from trezor.wire import context
|
||||
from trezor.wire import ActionCancelled, context
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Awaitable, Protocol, TypeVar
|
||||
from typing import Any, Awaitable, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
LayoutType = Awaitable
|
||||
PropertyType = tuple[str | None, str | bytes | None]
|
||||
ExceptionType = BaseException | type[BaseException]
|
||||
|
||||
class ProgressLayout(Protocol):
|
||||
def report(self, value: int, description: str | None = None) -> None: ...
|
||||
InfoFunc = Callable[[], Awaitable[None]]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
async def button_request(
|
||||
async def _button_request(
|
||||
br_name: str,
|
||||
code: ButtonRequestType = ButtonRequestType.Other,
|
||||
pages: int | None = None,
|
||||
pages: int = 0,
|
||||
) -> None:
|
||||
workflow.close_others()
|
||||
await context.maybe_call(
|
||||
ButtonRequest(code=code, pages=pages, name=br_name), ButtonAck
|
||||
ButtonRequest(code=code, pages=pages or None, name=br_name), ButtonAck
|
||||
)
|
||||
|
||||
|
||||
async def interact(
|
||||
layout: LayoutType[T],
|
||||
br_name: str,
|
||||
layout_obj: ui.LayoutObj[T],
|
||||
br_name: str | None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||
raise_on_cancel: ExceptionType | None = ActionCancelled,
|
||||
) -> T:
|
||||
pages = None
|
||||
if hasattr(layout, "page_count") and layout.page_count() > 1: # type: ignore [Cannot access attribute "page_count" for class "LayoutType"]
|
||||
# We know for certain how many pages the layout will have
|
||||
pages = layout.page_count() # type: ignore [Cannot access attribute "page_count" for class "LayoutType"]
|
||||
await button_request(br_name, br_code, pages)
|
||||
return await layout
|
||||
# shut down other workflows to prevent them from interfering with the current one
|
||||
workflow.close_others()
|
||||
# start the layout
|
||||
layout = ui.Layout(layout_obj)
|
||||
layout.start()
|
||||
# send the button request
|
||||
if br_name is not None:
|
||||
await _button_request(br_name, br_code, layout_obj.page_count())
|
||||
# wait for the layout 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
|
||||
return result
|
||||
|
||||
|
||||
def raise_if_not_confirmed(
|
||||
layout_obj: ui.LayoutObj[ui.UiResult],
|
||||
br_name: str | None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||
exc: ExceptionType = ActionCancelled,
|
||||
) -> Awaitable[None]:
|
||||
action = interact(layout_obj, br_name, br_code, exc)
|
||||
return action # type: ignore ["UiResult" is incompatible with "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)
|
||||
|
@ -1,8 +1,132 @@
|
||||
from trezor import utils
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if utils.UI_LAYOUT == "TT":
|
||||
from .tt.homescreen import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.homescreen import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "MERCURY":
|
||||
from .mercury.homescreen import * # noqa: F401,F403
|
||||
import storage.cache as storage_cache
|
||||
import trezorui2
|
||||
from trezor import TR, ui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Iterator
|
||||
|
||||
from trezor import loop
|
||||
|
||||
|
||||
class HomescreenBase(ui.Layout):
|
||||
RENDER_INDICATOR: object | None = None
|
||||
|
||||
def __init__(self, layout: Any) -> None:
|
||||
super().__init__(layout=layout)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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:
|
||||
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:
|
||||
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._event(self.layout.usb_event, is_connected)
|
||||
|
||||
def create_tasks(self) -> Iterator[loop.Task]:
|
||||
yield from super().create_tasks()
|
||||
yield 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 get_result(self) -> Any:
|
||||
result = await super().get_result()
|
||||
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:
|
||||
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 get_result(self) -> Any:
|
||||
from apps.base import set_homescreen
|
||||
|
||||
# Handle timeout.
|
||||
result = await super().get_result()
|
||||
assert result == trezorui2.CANCELLED
|
||||
storage_cache.delete(storage_cache.APP_COMMON_BUSY_DEADLINE_MS)
|
||||
set_homescreen()
|
||||
return result
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,54 +1,10 @@
|
||||
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
|
||||
|
||||
# needed solely for test_emu_u2f
|
||||
class _RustFidoLayoutImpl(RustLayout):
|
||||
def create_tasks(self) -> tuple[AwaitableTask, ...]:
|
||||
return (
|
||||
self.handle_input_and_rendering(),
|
||||
self.handle_timers(),
|
||||
self.handle_swipe(),
|
||||
self.handle_click_signal(),
|
||||
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, 120, 160),
|
||||
(io.TOUCH_MOVE, 120, 130),
|
||||
(io.TOUCH_END, 120, 100),
|
||||
(io.TOUCH_START, 120, 120),
|
||||
(io.TOUCH_END, 120, 120),
|
||||
):
|
||||
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(
|
||||
@ -58,16 +14,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):
|
||||
@ -82,7 +52,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,
|
||||
@ -91,4 +61,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,11 +1,11 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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
|
||||
@ -16,30 +16,36 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||
selector = RustLayout(trezorui2.select_word_count(recovery_type=recovery_type))
|
||||
count = await interact(selector, "word_count", ButtonRequestType.MnemonicWordCount)
|
||||
selector = trezorui2.select_word_count(recovery_type=recovery_type)
|
||||
count = await interact(
|
||||
selector, "recovery_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
|
||||
|
||||
|
||||
@ -78,20 +84,16 @@ def format_remaining_shares_info(
|
||||
|
||||
async def show_group_share_success(share_index: int, group_index: int) -> None:
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
)
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
|
||||
|
||||
@ -103,9 +105,7 @@ async def continue_recovery(
|
||||
show_info: bool = False,
|
||||
remaining_shares_info: "RemainingSharesInfo | None" = None,
|
||||
) -> bool:
|
||||
# NOTE: show_info can be understood as first screen before any shares
|
||||
# NOTE: button request sent from the flow
|
||||
result = await RustLayout(
|
||||
result = await interact(
|
||||
trezorui2.flow_continue_recovery(
|
||||
first_screen=show_info,
|
||||
recovery_type=recovery_type,
|
||||
@ -116,7 +116,10 @@ async def continue_recovery(
|
||||
if remaining_shares_info
|
||||
else None
|
||||
),
|
||||
)
|
||||
),
|
||||
None,
|
||||
ButtonRequestType.Other,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
return result is CONFIRMED
|
||||
|
||||
@ -130,17 +133,13 @@ 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="",
|
||||
danger=True,
|
||||
)
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
trezorui2.show_warning(
|
||||
title=content or TR.words__warning,
|
||||
value=subheader or "",
|
||||
button=button,
|
||||
description="",
|
||||
danger=True,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
|
@ -1,27 +1,21 @@
|
||||
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
|
||||
if share_index is None:
|
||||
subtitle = ""
|
||||
@ -43,7 +37,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,
|
||||
@ -51,12 +45,10 @@ async def show_share_words(
|
||||
description=description,
|
||||
text_info=text_info,
|
||||
text_confirm=text_confirm,
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if result != CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def select_word(
|
||||
words: Sequence[str],
|
||||
@ -81,14 +73,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
|
||||
@ -104,13 +97,11 @@ async def slip39_show_checklist(
|
||||
) -> None:
|
||||
items = _slip_39_checklist_items(step, advanced, count, threshold)
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_checklist(
|
||||
title=TR.reset__title_shamir_backup,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
)
|
||||
trezorui2.show_checklist(
|
||||
title=TR.reset__title_shamir_backup,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
),
|
||||
"slip39_checklist",
|
||||
ButtonRequestType.ResetDevice,
|
||||
@ -162,8 +153,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 +163,8 @@ async def _prompt_number(
|
||||
info=info,
|
||||
br_code=ButtonRequestType.ResetDevice,
|
||||
br_name=br_name,
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if __debug__ and result is CONFIRMED:
|
||||
@ -188,9 +179,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
|
||||
@ -212,7 +203,7 @@ async def slip39_prompt_threshold(
|
||||
)
|
||||
)
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_set_threshold,
|
||||
description,
|
||||
info,
|
||||
@ -298,63 +289,53 @@ async def show_intro_backup(single_share: bool, num_of_words: int | None) -> Non
|
||||
description = TR.backup__info_multi_share_backup
|
||||
|
||||
await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_info(
|
||||
title=TR.backup__title_create_wallet_backup, description=description
|
||||
)
|
||||
trezorui2.show_info(
|
||||
title=TR.backup__title_create_wallet_backup, description=description
|
||||
),
|
||||
"backup_intro",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
|
||||
def show_warning_backup() -> Awaitable[ui.UiResult]:
|
||||
return interact(
|
||||
trezorui2.show_warning(
|
||||
title=TR.words__important,
|
||||
value=TR.reset__never_make_digital_copy,
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
danger=False, # Use a less severe icon color
|
||||
),
|
||||
"backup_warning",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
|
||||
async def show_warning_backup() -> None:
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_warning(
|
||||
title=TR.words__important,
|
||||
value=TR.reset__never_make_digital_copy,
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
danger=False, # Use a less severe icon color
|
||||
)
|
||||
),
|
||||
"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 raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_warning(
|
||||
title=subheader or "",
|
||||
description=content,
|
||||
value="",
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
danger=True,
|
||||
)
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
) -> Awaitable[None]:
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.show_warning(
|
||||
title=subheader or "",
|
||||
description=content,
|
||||
value="",
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
danger=True,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
|
||||
|
||||
@ -391,11 +372,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,
|
||||
|
@ -1,13 +1,6 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import trezorui2
|
||||
from trezor import TR, config, ui, utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from .common import ProgressLayout
|
||||
|
||||
|
||||
def _storage_message_to_str(message: config.StorageMessage | None) -> str | None:
|
||||
from trezor import TR
|
||||
@ -28,37 +21,15 @@ def _storage_message_to_str(message: config.StorageMessage | None) -> str | None
|
||||
raise RuntimeError # unknown message
|
||||
|
||||
|
||||
class RustProgress:
|
||||
def __init__(
|
||||
self,
|
||||
layout: Any,
|
||||
):
|
||||
self.layout = layout
|
||||
ui.backlight_fade(ui.BacklightLevels.DIM)
|
||||
self.layout.attach_timer_fn(self.set_timer, None)
|
||||
if self.layout.paint():
|
||||
ui.refresh()
|
||||
ui.backlight_fade(ui.BacklightLevels.NORMAL)
|
||||
|
||||
def set_timer(self, token: int, duration_ms: int) -> None:
|
||||
raise RuntimeError # progress layouts should not set timers
|
||||
|
||||
def report(self, value: int, description: str | None = None):
|
||||
msg = self.layout.progress_event(value, description or "")
|
||||
assert msg is None
|
||||
if self.layout.paint():
|
||||
ui.refresh()
|
||||
|
||||
|
||||
def progress(
|
||||
description: str | None = None,
|
||||
title: str | None = None,
|
||||
indeterminate: bool = False,
|
||||
) -> ProgressLayout:
|
||||
) -> ui.ProgressLayout:
|
||||
if description is None:
|
||||
description = TR.progress__please_wait # def_arg
|
||||
|
||||
return RustProgress(
|
||||
return ui.ProgressLayout(
|
||||
layout=trezorui2.show_progress(
|
||||
description=description,
|
||||
title=title,
|
||||
@ -67,27 +38,27 @@ def progress(
|
||||
)
|
||||
|
||||
|
||||
def bitcoin_progress(message: str) -> ProgressLayout:
|
||||
def bitcoin_progress(message: str) -> ui.ProgressLayout:
|
||||
return progress(message)
|
||||
|
||||
|
||||
def coinjoin_progress(message: str) -> ProgressLayout:
|
||||
return RustProgress(
|
||||
def coinjoin_progress(message: str) -> ui.ProgressLayout:
|
||||
return ui.ProgressLayout(
|
||||
layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False)
|
||||
)
|
||||
|
||||
|
||||
def pin_progress(title: config.StorageMessage, description: str) -> ProgressLayout:
|
||||
def pin_progress(title: config.StorageMessage, description: str) -> ui.ProgressLayout:
|
||||
return progress(description=description, title=_storage_message_to_str(title))
|
||||
|
||||
|
||||
if not utils.BITCOIN_ONLY:
|
||||
|
||||
def monero_keyimage_sync_progress() -> ProgressLayout:
|
||||
def monero_keyimage_sync_progress() -> ui.ProgressLayout:
|
||||
return progress(TR.progress__syncing)
|
||||
|
||||
def monero_live_refresh_progress() -> ProgressLayout:
|
||||
def monero_live_refresh_progress() -> ui.ProgressLayout:
|
||||
return progress(TR.progress__refreshing, indeterminate=True)
|
||||
|
||||
def monero_transaction_progress_inner() -> ProgressLayout:
|
||||
def monero_transaction_progress_inner() -> ui.ProgressLayout:
|
||||
return progress(TR.progress__signing_transaction)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
||||
import trezorui2
|
||||
from trezor import ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout
|
||||
|
||||
|
||||
async def confirm_fido(
|
||||
@ -12,17 +12,13 @@ async def confirm_fido(
|
||||
accounts: list[str | None],
|
||||
) -> int:
|
||||
"""Webauthn confirmation for one or more credentials."""
|
||||
confirm = RustLayout(
|
||||
trezorui2.confirm_fido( # type: ignore [Argument missing for parameter "icon_name"]
|
||||
title=header,
|
||||
app_name=app_name,
|
||||
accounts=accounts,
|
||||
)
|
||||
confirm = trezorui2.confirm_fido( # type: ignore [Argument missing for parameter "icon_name"]
|
||||
title=header,
|
||||
app_name=app_name,
|
||||
accounts=accounts,
|
||||
)
|
||||
result = await interact(confirm, "confirm_fido", ButtonRequestType.Other)
|
||||
|
||||
# The Rust side returns either an int or `CANCELLED`. We detect the int situation
|
||||
# and assume cancellation otherwise.
|
||||
if isinstance(result, int):
|
||||
return result
|
||||
|
||||
@ -31,22 +27,17 @@ async def confirm_fido(
|
||||
if __debug__ and result is trezorui2.CONFIRMED:
|
||||
return 0
|
||||
|
||||
# Late import won't get executed on the happy path.
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
raise ActionCancelled
|
||||
raise RuntimeError # should not get here, cancellation is handled by `interact`
|
||||
|
||||
|
||||
async def confirm_fido_reset() -> bool:
|
||||
from trezor import TR
|
||||
|
||||
confirm = RustLayout(
|
||||
trezorui2.confirm_action(
|
||||
title=TR.fido__title_reset,
|
||||
description=TR.fido__wanna_erase_credentials,
|
||||
action=None,
|
||||
verb_cancel="",
|
||||
verb=TR.buttons__confirm,
|
||||
)
|
||||
confirm = trezorui2.confirm_action(
|
||||
title=TR.fido__title_reset,
|
||||
description=TR.fido__wanna_erase_credentials,
|
||||
action=None,
|
||||
verb_cancel="",
|
||||
verb=TR.buttons__confirm,
|
||||
)
|
||||
return (await confirm) is trezorui2.CONFIRMED
|
||||
return (await ui.Layout(confirm).get_result()) is trezorui2.CONFIRMED
|
||||
|
@ -1,128 +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 _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()
|
||||
|
||||
|
||||
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_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
|
||||
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,20 +1,22 @@
|
||||
from typing import TYPE_CHECKING, Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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, show_warning
|
||||
from . import show_warning
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Awaitable, Iterable
|
||||
|
||||
from apps.management.recovery_device.layout import RemainingSharesInfo
|
||||
|
||||
|
||||
async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||
count = await interact(
|
||||
RustLayout(trezorui2.select_word_count(recovery_type=recovery_type)),
|
||||
"word_count",
|
||||
trezorui2.select_word_count(recovery_type=recovery_type),
|
||||
"recovery_word_count",
|
||||
ButtonRequestType.MnemonicWordCount,
|
||||
)
|
||||
# It can be returning a string (for example for __debug__ in tests)
|
||||
@ -22,26 +24,31 @@ async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||
|
||||
|
||||
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:
|
||||
word_choice = RustLayout(
|
||||
trezorui2.request_slip39(
|
||||
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
|
||||
)
|
||||
)
|
||||
else:
|
||||
word_choice = RustLayout(
|
||||
trezorui2.request_bip39(
|
||||
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
|
||||
)
|
||||
|
||||
word: str = await word_choice
|
||||
else:
|
||||
keyboard = trezorui2.request_bip39(
|
||||
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
|
||||
)
|
||||
|
||||
word: str = await interact(
|
||||
keyboard,
|
||||
"mnemonic" if send_button_request else None,
|
||||
ButtonRequestType.MnemonicInput,
|
||||
)
|
||||
return word
|
||||
|
||||
|
||||
@ -53,22 +60,20 @@ async def show_remaining_shares(
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
async def show_group_share_success(share_index: int, group_index: int) -> None:
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
)
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
def show_group_share_success(
|
||||
share_index: int, group_index: int
|
||||
) -> Awaitable[ui.UiResult]:
|
||||
return interact(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
|
||||
|
||||
@ -117,26 +122,24 @@ async def continue_recovery(
|
||||
if subtext:
|
||||
text += f"\n\n{subtext}"
|
||||
|
||||
homepage = trezorui2.confirm_recovery(
|
||||
title="",
|
||||
description=text,
|
||||
button=button_label,
|
||||
recovery_type=recovery_type,
|
||||
info_button=False,
|
||||
show_info=show_info, # type: ignore [No parameter named "show_info"]
|
||||
)
|
||||
while True:
|
||||
homepage = RustLayout(
|
||||
trezorui2.confirm_recovery(
|
||||
title="",
|
||||
description=text,
|
||||
button=button_label,
|
||||
recovery_type=recovery_type,
|
||||
info_button=False,
|
||||
show_info=show_info, # type: ignore [No parameter named "show_info"]
|
||||
)
|
||||
)
|
||||
result = await interact(
|
||||
homepage,
|
||||
"recovery",
|
||||
ButtonRequestType.RecoveryHomepage,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
if result is trezorui2.CONFIRMED:
|
||||
return True
|
||||
|
||||
# user has chosen to abort, confirm the choice
|
||||
try:
|
||||
await _confirm_abort(recovery_type != RecoveryType.NormalRecovery)
|
||||
except ActionCancelled:
|
||||
@ -145,12 +148,12 @@ async def continue_recovery(
|
||||
return False
|
||||
|
||||
|
||||
async def show_recovery_warning(
|
||||
def show_recovery_warning(
|
||||
br_name: str,
|
||||
content: str,
|
||||
subheader: str | None = None,
|
||||
button: str | None = None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||||
) -> None:
|
||||
) -> Awaitable[ui.UiResult]:
|
||||
button = button or TR.buttons__try_again # def_arg
|
||||
await show_warning(br_name, content, subheader, button, br_code=br_code)
|
||||
return show_warning(br_name, content, subheader, button, br_code=br_code)
|
||||
|
@ -1,12 +1,11 @@
|
||||
from typing import Sequence
|
||||
from typing import Awaitable, Sequence
|
||||
|
||||
import trezorui2
|
||||
from trezor import TR
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, confirm_action, show_success, show_warning
|
||||
from ..common import interact, raise_if_not_confirmed
|
||||
from . import confirm_action, show_success, show_warning
|
||||
|
||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||
|
||||
@ -45,13 +44,12 @@ async def show_share_words(
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_share_words( # type: ignore [Arguments missing for parameters]
|
||||
share_words=share_words, # type: ignore [No parameter named "share_words"]
|
||||
)
|
||||
trezorui2.show_share_words( # type: ignore [Arguments missing for parameters]
|
||||
share_words=share_words, # type: ignore [No parameter named "share_words"]
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
if result is CONFIRMED:
|
||||
break
|
||||
@ -83,12 +81,13 @@ async def select_word(
|
||||
words.append(words[-1])
|
||||
|
||||
word_ordinal = format_ordinal(checked_index + 1)
|
||||
result = await RustLayout(
|
||||
result = await interact(
|
||||
trezorui2.select_word(
|
||||
title="",
|
||||
description=TR.reset__select_word_template.format(word_ordinal),
|
||||
words=(words[0].lower(), words[1].lower(), words[2].lower()),
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if __debug__ and isinstance(result, str):
|
||||
return result
|
||||
@ -96,12 +95,12 @@ async def select_word(
|
||||
return words[result]
|
||||
|
||||
|
||||
async def slip39_show_checklist(
|
||||
def slip39_show_checklist(
|
||||
step: int,
|
||||
advanced: bool,
|
||||
count: int | None = None,
|
||||
threshold: int | None = None,
|
||||
) -> None:
|
||||
) -> Awaitable[None]:
|
||||
items = (
|
||||
(
|
||||
TR.reset__slip39_checklist_num_shares,
|
||||
@ -116,20 +115,16 @@ async def slip39_show_checklist(
|
||||
)
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_checklist(
|
||||
title=TR.reset__slip39_checklist_title,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
)
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.show_checklist(
|
||||
title=TR.reset__slip39_checklist_title,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
),
|
||||
"slip39_checklist",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
if result is not CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def _prompt_number(
|
||||
@ -139,13 +134,11 @@ async def _prompt_number(
|
||||
max_count: int,
|
||||
br_name: str,
|
||||
) -> int:
|
||||
num_input = RustLayout(
|
||||
trezorui2.request_number(
|
||||
title=title,
|
||||
count=count,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
)
|
||||
num_input = trezorui2.request_number(
|
||||
title=title,
|
||||
count=count,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
@ -225,12 +218,12 @@ async def slip39_prompt_number_of_shares(
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_number_of_groups() -> int:
|
||||
def slip39_advanced_prompt_number_of_groups() -> Awaitable[int]:
|
||||
count = 5
|
||||
min_count = 2
|
||||
max_count = 16
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_number_of_groups,
|
||||
count,
|
||||
min_count,
|
||||
@ -239,12 +232,12 @@ async def slip39_advanced_prompt_number_of_groups() -> int:
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
|
||||
def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> Awaitable[int]:
|
||||
count = num_of_groups // 2 + 1
|
||||
min_count = 1
|
||||
max_count = num_of_groups
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_group_threshold,
|
||||
count,
|
||||
min_count,
|
||||
@ -253,15 +246,15 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
|
||||
)
|
||||
|
||||
|
||||
async def show_intro_backup(single_share: bool, num_of_words: int | None) -> None:
|
||||
def show_intro_backup(single_share: bool, num_of_words: int | None) -> Awaitable[None]:
|
||||
if single_share:
|
||||
assert num_of_words is not None
|
||||
description = TR.backup__info_single_share_backup.format(num_of_words)
|
||||
else:
|
||||
description = TR.backup__info_multi_share_backup
|
||||
|
||||
await confirm_action(
|
||||
"backup_warning",
|
||||
return confirm_action(
|
||||
"backup_intro",
|
||||
title=TR.backup__title_backup_wallet,
|
||||
verb=TR.buttons__continue,
|
||||
description=description,
|
||||
@ -270,8 +263,8 @@ async def show_intro_backup(single_share: bool, num_of_words: int | None) -> Non
|
||||
)
|
||||
|
||||
|
||||
async def show_warning_backup() -> None:
|
||||
await show_warning(
|
||||
def show_warning_backup() -> Awaitable[trezorui2.UiResult]:
|
||||
return show_warning(
|
||||
"backup_warning",
|
||||
TR.words__title_remember,
|
||||
TR.reset__never_make_digital_copy,
|
||||
@ -280,8 +273,8 @@ async def show_warning_backup() -> None:
|
||||
)
|
||||
|
||||
|
||||
async def show_success_backup() -> None:
|
||||
await confirm_action(
|
||||
def show_success_backup() -> Awaitable[None]:
|
||||
return confirm_action(
|
||||
"success_backup",
|
||||
TR.reset__title_backup_is_done,
|
||||
description=TR.words__keep_it_safe,
|
||||
@ -291,16 +284,16 @@ async def show_success_backup() -> None:
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
) -> Awaitable[trezorui2.UiResult]:
|
||||
button = button or TR.buttons__try_again # def_arg
|
||||
|
||||
await show_warning(
|
||||
return show_warning(
|
||||
br_name,
|
||||
subheader or "",
|
||||
content,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +1,8 @@
|
||||
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(
|
||||
@ -54,16 +12,31 @@ 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
|
||||
|
||||
confirm.touch_event(io.TOUCH_START, 220, 220)
|
||||
if confirm.paint():
|
||||
ui.refresh()
|
||||
msg = confirm.touch_event(io.TOUCH_END, 220, 220)
|
||||
if confirm.paint():
|
||||
ui.refresh()
|
||||
assert msg is trezorui2.LayoutState.DONE
|
||||
retval = confirm.return_value()
|
||||
assert isinstance(retval, int)
|
||||
return retval
|
||||
|
||||
# The Rust side returns either an int or `CANCELLED`. We detect the int situation
|
||||
# and assume cancellation otherwise.
|
||||
if isinstance(result, int):
|
||||
@ -78,7 +51,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,
|
||||
@ -86,4 +59,4 @@ async def confirm_fido_reset() -> bool:
|
||||
reverse=True,
|
||||
)
|
||||
)
|
||||
return (await confirm) is trezorui2.CONFIRMED
|
||||
return (await confirm.get_result()) is trezorui2.CONFIRMED
|
||||
|
@ -1,143 +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 _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,66 +1,60 @@
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import trezorui2
|
||||
from trezor import TR
|
||||
from trezor.enums import ButtonRequestType, RecoveryType
|
||||
from trezor import TR, ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, raise_if_not_confirmed
|
||||
|
||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||
INFO = trezorui2.INFO # global_import_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Awaitable
|
||||
|
||||
from trezor.enums import RecoveryType
|
||||
|
||||
from apps.management.recovery_device.layout import RemainingSharesInfo
|
||||
|
||||
|
||||
async def _homepage_with_info(
|
||||
dialog: RustLayout,
|
||||
info_func: Callable,
|
||||
) -> trezorui2.UiResult:
|
||||
while True:
|
||||
result = await dialog
|
||||
|
||||
if result is INFO:
|
||||
await info_func()
|
||||
dialog.request_complete_repaint()
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||
selector = RustLayout(trezorui2.select_word_count(recovery_type=recovery_type))
|
||||
count = await interact(selector, "word_count", ButtonRequestType.MnemonicWordCount)
|
||||
count = await interact(
|
||||
trezorui2.select_word_count(recovery_type=recovery_type),
|
||||
"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__type_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
|
||||
)
|
||||
)
|
||||
else:
|
||||
keyboard = RustLayout(
|
||||
trezorui2.request_bip39(
|
||||
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
|
||||
)
|
||||
|
||||
word: str = await keyboard
|
||||
else:
|
||||
keyboard = trezorui2.request_bip39(
|
||||
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
|
||||
)
|
||||
|
||||
word: str = await interact(
|
||||
keyboard,
|
||||
"mnemonic" if send_button_request else None,
|
||||
ButtonRequestType.MnemonicInput,
|
||||
)
|
||||
return word
|
||||
|
||||
|
||||
async def show_remaining_shares(
|
||||
def show_remaining_shares(
|
||||
groups: set[tuple[str, ...]],
|
||||
shares_remaining: list[int],
|
||||
group_threshold: int,
|
||||
) -> None:
|
||||
) -> Awaitable[trezorui2.UiResult]:
|
||||
from trezor import strings
|
||||
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
||||
|
||||
@ -86,31 +80,27 @@ async def show_remaining_shares(
|
||||
words = "\n".join(group)
|
||||
pages.append((title, words))
|
||||
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(trezorui2.show_remaining_shares(pages=pages)),
|
||||
"show_shares",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
return interact(
|
||||
trezorui2.show_remaining_shares(pages=pages),
|
||||
"show_shares",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
|
||||
|
||||
async def show_group_share_success(share_index: int, group_index: int) -> None:
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
)
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
def show_group_share_success(
|
||||
share_index: int, group_index: int
|
||||
) -> Awaitable[ui.UiResult]:
|
||||
return interact(
|
||||
trezorui2.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
|
||||
|
||||
@ -146,70 +136,60 @@ async def continue_recovery(
|
||||
show_info: bool = False,
|
||||
remaining_shares_info: "RemainingSharesInfo | None" = None,
|
||||
) -> bool:
|
||||
from trezor.enums import RecoveryType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import button_request
|
||||
|
||||
if show_info:
|
||||
if show_instructions:
|
||||
# Show this just one-time
|
||||
description = TR.recovery__enter_each_word
|
||||
else:
|
||||
description = subtext or ""
|
||||
|
||||
homepage = trezorui2.confirm_recovery(
|
||||
title=text,
|
||||
description=description,
|
||||
button=button_label,
|
||||
recovery_type=recovery_type,
|
||||
info_button=remaining_shares_info is not None,
|
||||
)
|
||||
|
||||
while True:
|
||||
homepage = RustLayout(
|
||||
trezorui2.confirm_recovery(
|
||||
title=text,
|
||||
description=description,
|
||||
button=button_label,
|
||||
recovery_type=recovery_type,
|
||||
info_button=remaining_shares_info is not None,
|
||||
)
|
||||
result = await interact(
|
||||
homepage,
|
||||
"recovery",
|
||||
ButtonRequestType.RecoveryHomepage,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
|
||||
await button_request("recovery", ButtonRequestType.RecoveryHomepage)
|
||||
|
||||
if remaining_shares_info is None:
|
||||
result = await homepage
|
||||
else:
|
||||
groups, shares_remaining, group_threshold = remaining_shares_info
|
||||
result = await _homepage_with_info(
|
||||
homepage,
|
||||
lambda: show_remaining_shares(
|
||||
groups, shares_remaining, group_threshold
|
||||
),
|
||||
)
|
||||
|
||||
if result is CONFIRMED:
|
||||
if result is trezorui2.CONFIRMED:
|
||||
return True
|
||||
|
||||
try:
|
||||
await _confirm_abort(recovery_type != RecoveryType.NormalRecovery)
|
||||
except ActionCancelled:
|
||||
pass
|
||||
elif result is trezorui2.INFO and remaining_shares_info is not None:
|
||||
await show_remaining_shares(*remaining_shares_info)
|
||||
else:
|
||||
return False
|
||||
try:
|
||||
await _confirm_abort(recovery_type != RecoveryType.NormalRecovery)
|
||||
except ActionCancelled:
|
||||
pass
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
async def show_recovery_warning(
|
||||
def show_recovery_warning(
|
||||
br_name: str,
|
||||
content: str,
|
||||
subheader: str | None = None,
|
||||
button: str | None = None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||||
) -> None:
|
||||
) -> Awaitable[ui.UiResult]:
|
||||
button = button or TR.buttons__try_again # def_arg
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_warning(
|
||||
title=content,
|
||||
description=subheader or "",
|
||||
button=button,
|
||||
allow_cancel=False,
|
||||
)
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
|
||||
return interact(
|
||||
trezorui2.show_warning(
|
||||
title=content,
|
||||
description=subheader or "",
|
||||
button=button,
|
||||
allow_cancel=False,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
|
@ -1,12 +1,10 @@
|
||||
from typing import Callable, Sequence
|
||||
from typing import Awaitable, Callable, Sequence
|
||||
|
||||
import trezorui2
|
||||
from trezor import TR
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import interact
|
||||
from . import RustLayout, raise_if_not_confirmed, show_success
|
||||
from ..common import interact, raise_if_not_confirmed
|
||||
|
||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||
|
||||
@ -35,11 +33,11 @@ def _split_share_into_pages(share_words: Sequence[str], per_page: int = 4) -> li
|
||||
return pages
|
||||
|
||||
|
||||
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]:
|
||||
if share_index is None:
|
||||
title = TR.reset__recovery_wallet_backup_title
|
||||
elif group_index is None:
|
||||
@ -51,18 +49,14 @@ async def show_share_words(
|
||||
|
||||
pages = _split_share_into_pages(share_words)
|
||||
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_share_words(
|
||||
title=title,
|
||||
pages=pages,
|
||||
),
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.show_share_words(
|
||||
title=title,
|
||||
pages=pages,
|
||||
),
|
||||
"backup_words",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
if result != CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def select_word(
|
||||
@ -88,14 +82,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
|
||||
@ -103,12 +98,12 @@ async def select_word(
|
||||
return words[result]
|
||||
|
||||
|
||||
async def slip39_show_checklist(
|
||||
def slip39_show_checklist(
|
||||
step: int,
|
||||
advanced: bool,
|
||||
count: int | None = None,
|
||||
threshold: int | None = None,
|
||||
) -> None:
|
||||
) -> Awaitable[None]:
|
||||
items = (
|
||||
(
|
||||
TR.reset__slip39_checklist_set_num_shares,
|
||||
@ -123,20 +118,16 @@ async def slip39_show_checklist(
|
||||
)
|
||||
)
|
||||
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_checklist(
|
||||
title=TR.reset__slip39_checklist_title,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
)
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.show_checklist(
|
||||
title=TR.reset__slip39_checklist_title,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
),
|
||||
"slip39_checklist",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
if result != CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def _prompt_number(
|
||||
@ -148,14 +139,12 @@ async def _prompt_number(
|
||||
max_count: int,
|
||||
br_name: str,
|
||||
) -> int:
|
||||
num_input = RustLayout(
|
||||
trezorui2.request_number(
|
||||
title=title,
|
||||
description=description,
|
||||
count=count,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
)
|
||||
num_input = trezorui2.request_number(
|
||||
title=title,
|
||||
description=description,
|
||||
count=count,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
)
|
||||
|
||||
while True:
|
||||
@ -163,31 +152,33 @@ async def _prompt_number(
|
||||
num_input,
|
||||
br_name,
|
||||
ButtonRequestType.ResetDevice,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
if __debug__:
|
||||
if not isinstance(result, tuple):
|
||||
# DebugLink currently can't send number of shares and it doesn't
|
||||
# change the counter either so just use the initial value.
|
||||
result = (result, count)
|
||||
result = result, count
|
||||
status, value = result
|
||||
|
||||
if status == CONFIRMED:
|
||||
assert isinstance(value, int)
|
||||
return value
|
||||
|
||||
await RustLayout(
|
||||
await interact(
|
||||
trezorui2.show_simple(
|
||||
title=None,
|
||||
description=info(value),
|
||||
button=TR.buttons__ok_i_understand,
|
||||
)
|
||||
),
|
||||
None,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
num_input.request_complete_repaint()
|
||||
|
||||
|
||||
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
|
||||
@ -230,7 +221,7 @@ async def slip39_prompt_threshold(
|
||||
text += " " + TR.reset__to_form_group_template.format(group_id + 1)
|
||||
return text
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_set_threshold,
|
||||
description,
|
||||
info,
|
||||
@ -241,9 +232,9 @@ async def slip39_prompt_threshold(
|
||||
)
|
||||
|
||||
|
||||
async def slip39_prompt_number_of_shares(
|
||||
def slip39_prompt_number_of_shares(
|
||||
num_words: int, group_id: int | None = None
|
||||
) -> int:
|
||||
) -> Awaitable[int]:
|
||||
count = 5
|
||||
min_count = 1
|
||||
max_count = 16
|
||||
@ -266,7 +257,7 @@ async def slip39_prompt_number_of_shares(
|
||||
num_words, group_id + 1
|
||||
)
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_set_number_of_shares,
|
||||
description,
|
||||
lambda i: info,
|
||||
@ -277,14 +268,14 @@ async def slip39_prompt_number_of_shares(
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_number_of_groups() -> int:
|
||||
def slip39_advanced_prompt_number_of_groups() -> Awaitable[int]:
|
||||
count = 5
|
||||
min_count = 2
|
||||
max_count = 16
|
||||
description = TR.reset__group_description
|
||||
info = TR.reset__group_info
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_set_number_of_groups,
|
||||
lambda i: description,
|
||||
lambda i: info,
|
||||
@ -295,14 +286,14 @@ async def slip39_advanced_prompt_number_of_groups() -> int:
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
|
||||
def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> Awaitable[int]:
|
||||
count = num_of_groups // 2 + 1
|
||||
min_count = 1
|
||||
max_count = num_of_groups
|
||||
description = TR.reset__required_number_of_groups
|
||||
info = TR.reset__advanced_group_threshold_info
|
||||
|
||||
return await _prompt_number(
|
||||
return _prompt_number(
|
||||
TR.reset__title_set_group_threshold,
|
||||
lambda i: description,
|
||||
lambda i: info,
|
||||
@ -313,44 +304,40 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
|
||||
)
|
||||
|
||||
|
||||
async def show_intro_backup(single_share: bool, num_of_words: int | None) -> None:
|
||||
def show_intro_backup(single_share: bool, num_of_words: int | None) -> Awaitable[None]:
|
||||
if single_share:
|
||||
assert num_of_words is not None
|
||||
description = TR.backup__info_single_share_backup.format(num_of_words)
|
||||
else:
|
||||
description = TR.backup__info_multi_share_backup
|
||||
|
||||
await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_info(
|
||||
title="",
|
||||
button=TR.buttons__continue,
|
||||
description=description,
|
||||
allow_cancel=False,
|
||||
)
|
||||
return raise_if_not_confirmed(
|
||||
trezorui2.show_info(
|
||||
title="",
|
||||
button=TR.buttons__continue,
|
||||
description=description,
|
||||
allow_cancel=False,
|
||||
),
|
||||
"backup_warning",
|
||||
"backup_intro",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
|
||||
async def show_warning_backup() -> None:
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.show_info(
|
||||
title=TR.reset__never_make_digital_copy,
|
||||
button=TR.buttons__ok_i_understand,
|
||||
allow_cancel=False,
|
||||
)
|
||||
def show_warning_backup() -> Awaitable[trezorui2.UiResult]:
|
||||
return interact(
|
||||
trezorui2.show_info(
|
||||
title=TR.reset__never_make_digital_copy,
|
||||
button=TR.buttons__ok_i_understand,
|
||||
allow_cancel=False,
|
||||
),
|
||||
"backup_warning",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
if result != CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def show_success_backup() -> None:
|
||||
from . import show_success
|
||||
|
||||
await show_success(
|
||||
"success_backup",
|
||||
TR.reset__use_your_backup,
|
||||
@ -358,27 +345,23 @@ async def show_success_backup() -> None:
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
) -> Awaitable[trezorui2.UiResult]:
|
||||
button = button or TR.buttons__try_again # def_arg
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.show_warning(
|
||||
title=subheader or "",
|
||||
description=content,
|
||||
button=button,
|
||||
allow_cancel=False,
|
||||
)
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
return interact(
|
||||
trezorui2.show_warning(
|
||||
title=subheader or "",
|
||||
description=content,
|
||||
button=button,
|
||||
allow_cancel=False,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
|
||||
|
||||
@ -387,6 +370,8 @@ async def show_share_confirmation_success(
|
||||
num_of_shares: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
from . import show_success
|
||||
|
||||
if share_index is None or num_of_shares is None:
|
||||
# it is a BIP39 or a 1-of-1 SLIP39 backup
|
||||
subheader = TR.reset__finished_verifying_wallet_backup
|
||||
|
@ -172,21 +172,6 @@ class Context:
|
||||
CURRENT_CONTEXT: Context | None = None
|
||||
|
||||
|
||||
def wait(task: Awaitable[T]) -> Awaitable[T]:
|
||||
"""
|
||||
Wait until the passed in task finishes, and return the result, while servicing the
|
||||
wire context.
|
||||
|
||||
Used to make sure the device is responsive on USB while waiting for user
|
||||
interaction. If a message is received before the task finishes, it raises an
|
||||
`UnexpectedMessage` exception, returning control to the session handler.
|
||||
"""
|
||||
if CURRENT_CONTEXT is None:
|
||||
return task
|
||||
else:
|
||||
return loop.race(CURRENT_CONTEXT.read(()), task)
|
||||
|
||||
|
||||
async def call(
|
||||
msg: protobuf.MessageType,
|
||||
expected_type: type[LoadedMessageType],
|
||||
|
@ -19,6 +19,7 @@ import logging
|
||||
import re
|
||||
import textwrap
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from enum import Enum, IntEnum, auto
|
||||
@ -38,19 +39,20 @@ from typing import (
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from mnemonic import Mnemonic
|
||||
from typing_extensions import Literal
|
||||
|
||||
from . import mapping, messages, models, protobuf
|
||||
from .client import TrezorClient
|
||||
from .exceptions import TrezorFailure
|
||||
from .log import DUMP_BYTES
|
||||
from .messages import DebugWaitType
|
||||
from .tools import expect
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from .messages import PinMatrixRequestType
|
||||
from .transport import Transport
|
||||
|
||||
@ -60,6 +62,15 @@ if TYPE_CHECKING:
|
||||
|
||||
AnyDict = Dict[str, Any]
|
||||
|
||||
class InputFunc(Protocol):
|
||||
def __call__(
|
||||
self,
|
||||
hold_ms: Optional[int] = None,
|
||||
wait: Optional[bool] = None,
|
||||
) -> "LayoutContent":
|
||||
...
|
||||
|
||||
|
||||
EXPECTED_RESPONSES_CONTEXT_LINES = 3
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -391,6 +402,29 @@ def multipage_content(layouts: List[LayoutContent]) -> str:
|
||||
return "".join(layout.text_content() for layout in layouts)
|
||||
|
||||
|
||||
def _make_input_func(
|
||||
button: Optional[messages.DebugButton] = None,
|
||||
physical_button: Optional[messages.DebugPhysicalButton] = None,
|
||||
swipe: Optional[messages.DebugSwipeDirection] = None,
|
||||
) -> "InputFunc":
|
||||
decision = messages.DebugLinkDecision(
|
||||
button=button,
|
||||
physical_button=physical_button,
|
||||
swipe=swipe,
|
||||
)
|
||||
|
||||
def input_func(
|
||||
self: "DebugLink",
|
||||
hold_ms: Optional[int] = None,
|
||||
wait: Optional[bool] = None,
|
||||
) -> LayoutContent:
|
||||
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
||||
decision.hold_ms = hold_ms
|
||||
return self._decision(decision, wait=wait)
|
||||
|
||||
return input_func # type: ignore [Parameter name mismatch]
|
||||
|
||||
|
||||
class DebugLink:
|
||||
def __init__(self, transport: "Transport", auto_interact: bool = True) -> None:
|
||||
self.transport = transport
|
||||
@ -405,7 +439,6 @@ class DebugLink:
|
||||
self.screenshot_recording_dir: Optional[str] = None
|
||||
|
||||
# For T1 screenshotting functionality in DebugUI
|
||||
self.t1_take_screenshots = False
|
||||
self.t1_screenshot_directory: Optional[Path] = None
|
||||
self.t1_screenshot_counter = 0
|
||||
|
||||
@ -413,6 +446,11 @@ class DebugLink:
|
||||
self.screen_text_file: Optional[Path] = None
|
||||
self.last_screen_content = ""
|
||||
|
||||
self.waiting_for_layout_change = False
|
||||
self.layout_dirty = True
|
||||
|
||||
self.input_wait_type = DebugWaitType.IMMEDIATE
|
||||
|
||||
@property
|
||||
def legacy_ui(self) -> bool:
|
||||
"""Differences between UI1 and UI2."""
|
||||
@ -439,7 +477,12 @@ class DebugLink:
|
||||
def close(self) -> None:
|
||||
self.transport.end_session()
|
||||
|
||||
def _call(self, msg: protobuf.MessageType, nowait: bool = False) -> Any:
|
||||
def _write(self, msg: protobuf.MessageType) -> None:
|
||||
if self.waiting_for_layout_change:
|
||||
raise RuntimeError(
|
||||
"Debuglink is unavailable while waiting for layout change."
|
||||
)
|
||||
|
||||
LOG.debug(
|
||||
f"sending message: {msg.__class__.__name__}",
|
||||
extra={"protobuf": msg},
|
||||
@ -450,13 +493,12 @@ class DebugLink:
|
||||
f"encoded as type {msg_type} ({len(msg_bytes)} bytes): {msg_bytes.hex()}",
|
||||
)
|
||||
self.transport.write(msg_type, msg_bytes)
|
||||
if nowait:
|
||||
return None
|
||||
|
||||
def _read(self) -> protobuf.MessageType:
|
||||
ret_type, ret_bytes = self.transport.read()
|
||||
LOG.log(
|
||||
DUMP_BYTES,
|
||||
f"received type {msg_type} ({len(msg_bytes)} bytes): {msg_bytes.hex()}",
|
||||
f"received type {ret_type} ({len(ret_bytes)} bytes): {ret_bytes.hex()}",
|
||||
)
|
||||
msg = self.mapping.decode(ret_type, ret_bytes)
|
||||
|
||||
@ -472,11 +514,20 @@ class DebugLink:
|
||||
)
|
||||
return msg
|
||||
|
||||
def state(self) -> messages.DebugLinkState:
|
||||
return self._call(messages.DebugLinkGetState())
|
||||
def _call(self, msg: protobuf.MessageType) -> Any:
|
||||
self._write(msg)
|
||||
return self._read()
|
||||
|
||||
def state(
|
||||
self, wait_type: DebugWaitType = DebugWaitType.CURRENT_LAYOUT
|
||||
) -> messages.DebugLinkState:
|
||||
result = self._call(messages.DebugLinkGetState(wait_layout=wait_type))
|
||||
if isinstance(result, messages.Failure):
|
||||
raise TrezorFailure(result)
|
||||
return result
|
||||
|
||||
def read_layout(self) -> LayoutContent:
|
||||
return LayoutContent(self.state().tokens or [])
|
||||
return LayoutContent(self.state().tokens)
|
||||
|
||||
def wait_layout(self, wait_for_external_change: bool = False) -> LayoutContent:
|
||||
# Next layout change will be caused by external event
|
||||
@ -487,11 +538,38 @@ class DebugLink:
|
||||
if wait_for_external_change:
|
||||
self.reset_debug_events()
|
||||
|
||||
obj = self._call(messages.DebugLinkGetState(wait_layout=True))
|
||||
obj = self._call(
|
||||
messages.DebugLinkGetState(wait_layout=DebugWaitType.NEXT_LAYOUT)
|
||||
)
|
||||
self.layout_dirty = True
|
||||
if isinstance(obj, messages.Failure):
|
||||
raise TrezorFailure(obj)
|
||||
return LayoutContent(obj.tokens)
|
||||
|
||||
@contextmanager
|
||||
def wait_for_layout_change(self) -> Iterator[LayoutContent]:
|
||||
# set up a dummy layout content object to be yielded
|
||||
layout_content = LayoutContent(
|
||||
["DUMMY CONTENT, WAIT UNTIL THE END OF THE BLOCK :("]
|
||||
)
|
||||
|
||||
# send GetState without waiting for reply
|
||||
self._write(messages.DebugLinkGetState(wait_layout=DebugWaitType.NEXT_LAYOUT))
|
||||
|
||||
# allow the block to proceed
|
||||
self.waiting_for_layout_change = True
|
||||
try:
|
||||
yield layout_content
|
||||
finally:
|
||||
self.waiting_for_layout_change = False
|
||||
|
||||
# wait for the reply
|
||||
resp = self._read()
|
||||
assert isinstance(resp, messages.DebugLinkState)
|
||||
|
||||
# replace contents of the yielded object with the new thing
|
||||
layout_content.__init__(resp.tokens)
|
||||
|
||||
def reset_debug_events(self) -> None:
|
||||
# Only supported on TT and above certain version
|
||||
if (self.model is not models.T1B1) and not self.legacy_debug:
|
||||
@ -535,56 +613,103 @@ class DebugLink:
|
||||
state = self._call(messages.DebugLinkGetState(wait_word_list=True))
|
||||
return state.reset_word
|
||||
|
||||
def input(
|
||||
self,
|
||||
word: Optional[str] = None,
|
||||
button: Optional[messages.DebugButton] = None,
|
||||
physical_button: Optional[messages.DebugPhysicalButton] = None,
|
||||
swipe: Optional[messages.DebugSwipeDirection] = None,
|
||||
x: Optional[int] = None,
|
||||
y: Optional[int] = None,
|
||||
wait: Optional[bool] = None,
|
||||
hold_ms: Optional[int] = None,
|
||||
) -> Optional[LayoutContent]:
|
||||
def _decision(
|
||||
self, decision: messages.DebugLinkDecision, wait: Optional[bool] = None
|
||||
) -> LayoutContent:
|
||||
"""Send a debuglink decision and returns the resulting layout.
|
||||
|
||||
If hold_ms is set, an additional 200ms is added to account for processing
|
||||
delays. (This is needed for hold-to-confirm to trigger reliably.)
|
||||
|
||||
If `wait` is unset, the following wait mode is used:
|
||||
|
||||
- `IMMEDIATE`, when in normal tests, which never deadlocks the device, but may
|
||||
return an empty layout in case the next one didn't come up immediately. (E.g.,
|
||||
in SignTx flow, the device is waiting for more TxRequest/TxAck exchanges
|
||||
before showing the next UI layout.)
|
||||
- `CURRENT_LAYOUT`, when in tests running through a `DeviceHandler`. This mode
|
||||
returns the current layout or waits for some layout to come up if there is
|
||||
none at the moment. The assumption is that wirelink is communicating on
|
||||
another thread and won't be blocked by waiting on debuglink.
|
||||
|
||||
Force waiting for the layout by setting `wait=True`. Force not waiting by
|
||||
setting `wait=False` -- useful when, e.g., you are causing the next layout to be
|
||||
deliberately delayed.
|
||||
"""
|
||||
if not self.allow_interactions:
|
||||
return None
|
||||
return self.wait_layout()
|
||||
|
||||
args = sum(a is not None for a in (word, button, physical_button, swipe, x))
|
||||
if args != 1:
|
||||
raise ValueError(
|
||||
"Invalid input - must use one of word, button, physical_button, swipe, click(x,y)"
|
||||
)
|
||||
if decision.hold_ms is not None:
|
||||
decision.hold_ms += 200
|
||||
|
||||
decision = messages.DebugLinkDecision(
|
||||
button=button,
|
||||
physical_button=physical_button,
|
||||
swipe=swipe,
|
||||
input=word,
|
||||
x=x,
|
||||
y=y,
|
||||
wait=wait,
|
||||
hold_ms=hold_ms,
|
||||
self._write(decision)
|
||||
self.layout_dirty = True
|
||||
if wait is True:
|
||||
wait_type = DebugWaitType.CURRENT_LAYOUT
|
||||
elif wait is False:
|
||||
wait_type = DebugWaitType.IMMEDIATE
|
||||
else:
|
||||
wait_type = self.input_wait_type
|
||||
return self.snapshot(wait_type)
|
||||
|
||||
press_yes = _make_input_func(button=messages.DebugButton.YES)
|
||||
"""Confirm current layout. See `_decision` for more details."""
|
||||
press_no = _make_input_func(button=messages.DebugButton.NO)
|
||||
"""Reject current layout. See `_decision` for more details."""
|
||||
press_info = _make_input_func(button=messages.DebugButton.INFO)
|
||||
"""Trigger the Info action. See `_decision` for more details."""
|
||||
swipe_up = _make_input_func(swipe=messages.DebugSwipeDirection.UP)
|
||||
"""Swipe up. See `_decision` for more details."""
|
||||
swipe_down = _make_input_func(swipe=messages.DebugSwipeDirection.DOWN)
|
||||
"""Swipe down. See `_decision` for more details."""
|
||||
swipe_right = _make_input_func(swipe=messages.DebugSwipeDirection.RIGHT)
|
||||
"""Swipe right. See `_decision` for more details."""
|
||||
swipe_left = _make_input_func(swipe=messages.DebugSwipeDirection.LEFT)
|
||||
"""Swipe left. See `_decision` for more details."""
|
||||
press_left = _make_input_func(physical_button=messages.DebugPhysicalButton.LEFT_BTN)
|
||||
"""Press left button. See `_decision` for more details."""
|
||||
press_middle = _make_input_func(
|
||||
physical_button=messages.DebugPhysicalButton.MIDDLE_BTN
|
||||
)
|
||||
"""Press middle button. See `_decision` for more details."""
|
||||
press_right = _make_input_func(
|
||||
physical_button=messages.DebugPhysicalButton.RIGHT_BTN
|
||||
)
|
||||
"""Press right button. See `_decision` for more details."""
|
||||
|
||||
def input(self, word: str, wait: Optional[bool] = None) -> LayoutContent:
|
||||
"""Send text input to the device. See `_decision` for more details."""
|
||||
return self._decision(messages.DebugLinkDecision(input=word), wait)
|
||||
|
||||
def click(
|
||||
self,
|
||||
click: Tuple[int, int],
|
||||
hold_ms: Optional[int] = None,
|
||||
wait: Optional[bool] = None,
|
||||
) -> LayoutContent:
|
||||
"""Send a click to the device. See `_decision` for more details."""
|
||||
x, y = click
|
||||
return self._decision(
|
||||
messages.DebugLinkDecision(x=x, y=y, hold_ms=hold_ms), wait
|
||||
)
|
||||
|
||||
ret = self._call(decision, nowait=not wait)
|
||||
if ret is not None:
|
||||
return LayoutContent(ret.tokens)
|
||||
def snapshot(
|
||||
self, wait_type: DebugWaitType = DebugWaitType.IMMEDIATE
|
||||
) -> LayoutContent:
|
||||
"""Save text and image content of the screen to relevant directories."""
|
||||
# take the snapshot
|
||||
state = self.state(wait_type)
|
||||
layout = LayoutContent(state.tokens)
|
||||
|
||||
# Getting the current screen after the (nowait) decision
|
||||
self.save_current_screen_if_relevant(wait=False)
|
||||
if state.tokens and self.layout_dirty:
|
||||
# save it, unless we already did or unless it's empty
|
||||
self.save_debug_screen(layout.visible_screen())
|
||||
if state.layout is not None:
|
||||
self.save_screenshot(state.layout)
|
||||
self.layout_dirty = False
|
||||
|
||||
return None
|
||||
|
||||
def save_current_screen_if_relevant(self, wait: bool = True) -> None:
|
||||
"""Optionally saving the textual screen output."""
|
||||
if self.screen_text_file is None:
|
||||
return
|
||||
|
||||
if wait:
|
||||
layout = self.wait_layout()
|
||||
else:
|
||||
layout = self.read_layout()
|
||||
self.save_debug_screen(layout.visible_screen())
|
||||
# return the layout
|
||||
return layout
|
||||
|
||||
def save_debug_screen(self, screen_content: str) -> None:
|
||||
if self.screen_text_file is None:
|
||||
@ -603,127 +728,8 @@ class DebugLink:
|
||||
f.write(screen_content)
|
||||
f.write("\n" + 80 * "/" + "\n")
|
||||
|
||||
# Type overloads below make sure that when we supply `wait=True` into functions,
|
||||
# they will always return `LayoutContent` and we do not need to assert `is not None`.
|
||||
|
||||
@overload
|
||||
def click(self, click: Tuple[int, int]) -> None: ...
|
||||
|
||||
@overload
|
||||
def click(self, click: Tuple[int, int], wait: Literal[True]) -> LayoutContent: ...
|
||||
|
||||
def click(
|
||||
self, click: Tuple[int, int], wait: bool = False
|
||||
) -> Optional[LayoutContent]:
|
||||
x, y = click
|
||||
return self.input(x=x, y=y, wait=wait)
|
||||
|
||||
# Made into separate function as `hold_ms: Optional[int]` in `click`
|
||||
# was causing problems with @overload
|
||||
def click_hold(
|
||||
self, click: Tuple[int, int], hold_ms: int
|
||||
) -> Optional[LayoutContent]:
|
||||
x, y = click
|
||||
return self.input(x=x, y=y, hold_ms=hold_ms, wait=True)
|
||||
|
||||
def press_yes(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(button=messages.DebugButton.YES, wait=wait)
|
||||
|
||||
def press_no(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(button=messages.DebugButton.NO, wait=wait)
|
||||
|
||||
def press_info(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(button=messages.DebugButton.INFO, wait=wait)
|
||||
|
||||
def swipe_up(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait)
|
||||
|
||||
def swipe_down(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(swipe=messages.DebugSwipeDirection.DOWN, wait=wait)
|
||||
|
||||
@overload
|
||||
def swipe_right(self) -> None: ...
|
||||
|
||||
@overload
|
||||
def swipe_right(self, wait: Literal[True]) -> LayoutContent: ...
|
||||
|
||||
def swipe_right(self, wait: bool = False) -> Union[LayoutContent, None]:
|
||||
return self.input(swipe=messages.DebugSwipeDirection.RIGHT, wait=wait)
|
||||
|
||||
@overload
|
||||
def swipe_left(self) -> None: ...
|
||||
|
||||
@overload
|
||||
def swipe_left(self, wait: Literal[True]) -> LayoutContent: ...
|
||||
|
||||
def swipe_left(self, wait: bool = False) -> Union[LayoutContent, None]:
|
||||
return self.input(swipe=messages.DebugSwipeDirection.LEFT, wait=wait)
|
||||
|
||||
@overload
|
||||
def press_left(self) -> None: ...
|
||||
|
||||
@overload
|
||||
def press_left(self, wait: Literal[True]) -> LayoutContent: ...
|
||||
|
||||
def press_left(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(
|
||||
physical_button=messages.DebugPhysicalButton.LEFT_BTN, wait=wait
|
||||
)
|
||||
|
||||
@overload
|
||||
def press_middle(self) -> None: ...
|
||||
|
||||
@overload
|
||||
def press_middle(self, wait: Literal[True]) -> LayoutContent: ...
|
||||
|
||||
def press_middle(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(
|
||||
physical_button=messages.DebugPhysicalButton.MIDDLE_BTN, wait=wait
|
||||
)
|
||||
|
||||
def press_middle_htc(
|
||||
self, hold_ms: int, extra_ms: int = 200
|
||||
) -> Optional[LayoutContent]:
|
||||
return self.press_htc(
|
||||
button=messages.DebugPhysicalButton.MIDDLE_BTN,
|
||||
hold_ms=hold_ms,
|
||||
extra_ms=extra_ms,
|
||||
)
|
||||
|
||||
@overload
|
||||
def press_right(self) -> None: ...
|
||||
|
||||
@overload
|
||||
def press_right(self, wait: Literal[True]) -> LayoutContent: ...
|
||||
|
||||
def press_right(self, wait: bool = False) -> Optional[LayoutContent]:
|
||||
return self.input(
|
||||
physical_button=messages.DebugPhysicalButton.RIGHT_BTN, wait=wait
|
||||
)
|
||||
|
||||
def press_right_htc(
|
||||
self, hold_ms: int, extra_ms: int = 200
|
||||
) -> Optional[LayoutContent]:
|
||||
return self.press_htc(
|
||||
button=messages.DebugPhysicalButton.RIGHT_BTN,
|
||||
hold_ms=hold_ms,
|
||||
extra_ms=extra_ms,
|
||||
)
|
||||
|
||||
def press_htc(
|
||||
self, button: messages.DebugPhysicalButton, hold_ms: int, extra_ms: int = 200
|
||||
) -> Optional[LayoutContent]:
|
||||
hold_ms = hold_ms + extra_ms # safety margin
|
||||
result = self.input(
|
||||
physical_button=button,
|
||||
hold_ms=hold_ms,
|
||||
)
|
||||
# sleeping little longer for UI to update
|
||||
time.sleep(hold_ms / 1000 + 0.1)
|
||||
return result
|
||||
|
||||
def stop(self) -> None:
|
||||
self._call(messages.DebugLinkStop(), nowait=True)
|
||||
self._write(messages.DebugLinkStop())
|
||||
|
||||
def reseed(self, value: int) -> protobuf.MessageType:
|
||||
return self._call(messages.DebugLinkReseedRandom(value=value))
|
||||
@ -757,44 +763,35 @@ class DebugLink:
|
||||
return self._call(messages.DebugLinkMemoryRead(address=address, length=length))
|
||||
|
||||
def memory_write(self, address: int, memory: bytes, flash: bool = False) -> None:
|
||||
self._call(
|
||||
messages.DebugLinkMemoryWrite(address=address, memory=memory, flash=flash),
|
||||
nowait=True,
|
||||
self._write(
|
||||
messages.DebugLinkMemoryWrite(address=address, memory=memory, flash=flash)
|
||||
)
|
||||
|
||||
def flash_erase(self, sector: int) -> None:
|
||||
self._call(messages.DebugLinkFlashErase(sector=sector), nowait=True)
|
||||
self._write(messages.DebugLinkFlashErase(sector=sector))
|
||||
|
||||
@expect(messages.Success)
|
||||
def erase_sd_card(self, format: bool = True) -> messages.Success:
|
||||
return self._call(messages.DebugLinkEraseSdCard(format=format))
|
||||
|
||||
def take_t1_screenshot_if_relevant(self) -> None:
|
||||
"""Conditionally take screenshots on T1.
|
||||
def save_screenshot(self, data: bytes) -> None:
|
||||
if self.t1_screenshot_directory is None:
|
||||
return
|
||||
|
||||
TT handles them differently, see debuglink.start_recording.
|
||||
"""
|
||||
if self.model is models.T1B1 and self.t1_take_screenshots:
|
||||
self.save_screenshot_for_t1()
|
||||
|
||||
def save_screenshot_for_t1(self) -> None:
|
||||
from PIL import Image
|
||||
|
||||
layout = self.state().layout
|
||||
assert layout is not None
|
||||
assert len(layout) == 128 * 64 // 8
|
||||
assert len(data) == 128 * 64 // 8
|
||||
|
||||
pixels: List[int] = []
|
||||
for byteline in range(64 // 8):
|
||||
offset = byteline * 128
|
||||
row = layout[offset : offset + 128]
|
||||
row = data[offset : offset + 128]
|
||||
for bit in range(8):
|
||||
pixels.extend(bool(px & (1 << bit)) for px in row)
|
||||
|
||||
im = Image.new("1", (128, 64))
|
||||
im.putdata(pixels[::-1])
|
||||
|
||||
assert self.t1_screenshot_directory is not None
|
||||
img_location = (
|
||||
self.t1_screenshot_directory / f"{self.t1_screenshot_counter:04d}.png"
|
||||
)
|
||||
@ -802,6 +799,9 @@ class DebugLink:
|
||||
self.t1_screenshot_counter += 1
|
||||
|
||||
|
||||
del _make_input_func
|
||||
|
||||
|
||||
class NullDebugLink(DebugLink):
|
||||
def __init__(self) -> None:
|
||||
# Ignoring type error as self.transport will not be touched while using NullDebugLink
|
||||
@ -859,15 +859,9 @@ class DebugUI:
|
||||
self.debuglink.press_yes()
|
||||
|
||||
def button_request(self, br: messages.ButtonRequest) -> None:
|
||||
self.debuglink.take_t1_screenshot_if_relevant()
|
||||
self.debuglink.snapshot()
|
||||
|
||||
if self.input_flow is None:
|
||||
# Only calling screen-saver when not in input-flow
|
||||
# as it collides with wait-layout of input flows.
|
||||
# All input flows call debuglink.input(), so
|
||||
# recording their screens that way (as well as
|
||||
# possible swipes below).
|
||||
self.debuglink.save_current_screen_if_relevant(wait=True)
|
||||
self._default_input_flow(br)
|
||||
elif self.input_flow is self.INPUT_FLOW_DONE:
|
||||
raise AssertionError("input flow ended prematurely")
|
||||
@ -879,7 +873,7 @@ class DebugUI:
|
||||
self.input_flow = self.INPUT_FLOW_DONE
|
||||
|
||||
def get_pin(self, code: Optional["PinMatrixRequestType"] = None) -> str:
|
||||
self.debuglink.take_t1_screenshot_if_relevant()
|
||||
self.debuglink.snapshot()
|
||||
|
||||
if self.pins is None:
|
||||
raise RuntimeError("PIN requested but no sequence was configured")
|
||||
@ -890,7 +884,7 @@ class DebugUI:
|
||||
raise AssertionError("PIN sequence ended prematurely")
|
||||
|
||||
def get_passphrase(self, available_on_device: bool) -> str:
|
||||
self.debuglink.take_t1_screenshot_if_relevant()
|
||||
self.debuglink.snapshot()
|
||||
return self.passphrase
|
||||
|
||||
|
||||
|
10
python/src/trezorlib/messages.py
generated
10
python/src/trezorlib/messages.py
generated
@ -308,6 +308,12 @@ class DebugPhysicalButton(IntEnum):
|
||||
RIGHT_BTN = 2
|
||||
|
||||
|
||||
class DebugWaitType(IntEnum):
|
||||
IMMEDIATE = 0
|
||||
NEXT_LAYOUT = 1
|
||||
CURRENT_LAYOUT = 2
|
||||
|
||||
|
||||
class EthereumDefinitionType(IntEnum):
|
||||
NETWORK = 0
|
||||
TOKEN = 1
|
||||
@ -4078,7 +4084,7 @@ class DebugLinkGetState(protobuf.MessageType):
|
||||
FIELDS = {
|
||||
1: protobuf.Field("wait_word_list", "bool", repeated=False, required=False, default=None),
|
||||
2: protobuf.Field("wait_word_pos", "bool", repeated=False, required=False, default=None),
|
||||
3: protobuf.Field("wait_layout", "bool", repeated=False, required=False, default=None),
|
||||
3: protobuf.Field("wait_layout", "DebugWaitType", repeated=False, required=False, default=DebugWaitType.IMMEDIATE),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
@ -4086,7 +4092,7 @@ class DebugLinkGetState(protobuf.MessageType):
|
||||
*,
|
||||
wait_word_list: Optional["bool"] = None,
|
||||
wait_word_pos: Optional["bool"] = None,
|
||||
wait_layout: Optional["bool"] = None,
|
||||
wait_layout: Optional["DebugWaitType"] = DebugWaitType.IMMEDIATE,
|
||||
) -> None:
|
||||
self.wait_word_list = wait_word_list
|
||||
self.wait_word_pos = wait_word_pos
|
||||
|
@ -546,12 +546,17 @@ def format_message(
|
||||
return printable / len(bytes) > 0.8
|
||||
|
||||
def pformat(name: str, value: t.Any, indent: int) -> str:
|
||||
from . import messages
|
||||
|
||||
level = sep * indent
|
||||
leadin = sep * (indent + 1)
|
||||
|
||||
if isinstance(value, MessageType):
|
||||
return format_message(value, indent, sep)
|
||||
|
||||
if isinstance(pb, messages.DebugLinkState) and name == "tokens":
|
||||
return "".join(value)
|
||||
|
||||
if isinstance(value, list):
|
||||
# short list of simple values
|
||||
if not value or all(isinstance(x, int) for x in value):
|
||||
|
@ -1127,7 +1127,7 @@ pub struct DebugLinkGetState {
|
||||
// @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetState.wait_word_pos)
|
||||
pub wait_word_pos: ::std::option::Option<bool>,
|
||||
// @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetState.wait_layout)
|
||||
pub wait_layout: ::std::option::Option<bool>,
|
||||
pub wait_layout: ::std::option::Option<::protobuf::EnumOrUnknown<debug_link_get_state::DebugWaitType>>,
|
||||
// special fields
|
||||
// @@protoc_insertion_point(special_field:hw.trezor.messages.debug.DebugLinkGetState.special_fields)
|
||||
pub special_fields: ::protobuf::SpecialFields,
|
||||
@ -1182,10 +1182,13 @@ impl DebugLinkGetState {
|
||||
self.wait_word_pos = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
// optional bool wait_layout = 3;
|
||||
// optional .hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType wait_layout = 3;
|
||||
|
||||
pub fn wait_layout(&self) -> bool {
|
||||
self.wait_layout.unwrap_or(false)
|
||||
pub fn wait_layout(&self) -> debug_link_get_state::DebugWaitType {
|
||||
match self.wait_layout {
|
||||
Some(e) => e.enum_value_or(debug_link_get_state::DebugWaitType::IMMEDIATE),
|
||||
None => debug_link_get_state::DebugWaitType::IMMEDIATE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_wait_layout(&mut self) {
|
||||
@ -1197,8 +1200,8 @@ impl DebugLinkGetState {
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_wait_layout(&mut self, v: bool) {
|
||||
self.wait_layout = ::std::option::Option::Some(v);
|
||||
pub fn set_wait_layout(&mut self, v: debug_link_get_state::DebugWaitType) {
|
||||
self.wait_layout = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v));
|
||||
}
|
||||
|
||||
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
|
||||
@ -1244,7 +1247,7 @@ impl ::protobuf::Message for DebugLinkGetState {
|
||||
self.wait_word_pos = ::std::option::Option::Some(is.read_bool()?);
|
||||
},
|
||||
24 => {
|
||||
self.wait_layout = ::std::option::Option::Some(is.read_bool()?);
|
||||
self.wait_layout = ::std::option::Option::Some(is.read_enum_or_unknown()?);
|
||||
},
|
||||
tag => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
|
||||
@ -1265,7 +1268,7 @@ impl ::protobuf::Message for DebugLinkGetState {
|
||||
my_size += 1 + 1;
|
||||
}
|
||||
if let Some(v) = self.wait_layout {
|
||||
my_size += 1 + 1;
|
||||
my_size += ::protobuf::rt::int32_size(3, v.value());
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
|
||||
self.special_fields.cached_size().set(my_size as u32);
|
||||
@ -1280,7 +1283,7 @@ impl ::protobuf::Message for DebugLinkGetState {
|
||||
os.write_bool(2, v)?;
|
||||
}
|
||||
if let Some(v) = self.wait_layout {
|
||||
os.write_bool(3, v)?;
|
||||
os.write_enum(3, ::protobuf::EnumOrUnknown::value(&v))?;
|
||||
}
|
||||
os.write_unknown_fields(self.special_fields.unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
@ -1333,6 +1336,76 @@ impl ::protobuf::reflect::ProtobufValue for DebugLinkGetState {
|
||||
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
|
||||
}
|
||||
|
||||
/// Nested message and enums of message `DebugLinkGetState`
|
||||
pub mod debug_link_get_state {
|
||||
#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)]
|
||||
// @@protoc_insertion_point(enum:hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType)
|
||||
pub enum DebugWaitType {
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType.IMMEDIATE)
|
||||
IMMEDIATE = 0,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType.NEXT_LAYOUT)
|
||||
NEXT_LAYOUT = 1,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType.CURRENT_LAYOUT)
|
||||
CURRENT_LAYOUT = 2,
|
||||
}
|
||||
|
||||
impl ::protobuf::Enum for DebugWaitType {
|
||||
const NAME: &'static str = "DebugWaitType";
|
||||
|
||||
fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
|
||||
fn from_i32(value: i32) -> ::std::option::Option<DebugWaitType> {
|
||||
match value {
|
||||
0 => ::std::option::Option::Some(DebugWaitType::IMMEDIATE),
|
||||
1 => ::std::option::Option::Some(DebugWaitType::NEXT_LAYOUT),
|
||||
2 => ::std::option::Option::Some(DebugWaitType::CURRENT_LAYOUT),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(str: &str) -> ::std::option::Option<DebugWaitType> {
|
||||
match str {
|
||||
"IMMEDIATE" => ::std::option::Option::Some(DebugWaitType::IMMEDIATE),
|
||||
"NEXT_LAYOUT" => ::std::option::Option::Some(DebugWaitType::NEXT_LAYOUT),
|
||||
"CURRENT_LAYOUT" => ::std::option::Option::Some(DebugWaitType::CURRENT_LAYOUT),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
const VALUES: &'static [DebugWaitType] = &[
|
||||
DebugWaitType::IMMEDIATE,
|
||||
DebugWaitType::NEXT_LAYOUT,
|
||||
DebugWaitType::CURRENT_LAYOUT,
|
||||
];
|
||||
}
|
||||
|
||||
impl ::protobuf::EnumFull for DebugWaitType {
|
||||
fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor {
|
||||
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new();
|
||||
descriptor.get(|| super::file_descriptor().enum_by_package_relative_name("DebugLinkGetState.DebugWaitType").unwrap()).clone()
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor {
|
||||
let index = *self as usize;
|
||||
Self::enum_descriptor().value_by_index(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::default::Default for DebugWaitType {
|
||||
fn default() -> Self {
|
||||
DebugWaitType::IMMEDIATE
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugWaitType {
|
||||
pub(in super) fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData {
|
||||
::protobuf::reflect::GeneratedEnumDescriptorData::new::<DebugWaitType>("DebugLinkGetState.DebugWaitType")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(message:hw.trezor.messages.debug.DebugLinkState)
|
||||
#[derive(PartialEq,Clone,Default,Debug)]
|
||||
pub struct DebugLinkState {
|
||||
@ -3560,53 +3633,56 @@ impl ::protobuf::reflect::ProtobufValue for DebugLinkOptigaSetSecMax {
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x14messages-debug.proto\x12\x18hw.trezor.messages.debug\x1a\x15messag\
|
||||
es-common.proto\x1a\x19messages-management.proto\x1a\roptions.proto\"\
|
||||
\xb0\x04\n\x11DebugLinkDecision\x12O\n\x06button\x18\x01\x20\x01(\x0e27.\
|
||||
\xb4\x04\n\x11DebugLinkDecision\x12O\n\x06button\x18\x01\x20\x01(\x0e27.\
|
||||
hw.trezor.messages.debug.DebugLinkDecision.DebugButtonR\x06button\x12U\n\
|
||||
\x05swipe\x18\x02\x20\x01(\x0e2?.hw.trezor.messages.debug.DebugLinkDecis\
|
||||
ion.DebugSwipeDirectionR\x05swipe\x12\x14\n\x05input\x18\x03\x20\x01(\tR\
|
||||
\x05input\x12\x0c\n\x01x\x18\x04\x20\x01(\rR\x01x\x12\x0c\n\x01y\x18\x05\
|
||||
\x20\x01(\rR\x01y\x12\x12\n\x04wait\x18\x06\x20\x01(\x08R\x04wait\x12\
|
||||
\x17\n\x07hold_ms\x18\x07\x20\x01(\rR\x06holdMs\x12h\n\x0fphysical_butto\
|
||||
n\x18\x08\x20\x01(\x0e2?.hw.trezor.messages.debug.DebugLinkDecision.Debu\
|
||||
gPhysicalButtonR\x0ephysicalButton\"<\n\x13DebugSwipeDirection\x12\x06\n\
|
||||
\x02UP\x10\0\x12\x08\n\x04DOWN\x10\x01\x12\x08\n\x04LEFT\x10\x02\x12\t\n\
|
||||
\x05RIGHT\x10\x03\"(\n\x0bDebugButton\x12\x06\n\x02NO\x10\0\x12\x07\n\
|
||||
\x03YES\x10\x01\x12\x08\n\x04INFO\x10\x02\"B\n\x13DebugPhysicalButton\
|
||||
\x12\x0c\n\x08LEFT_BTN\x10\0\x12\x0e\n\nMIDDLE_BTN\x10\x01\x12\r\n\tRIGH\
|
||||
T_BTN\x10\x02\")\n\x0fDebugLinkLayout\x12\x16\n\x06tokens\x18\x01\x20\
|
||||
\x03(\tR\x06tokens\"-\n\x15DebugLinkReseedRandom\x12\x14\n\x05value\x18\
|
||||
\x01\x20\x01(\rR\x05value\"j\n\x15DebugLinkRecordScreen\x12)\n\x10target\
|
||||
_directory\x18\x01\x20\x01(\tR\x0ftargetDirectory\x12&\n\rrefresh_index\
|
||||
\x18\x02\x20\x01(\r:\x010R\x0crefreshIndex\"~\n\x11DebugLinkGetState\x12\
|
||||
$\n\x0ewait_word_list\x18\x01\x20\x01(\x08R\x0cwaitWordList\x12\"\n\rwai\
|
||||
t_word_pos\x18\x02\x20\x01(\x08R\x0bwaitWordPos\x12\x1f\n\x0bwait_layout\
|
||||
\x18\x03\x20\x01(\x08R\nwaitLayout\"\x97\x04\n\x0eDebugLinkState\x12\x16\
|
||||
\n\x06layout\x18\x01\x20\x01(\x0cR\x06layout\x12\x10\n\x03pin\x18\x02\
|
||||
\x20\x01(\tR\x03pin\x12\x16\n\x06matrix\x18\x03\x20\x01(\tR\x06matrix\
|
||||
\x12'\n\x0fmnemonic_secret\x18\x04\x20\x01(\x0cR\x0emnemonicSecret\x129\
|
||||
\n\x04node\x18\x05\x20\x01(\x0b2%.hw.trezor.messages.common.HDNodeTypeR\
|
||||
\x04node\x123\n\x15passphrase_protection\x18\x06\x20\x01(\x08R\x14passph\
|
||||
raseProtection\x12\x1d\n\nreset_word\x18\x07\x20\x01(\tR\tresetWord\x12#\
|
||||
\n\rreset_entropy\x18\x08\x20\x01(\x0cR\x0cresetEntropy\x12,\n\x12recove\
|
||||
ry_fake_word\x18\t\x20\x01(\tR\x10recoveryFakeWord\x12*\n\x11recovery_wo\
|
||||
rd_pos\x18\n\x20\x01(\rR\x0frecoveryWordPos\x12$\n\x0ereset_word_pos\x18\
|
||||
\x0b\x20\x01(\rR\x0cresetWordPos\x12N\n\rmnemonic_type\x18\x0c\x20\x01(\
|
||||
\x0e2).hw.trezor.messages.management.BackupTypeR\x0cmnemonicType\x12\x16\
|
||||
\n\x06tokens\x18\r\x20\x03(\tR\x06tokens\"\x0f\n\rDebugLinkStop\"P\n\x0c\
|
||||
DebugLinkLog\x12\x14\n\x05level\x18\x01\x20\x01(\rR\x05level\x12\x16\n\
|
||||
\x06bucket\x18\x02\x20\x01(\tR\x06bucket\x12\x12\n\x04text\x18\x03\x20\
|
||||
\x01(\tR\x04text\"G\n\x13DebugLinkMemoryRead\x12\x18\n\x07address\x18\
|
||||
\x01\x20\x01(\rR\x07address\x12\x16\n\x06length\x18\x02\x20\x01(\rR\x06l\
|
||||
ength\")\n\x0fDebugLinkMemory\x12\x16\n\x06memory\x18\x01\x20\x01(\x0cR\
|
||||
\x06memory\"^\n\x14DebugLinkMemoryWrite\x12\x18\n\x07address\x18\x01\x20\
|
||||
\x01(\rR\x07address\x12\x16\n\x06memory\x18\x02\x20\x01(\x0cR\x06memory\
|
||||
\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\x05flash\"-\n\x13DebugLinkFlas\
|
||||
hErase\x12\x16\n\x06sector\x18\x01\x20\x01(\rR\x06sector\".\n\x14DebugLi\
|
||||
nkEraseSdCard\x12\x16\n\x06format\x18\x01\x20\x01(\x08R\x06format\",\n\
|
||||
\x14DebugLinkWatchLayout\x12\x14\n\x05watch\x18\x01\x20\x01(\x08R\x05wat\
|
||||
ch\"\x1b\n\x19DebugLinkResetDebugEvents\"\x1a\n\x18DebugLinkOptigaSetSec\
|
||||
MaxB=\n#com.satoshilabs.trezor.lib.protobufB\x12TrezorMessageDebug\x80\
|
||||
\xa6\x1d\x01\
|
||||
\x20\x01(\rR\x01y\x12\x16\n\x04wait\x18\x06\x20\x01(\x08R\x04waitB\x02\
|
||||
\x18\x01\x12\x17\n\x07hold_ms\x18\x07\x20\x01(\rR\x06holdMs\x12h\n\x0fph\
|
||||
ysical_button\x18\x08\x20\x01(\x0e2?.hw.trezor.messages.debug.DebugLinkD\
|
||||
ecision.DebugPhysicalButtonR\x0ephysicalButton\"<\n\x13DebugSwipeDirecti\
|
||||
on\x12\x06\n\x02UP\x10\0\x12\x08\n\x04DOWN\x10\x01\x12\x08\n\x04LEFT\x10\
|
||||
\x02\x12\t\n\x05RIGHT\x10\x03\"(\n\x0bDebugButton\x12\x06\n\x02NO\x10\0\
|
||||
\x12\x07\n\x03YES\x10\x01\x12\x08\n\x04INFO\x10\x02\"B\n\x13DebugPhysica\
|
||||
lButton\x12\x0c\n\x08LEFT_BTN\x10\0\x12\x0e\n\nMIDDLE_BTN\x10\x01\x12\r\
|
||||
\n\tRIGHT_BTN\x10\x02\"-\n\x0fDebugLinkLayout\x12\x16\n\x06tokens\x18\
|
||||
\x01\x20\x03(\tR\x06tokens:\x02\x18\x01\"-\n\x15DebugLinkReseedRandom\
|
||||
\x12\x14\n\x05value\x18\x01\x20\x01(\rR\x05value\"j\n\x15DebugLinkRecord\
|
||||
Screen\x12)\n\x10target_directory\x18\x01\x20\x01(\tR\x0ftargetDirectory\
|
||||
\x12&\n\rrefresh_index\x18\x02\x20\x01(\r:\x010R\x0crefreshIndex\"\x91\
|
||||
\x02\n\x11DebugLinkGetState\x12(\n\x0ewait_word_list\x18\x01\x20\x01(\
|
||||
\x08R\x0cwaitWordListB\x02\x18\x01\x12&\n\rwait_word_pos\x18\x02\x20\x01\
|
||||
(\x08R\x0bwaitWordPosB\x02\x18\x01\x12e\n\x0bwait_layout\x18\x03\x20\x01\
|
||||
(\x0e29.hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType:\tIMMED\
|
||||
IATER\nwaitLayout\"C\n\rDebugWaitType\x12\r\n\tIMMEDIATE\x10\0\x12\x0f\n\
|
||||
\x0bNEXT_LAYOUT\x10\x01\x12\x12\n\x0eCURRENT_LAYOUT\x10\x02\"\x97\x04\n\
|
||||
\x0eDebugLinkState\x12\x16\n\x06layout\x18\x01\x20\x01(\x0cR\x06layout\
|
||||
\x12\x10\n\x03pin\x18\x02\x20\x01(\tR\x03pin\x12\x16\n\x06matrix\x18\x03\
|
||||
\x20\x01(\tR\x06matrix\x12'\n\x0fmnemonic_secret\x18\x04\x20\x01(\x0cR\
|
||||
\x0emnemonicSecret\x129\n\x04node\x18\x05\x20\x01(\x0b2%.hw.trezor.messa\
|
||||
ges.common.HDNodeTypeR\x04node\x123\n\x15passphrase_protection\x18\x06\
|
||||
\x20\x01(\x08R\x14passphraseProtection\x12\x1d\n\nreset_word\x18\x07\x20\
|
||||
\x01(\tR\tresetWord\x12#\n\rreset_entropy\x18\x08\x20\x01(\x0cR\x0creset\
|
||||
Entropy\x12,\n\x12recovery_fake_word\x18\t\x20\x01(\tR\x10recoveryFakeWo\
|
||||
rd\x12*\n\x11recovery_word_pos\x18\n\x20\x01(\rR\x0frecoveryWordPos\x12$\
|
||||
\n\x0ereset_word_pos\x18\x0b\x20\x01(\rR\x0cresetWordPos\x12N\n\rmnemoni\
|
||||
c_type\x18\x0c\x20\x01(\x0e2).hw.trezor.messages.management.BackupTypeR\
|
||||
\x0cmnemonicType\x12\x16\n\x06tokens\x18\r\x20\x03(\tR\x06tokens\"\x0f\n\
|
||||
\rDebugLinkStop\"P\n\x0cDebugLinkLog\x12\x14\n\x05level\x18\x01\x20\x01(\
|
||||
\rR\x05level\x12\x16\n\x06bucket\x18\x02\x20\x01(\tR\x06bucket\x12\x12\n\
|
||||
\x04text\x18\x03\x20\x01(\tR\x04text\"G\n\x13DebugLinkMemoryRead\x12\x18\
|
||||
\n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06length\x18\x02\
|
||||
\x20\x01(\rR\x06length\")\n\x0fDebugLinkMemory\x12\x16\n\x06memory\x18\
|
||||
\x01\x20\x01(\x0cR\x06memory\"^\n\x14DebugLinkMemoryWrite\x12\x18\n\x07a\
|
||||
ddress\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06memory\x18\x02\x20\
|
||||
\x01(\x0cR\x06memory\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\x05flash\"\
|
||||
-\n\x13DebugLinkFlashErase\x12\x16\n\x06sector\x18\x01\x20\x01(\rR\x06se\
|
||||
ctor\".\n\x14DebugLinkEraseSdCard\x12\x16\n\x06format\x18\x01\x20\x01(\
|
||||
\x08R\x06format\"0\n\x14DebugLinkWatchLayout\x12\x14\n\x05watch\x18\x01\
|
||||
\x20\x01(\x08R\x05watch:\x02\x18\x01\"\x1f\n\x19DebugLinkResetDebugEvent\
|
||||
s:\x02\x18\x01\"\x1a\n\x18DebugLinkOptigaSetSecMaxB=\n#com.satoshilabs.t\
|
||||
rezor.lib.protobufB\x12TrezorMessageDebug\x80\xa6\x1d\x01\
|
||||
";
|
||||
|
||||
/// `FileDescriptorProto` object which was a source for this generated file
|
||||
@ -3644,10 +3720,11 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
|
||||
messages.push(DebugLinkWatchLayout::generated_message_descriptor_data());
|
||||
messages.push(DebugLinkResetDebugEvents::generated_message_descriptor_data());
|
||||
messages.push(DebugLinkOptigaSetSecMax::generated_message_descriptor_data());
|
||||
let mut enums = ::std::vec::Vec::with_capacity(3);
|
||||
let mut enums = ::std::vec::Vec::with_capacity(4);
|
||||
enums.push(debug_link_decision::DebugSwipeDirection::generated_enum_descriptor_data());
|
||||
enums.push(debug_link_decision::DebugButton::generated_enum_descriptor_data());
|
||||
enums.push(debug_link_decision::DebugPhysicalButton::generated_enum_descriptor_data());
|
||||
enums.push(debug_link_get_state::DebugWaitType::generated_enum_descriptor_data());
|
||||
::protobuf::reflect::GeneratedFileDescriptor::new_generated(
|
||||
file_descriptor_proto(),
|
||||
deps,
|
||||
|
@ -2,6 +2,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
|
||||
from trezorlib.client import PASSPHRASE_ON_DEVICE
|
||||
from trezorlib.messages import DebugWaitType
|
||||
from trezorlib.transport import udp
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -42,6 +43,7 @@ class BackgroundDeviceHandler:
|
||||
self.client = client
|
||||
self.client.ui = NullUI # type: ignore [NullUI is OK UI]
|
||||
self.client.watch_layout(True)
|
||||
self.client.debug.input_wait_type = DebugWaitType.CURRENT_LAYOUT
|
||||
|
||||
def run(self, function: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
|
||||
"""Runs some function that interacts with a device.
|
||||
@ -50,8 +52,14 @@ class BackgroundDeviceHandler:
|
||||
"""
|
||||
if self.task is not None:
|
||||
raise RuntimeError("Wait for previous task first")
|
||||
self.task = self._pool.submit(function, self.client, *args, **kwargs)
|
||||
self.debuglink().wait_layout(wait_for_external_change=True)
|
||||
|
||||
# make sure we start the wait while a layout is up
|
||||
# TODO should this be part of "wait_for_layout_change"?
|
||||
self.debuglink().read_layout()
|
||||
# from the displayed layout, wait for the first UI change triggered by the
|
||||
# task running in the background
|
||||
with self.debuglink().wait_for_layout_change():
|
||||
self.task = self._pool.submit(function, self.client, *args, **kwargs)
|
||||
|
||||
def kill_task(self) -> None:
|
||||
if self.task is not None:
|
||||
|
@ -383,8 +383,8 @@ def test_signmessage_pagination_trailing_newline(client: Client):
|
||||
[
|
||||
# expect address confirmation
|
||||
message_filters.ButtonRequest(code=messages.ButtonRequestType.Other),
|
||||
# expect a ButtonRequest that does not have pagination set
|
||||
message_filters.ButtonRequest(pages=None),
|
||||
# expect a ButtonRequest for a single-page screen
|
||||
message_filters.ButtonRequest(pages=1),
|
||||
messages.MessageSignature,
|
||||
]
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user