from typing import TYPE_CHECKING from trezor import io, log, loop, ui, wire, workflow from trezor.enums import ButtonRequestType import trezorui2 from ...constants.tt import MONO_ADDR_PER_LINE from ..common import button_request, interact if TYPE_CHECKING: from typing import Any, Awaitable, Iterable, NoReturn, Sequence from ..common import PropertyType, ExceptionType class _RustLayout(ui.Layout): # pylint: disable=super-init-not-called def __init__(self, layout: Any): self.layout = layout self.timer = loop.Timer() self.layout.attach_timer_fn(self.set_timer) def set_timer(self, token: int, deadline: int) -> None: self.timer.schedule(deadline, token) if __debug__: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: from apps.debug import confirm_signal, input_signal return ( self.handle_timers(), self.handle_input_and_rendering(), confirm_signal(), input_signal(), ) def read_content(self) -> list[str]: result = [] def callback(*args): for arg in args: result.append(str(arg)) self.layout.trace(callback) result = " ".join(result).split("\n") return result else: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: return self.handle_timers(), self.handle_input_and_rendering() def _before_render(self) -> None: # Clear the screen of any leftovers. ui.backlight_fade(ui.style.BACKLIGHT_DIM) ui.display.clear() if __debug__ and self.should_notify_layout_change: from apps.debug import notify_layout_change # notify about change and do not notify again until next await. # (handle_rendering might be called multiple times in a single await, # because of the endless loop in __iter__) self.should_notify_layout_change = False notify_layout_change(self) # Turn the brightness on again. ui.backlight_fade(self.BACKLIGHT_LEVEL) def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator] touch = loop.wait(io.TOUCH) self._before_render() self.layout.paint() # self.layout.bounds() while True: # Using `yield` instead of `await` to avoid allocations. event, x, y = yield touch workflow.idle_timer.touch() msg = None if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END): msg = self.layout.touch_event(event, x, y) self.layout.paint() # self.layout.bounds() if msg is not None: raise ui.Result(msg) def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator] while True: # Using `yield` instead of `await` to avoid allocations. token = yield self.timer msg = self.layout.timer(token) self.layout.paint() if msg is not None: raise ui.Result(msg) def page_count(self) -> int: return self.layout.page_count() async def confirm_action( ctx: wire.GenericContext, br_type: str, title: str, action: str | None = None, description: str | None = None, description_param: str | None = None, description_param_font: int = ui.BOLD, verb: str | bytes | None = "CONFIRM", verb_cancel: str | bytes | None = None, hold: bool = False, hold_danger: bool = False, icon: str | None = None, icon_color: int | None = None, reverse: bool = False, larger_vspace: bool = False, exc: ExceptionType = wire.ActionCancelled, br_code: ButtonRequestType = ButtonRequestType.Other, ) -> None: if isinstance(verb, bytes) or isinstance(verb_cancel, bytes): raise NotImplementedError elif isinstance(verb, str): verb = verb.upper() if description is not None and description_param is not None: if description_param_font != ui.BOLD: log.error(__name__, "confirm_action description_param_font not implemented") description = description.format(description_param) if hold: log.error(__name__, "confirm_action hold not implemented") if verb_cancel: log.error(__name__, "confirm_action verb_cancel not implemented") result = await interact( ctx, _RustLayout( trezorui2.confirm_action( title=title.upper(), action=action, description=description, verb=verb, hold=hold, reverse=reverse, ) ), br_type, br_code, ) if result is not trezorui2.CONFIRMED: raise exc async def confirm_reset_device( ctx: wire.GenericContext, prompt: str, recovery: bool = False ) -> None: return await confirm_action( ctx, "recover_device" if recovery else "setup_device", "not implemented", action="not implemented", ) # TODO cleanup @ redesign async def confirm_backup(ctx: wire.GenericContext) -> bool: raise NotImplementedError async def confirm_path_warning( ctx: wire.GenericContext, path: str, path_type: str = "Path" ) -> None: raise NotImplementedError async def show_xpub( ctx: wire.GenericContext, xpub: str, title: str, cancel: str ) -> None: raise NotImplementedError async def show_address( ctx: wire.GenericContext, address: str, *, case_sensitive: bool = True, address_qr: str | None = None, title: str = "Confirm address", network: str | None = None, multisig_index: int | None = None, xpubs: Sequence[str] = (), address_extra: str | None = None, title_qr: str | None = None, ) -> None: raise NotImplementedError def show_pubkey( ctx: wire.Context, pubkey: str, title: str = "Confirm public key" ) -> Awaitable[None]: return confirm_blob( ctx, br_type="show_pubkey", title="Confirm public key", data=pubkey, br_code=ButtonRequestType.PublicKey, icon=ui.ICON_RECEIVE, ) async def _show_modal( ctx: wire.GenericContext, br_type: str, br_code: ButtonRequestType, header: str, subheader: str | None, content: str, button_confirm: str | None, button_cancel: str | None, icon: str, icon_color: int, exc: ExceptionType = wire.ActionCancelled, ) -> None: raise NotImplementedError async def show_error_and_raise( ctx: wire.GenericContext, br_type: str, content: str, header: str = "Error", subheader: str | None = None, button: str = "Close", red: bool = False, exc: ExceptionType = wire.ActionCancelled, ) -> NoReturn: await _show_modal( ctx, br_type=br_type, br_code=ButtonRequestType.Other, header=header, subheader=subheader, content=content, button_confirm=None, button_cancel=button, icon=ui.ICON_WRONG, icon_color=ui.RED if red else ui.ORANGE_ICON, exc=exc, ) raise exc def show_warning( ctx: wire.GenericContext, br_type: str, content: str, header: str = "Warning", subheader: str | None = None, button: str = "Try again", br_code: ButtonRequestType = ButtonRequestType.Warning, icon: str = ui.ICON_WRONG, icon_color: int = ui.RED, ) -> Awaitable[None]: return _show_modal( ctx, br_type=br_type, br_code=br_code, header=header, subheader=subheader, content=content, button_confirm=button, button_cancel=None, icon=icon, icon_color=icon_color, ) def show_success( ctx: wire.GenericContext, br_type: str, content: str, subheader: str | None = None, button: str = "Continue", ) -> Awaitable[None]: return _show_modal( ctx, br_type=br_type, br_code=ButtonRequestType.Success, header="Success", subheader=subheader, content=content, button_confirm=button, button_cancel=None, icon=ui.ICON_CONFIRM, icon_color=ui.GREEN, ) async def confirm_output( ctx: wire.GenericContext, address: str, amount: str, font_amount: int = ui.NORMAL, # TODO cleanup @ redesign title: str = "Confirm sending", subtitle: str | None = None, # TODO cleanup @ redesign color_to: int = ui.FG, # TODO cleanup @ redesign to_str: str = " to\n", # TODO cleanup @ redesign to_paginated: bool = False, # TODO cleanup @ redesign width: int = MONO_ADDR_PER_LINE, width_paginated: int = MONO_ADDR_PER_LINE - 1, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, icon: str = ui.ICON_SEND, ) -> None: raise NotImplementedError async def confirm_payment_request( ctx: wire.GenericContext, recipient_name: str, amount: str, memos: list[str], ) -> Any: raise NotImplementedError async def should_show_more( ctx: wire.GenericContext, title: str, para: Iterable[tuple[int, str]], button_text: str = "Show all", br_type: str = "should_show_more", br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_DEFAULT, icon_color: int = ui.ORANGE_ICON, ) -> bool: raise NotImplementedError async def confirm_blob( ctx: wire.GenericContext, br_type: str, title: str, data: bytes | str, description: str | None = None, hold: bool = False, br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign ask_pagination: bool = False, ) -> None: raise NotImplementedError def confirm_address( ctx: wire.GenericContext, title: str, address: str, description: str | None = "Address:", br_type: str = "confirm_address", br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign ) -> Awaitable[None]: raise NotImplementedError async def confirm_text( ctx: wire.GenericContext, br_type: str, title: str, data: str, description: str | None = None, br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign ) -> None: raise NotImplementedError def confirm_amount( ctx: wire.GenericContext, title: str, amount: str, description: str = "Amount:", br_type: str = "confirm_amount", br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign ) -> Awaitable[None]: raise NotImplementedError async def confirm_properties( ctx: wire.GenericContext, br_type: str, title: str, props: Iterable[PropertyType], icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign hold: bool = False, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, ) -> None: raise NotImplementedError async def confirm_total( ctx: wire.GenericContext, total_amount: str, fee_amount: str, fee_rate_amount: str | None = None, title: str = "Confirm transaction", total_label: str = "Total amount:\n", fee_label: str = "\nincluding fee:\n", icon_color: int = ui.GREEN, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> None: raise NotImplementedError async def confirm_joint_total( ctx: wire.GenericContext, spending_amount: str, total_amount: str ) -> None: raise NotImplementedError async def confirm_metadata( ctx: wire.GenericContext, br_type: str, title: str, content: str, param: str | None = None, br_code: ButtonRequestType = ButtonRequestType.SignTx, hide_continue: bool = False, hold: bool = False, param_font: int = ui.BOLD, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign larger_vspace: bool = False, # TODO cleanup @ redesign ) -> None: raise NotImplementedError async def confirm_replacement( ctx: wire.GenericContext, description: str, txid: str ) -> None: raise NotImplementedError async def confirm_modify_output( ctx: wire.GenericContext, address: str, sign: int, amount_change: str, amount_new: str, ) -> None: raise NotImplementedError async def confirm_modify_fee( ctx: wire.GenericContext, sign: int, user_fee_change: str, total_fee_new: str, ) -> None: raise NotImplementedError async def confirm_coinjoin( ctx: wire.GenericContext, coin_name: str, max_rounds: int, max_fee_per_vbyte: str ) -> None: raise NotImplementedError # TODO cleanup @ redesign async def confirm_sign_identity( ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: str | None ) -> None: raise NotImplementedError async def confirm_signverify( ctx: wire.GenericContext, coin: str, message: str, address: str, verify: bool ) -> None: raise NotImplementedError async def show_popup( title: str, description: str, subtitle: str | None = None, description_param: str = "", timeout_ms: int = 3000, ) -> None: raise NotImplementedError def draw_simple_text(title: str, description: str = "") -> None: log.error(__name__, "draw_simple_text not implemented") async def request_passphrase_on_device(ctx: wire.GenericContext, max_len: int) -> str: await button_request( ctx, "passphrase_device", code=ButtonRequestType.PassphraseEntry ) keyboard = _RustLayout( trezorui2.request_passphrase(prompt="Enter passphrase", max_len=max_len) ) result = await ctx.wait(keyboard) if result is trezorui2.CANCELLED: raise wire.ActionCancelled("Passphrase entry cancelled") assert isinstance(result, str) return result async def request_pin_on_device( ctx: wire.GenericContext, prompt: str, attempts_remaining: int | None, allow_cancel: bool, ) -> str: await button_request(ctx, "pin_device", code=ButtonRequestType.PinEntry) warning = "Wrong PIN" if "Wrong" in prompt else None if attempts_remaining is None: subprompt = "" elif attempts_remaining == 1: prompt = "Enter PIN" subprompt = "Last attempt" else: prompt = "Enter PIN" subprompt = f"{attempts_remaining} tries left" dialog = _RustLayout( trezorui2.request_pin( prompt=prompt, subprompt=subprompt, allow_cancel=allow_cancel, warning=warning, ) ) while True: result = await ctx.wait(dialog) if result is trezorui2.CANCELLED: raise wire.PinCancelled assert isinstance(result, str) return result