1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-23 14:58:09 +00:00

fix(core/ui): T3T1 ButtonRequests from rust

[no changelog]
This commit is contained in:
Martin Milata 2024-05-20 18:30:57 +02:00
parent a212b325fe
commit b16411631b
10 changed files with 163 additions and 91 deletions

View File

@ -101,6 +101,8 @@ static void _librust_qstrs(void) {
MP_QSTR_bitcoin__voting_rights; MP_QSTR_bitcoin__voting_rights;
MP_QSTR_bootscreen; MP_QSTR_bootscreen;
MP_QSTR_bounds; MP_QSTR_bounds;
MP_QSTR_br_code;
MP_QSTR_br_type;
MP_QSTR_button; MP_QSTR_button;
MP_QSTR_button_event; MP_QSTR_button_event;
MP_QSTR_button_request; MP_QSTR_button_request;

View File

@ -63,3 +63,21 @@ pub trait ButtonRequestExt {
} }
impl<T: Component> ButtonRequestExt for T {} impl<T: Component> ButtonRequestExt for T {}
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
impl<T> crate::ui::flow::Swipable for OneButtonRequest<T>
where
T: Component + crate::ui::flow::Swipable,
{
fn swipe_start(
&mut self,
ctx: &mut EventCtx,
direction: crate::ui::component::SwipeDirection,
) -> bool {
self.inner.swipe_start(ctx, direction)
}
fn swipe_finished(&self) -> bool {
self.inner.swipe_finished()
}
}

View File

@ -4,7 +4,8 @@ use crate::{
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ComponentExt, SwipeDirection}, button_request::ButtonRequest,
component::{ButtonRequestExt, ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage}, flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
}, },
}; };
@ -91,6 +92,8 @@ impl ConfirmOutput {
let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let account_path: Option<TString> = let account_path: Option<TString> =
kwargs.get(Qstr::MP_QSTR_account_path)?.try_into_option()?; kwargs.get(Qstr::MP_QSTR_account_path)?.try_into_option()?;
let br_type: TString = kwargs.get(Qstr::MP_QSTR_br_type)?.try_into()?;
let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?;
let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?; let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?;
let amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?; let amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?;
@ -105,8 +108,8 @@ impl ConfirmOutput {
.with_footer(TR::instructions__swipe_up.into(), None) .with_footer(TR::instructions__swipe_up.into(), None)
.with_chunkify(chunkify) .with_chunkify(chunkify)
.with_text_mono(text_mono) .with_text_mono(text_mono)
.into_layout()?; .into_layout()?
// .one_button_request(ButtonRequestCode::ConfirmOutput, br_type); .one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Amount // Amount
let content_amount = ConfirmBlobParams::new(TR::words__amount.into(), amount, None) let content_amount = ConfirmBlobParams::new(TR::words__amount.into(), amount, None)
@ -114,8 +117,8 @@ impl ConfirmOutput {
.with_menu_button() .with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None) .with_footer(TR::instructions__swipe_up.into(), None)
.with_text_mono(text_mono) .with_text_mono(text_mono)
.into_layout()?; .into_layout()?
// .one_button_request(ButtonRequestCode::ConfirmOutput, br_type); .one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Menu // Menu
let content_menu = Frame::left_aligned( let content_menu = Frame::left_aligned(

View File

@ -4,7 +4,8 @@ use crate::{
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ComponentExt, SwipeDirection}, button_request::ButtonRequest,
component::{ButtonRequestExt, ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
}, },
}; };
@ -91,6 +92,8 @@ impl ConfirmSummary {
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_items)?; let account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_items)?;
let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?; let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?;
let br_type: TString = kwargs.get(Qstr::MP_QSTR_br_type)?.try_into()?;
let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?;
// Summary // Summary
let mut summary = ShowInfoParams::new(title) let mut summary = ShowInfoParams::new(title)
@ -100,7 +103,9 @@ impl ConfirmSummary {
let [label, value]: [TString; 2] = util::iter_into_array(pair)?; let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
summary = unwrap!(summary.add(label, value)); summary = unwrap!(summary.add(label, value));
} }
let content_summary = summary.into_layout()?; let content_summary = summary
.into_layout()?
.one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Hold to confirm // Hold to confirm
let content_hold = Frame::left_aligned( let content_hold = Frame::left_aligned(

View File

@ -4,9 +4,10 @@ use crate::{
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
button_request::ButtonRequest,
component::{ component::{
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs}, text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
ComponentExt, Qr, SwipeDirection, ButtonRequestExt, ComponentExt, Qr, SwipeDirection,
}, },
flow::{ flow::{
base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow, base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow,
@ -147,6 +148,9 @@ impl GetAddress {
let path: Option<TString> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; let path: Option<TString> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?;
let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?;
let br_type: TString = kwargs.get(Qstr::MP_QSTR_br_type)?.try_into()?;
let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?;
// Address // Address
let data_style = if chunkify { let data_style = if chunkify {
let address: TString = address.try_into()?; let address: TString = address.try_into()?;
@ -166,8 +170,8 @@ impl GetAddress {
let content_address = Frame::left_aligned(title, SwipePage::vertical(paragraphs)) let content_address = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button() .with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None) .with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)); .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
// .one_button_request(ButtonRequestCode::Address, "show_address"); .one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Tap // Tap
let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm()) let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm())

View File

@ -1980,6 +1980,8 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// account: str | None, /// account: str | None,
/// path: str | None, /// path: str | None,
/// xpubs: list[tuple[str, str]], /// xpubs: list[tuple[str, str]],
/// br_code: ButtonRequestType,
/// br_type: str,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Get address / receive funds.""" /// """Get address / receive funds."""
Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, flow::get_address::new_get_address).as_obj(), Qstr::MP_QSTR_flow_get_address => obj_fn_kw!(0, flow::get_address::new_get_address).as_obj(),
@ -2001,6 +2003,8 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// chunkify: bool, /// chunkify: bool,
/// account: str | None, /// account: str | None,
/// account_path: str | None, /// account_path: str | None,
/// br_code: ButtonRequestType,
/// br_type: str,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Confirm recipient.""" /// """Confirm recipient."""
Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, flow::new_confirm_output).as_obj(), Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, flow::new_confirm_output).as_obj(),
@ -2011,6 +2015,8 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// items: Iterable[tuple[str, str]], /// items: Iterable[tuple[str, str]],
/// account_items: Iterable[tuple[str, str]], /// account_items: Iterable[tuple[str, str]],
/// fee_items: Iterable[tuple[str, str]], /// fee_items: Iterable[tuple[str, str]],
/// br_code: ButtonRequestType,
/// br_type: str,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Total summary and hold to confirm.""" /// """Total summary and hold to confirm."""
Qstr::MP_QSTR_flow_confirm_summary => obj_fn_kw!(0, flow::new_confirm_summary).as_obj(), Qstr::MP_QSTR_flow_confirm_summary => obj_fn_kw!(0, flow::new_confirm_summary).as_obj(),

View File

@ -537,6 +537,8 @@ def flow_get_address(
account: str | None, account: str | None,
path: str | None, path: str | None,
xpubs: list[tuple[str, str]], xpubs: list[tuple[str, str]],
br_code: ButtonRequestType,
br_type: str,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Get address / receive funds.""" """Get address / receive funds."""
@ -560,6 +562,8 @@ def flow_confirm_output(
chunkify: bool, chunkify: bool,
account: str | None, account: str | None,
account_path: str | None, account_path: str | None,
br_code: ButtonRequestType,
br_type: str,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Confirm recipient.""" """Confirm recipient."""
@ -571,6 +575,8 @@ def flow_confirm_summary(
items: Iterable[tuple[str, str]], items: Iterable[tuple[str, str]],
account_items: Iterable[tuple[str, str]], account_items: Iterable[tuple[str, str]],
fee_items: Iterable[tuple[str, str]], fee_items: Iterable[tuple[str, str]],
br_code: ButtonRequestType,
br_type: str,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Total summary and hold to confirm.""" """Total summary and hold to confirm."""
CONFIRMED: UiResult CONFIRMED: UiResult

View File

@ -1,10 +1,10 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import trezorui2 import trezorui2
from trezor import TR, io, loop, ui, utils from trezor import TR, io, log, loop, ui, utils
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled from trezor.messages import ButtonAck, ButtonRequest
from trezor.wire.context import wait as ctx_wait from trezor.wire import ActionCancelled, context
from ..common import button_request, interact from ..common import button_request, interact
@ -34,9 +34,11 @@ class RustLayout(ui.Layout):
# pylint: disable=super-init-not-called # pylint: disable=super-init-not-called
def __init__(self, layout: Any): def __init__(self, layout: Any):
self.br_chan = loop.chan()
self.layout = layout self.layout = layout
self.timer = loop.Timer() self.timer = loop.Timer()
self.layout.attach_timer_fn(self.set_timer) self.layout.attach_timer_fn(self.set_timer)
self._send_button_request()
def set_timer(self, token: int, deadline: int) -> None: def set_timer(self, token: int, deadline: int) -> None:
self.timer.schedule(deadline, token) self.timer.schedule(deadline, token)
@ -57,6 +59,16 @@ class RustLayout(ui.Layout):
if __debug__: if __debug__:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
if context.CURRENT_CONTEXT:
return (
self.handle_timers(),
self.handle_input_and_rendering(),
self.handle_swipe(),
self.handle_click_signal(),
self.handle_result_signal(),
self.handle_usb(context.get_context()),
)
else:
return ( return (
self.handle_timers(), self.handle_timers(),
self.handle_input_and_rendering(), self.handle_input_and_rendering(),
@ -110,6 +122,7 @@ class RustLayout(ui.Layout):
(io.TOUCH_END, orig_x + 2 * off_x, orig_y + 2 * off_y), (io.TOUCH_END, orig_x + 2 * off_x, orig_y + 2 * off_y),
): ):
msg = self.layout.touch_event(event, x, y) msg = self.layout.touch_event(event, x, y)
self._send_button_request()
self._paint() self._paint()
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
@ -129,10 +142,12 @@ class RustLayout(ui.Layout):
from apps.debug import notify_layout_change from apps.debug import notify_layout_change
self.layout.touch_event(io.TOUCH_START, x, y) self.layout.touch_event(io.TOUCH_START, x, y)
self._send_button_request()
self._paint() self._paint()
if hold_ms is not None: if hold_ms is not None:
await loop.sleep(hold_ms) await loop.sleep(hold_ms)
msg = self.layout.touch_event(io.TOUCH_END, x, y) msg = self.layout.touch_event(io.TOUCH_END, x, y)
self._send_button_request()
if msg is not None: if msg is not None:
debug_storage.new_layout_event_id = event_id debug_storage.new_layout_event_id = event_id
@ -159,7 +174,17 @@ class RustLayout(ui.Layout):
else: else:
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return self.handle_timers(), self.handle_input_and_rendering() if context.CURRENT_CONTEXT:
return (
self.handle_timers(),
self.handle_input_and_rendering(),
self.handle_usb(context.get_context()),
)
else:
return (
self.handle_timers(),
self.handle_input_and_rendering(),
)
def _first_paint(self) -> None: def _first_paint(self) -> None:
ui.backlight_fade(ui.style.BACKLIGHT_NONE) ui.backlight_fade(ui.style.BACKLIGHT_NONE)
@ -199,6 +224,7 @@ class RustLayout(ui.Layout):
msg = None msg = None
if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END): if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END):
msg = self.layout.touch_event(event, x, y) msg = self.layout.touch_event(event, x, y)
self._send_button_request()
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
self._paint() self._paint()
@ -208,6 +234,7 @@ class RustLayout(ui.Layout):
# Using `yield` instead of `await` to avoid allocations. # Using `yield` instead of `await` to avoid allocations.
token = yield self.timer token = yield self.timer
msg = self.layout.timer(token) msg = self.layout.timer(token)
self._send_button_request()
if msg is not None: if msg is not None:
raise ui.Result(msg) raise ui.Result(msg)
self._paint() self._paint()
@ -215,6 +242,20 @@ class RustLayout(ui.Layout):
def page_count(self) -> int: def page_count(self) -> int:
return self.layout.page_count() return self.layout.page_count()
async def handle_usb(self, ctx: context.Context):
while True:
br_code, br_type, page_count = await loop.race(
ctx.read(()), self.br_chan.take()
)
log.debug(__name__, "ButtonRequest.type=%s", br_type)
await ctx.call(ButtonRequest(code=br_code, pages=page_count), ButtonAck)
def _send_button_request(self):
res = self.layout.button_request()
if res is not None:
br_code, br_type = res
self.br_chan.publish((br_code, br_type, self.layout.page_count()))
def draw_simple(layout: Any) -> None: def draw_simple(layout: Any) -> None:
# Simple drawing not supported for layouts that set timers. # Simple drawing not supported for layouts that set timers.
@ -421,7 +462,6 @@ async def show_address(
return result return result
await raise_if_not_confirmed( await raise_if_not_confirmed(
interact(
RustLayout( RustLayout(
trezorui2.flow_get_address( trezorui2.flow_get_address(
address=address, address=address,
@ -433,10 +473,9 @@ async def show_address(
account=account, account=account,
path=path, path=path,
xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)], xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)],
br_type=br_type,
br_code=br_code,
) )
),
br_type,
br_code,
) )
) )
@ -551,9 +590,7 @@ async def confirm_output(
else: else:
title = TR.send__title_sending_to title = TR.send__title_sending_to
# TODO: this should send 2x ButtonRequest
await raise_if_not_confirmed( await raise_if_not_confirmed(
interact(
RustLayout( RustLayout(
trezorui2.flow_confirm_output( trezorui2.flow_confirm_output(
address=address, address=address,
@ -562,10 +599,9 @@ async def confirm_output(
chunkify=chunkify, chunkify=chunkify,
account=source_account, account=source_account,
account_path=source_account_path, account_path=source_account_path,
br_code=br_code,
br_type="confirm_output",
) )
),
"confirm_output",
br_code,
) )
) )
@ -877,17 +913,15 @@ async def confirm_total(
fee_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) fee_items.append((TR.confirm_total__fee_rate, fee_rate_amount))
await raise_if_not_confirmed( await raise_if_not_confirmed(
interact(
RustLayout( RustLayout(
trezorui2.flow_confirm_summary( trezorui2.flow_confirm_summary(
title=title, title=title,
items=items, items=items,
fee_items=fee_items, fee_items=fee_items,
account_items=account_items, account_items=account_items,
br_type=br_type,
br_code=br_code,
) )
),
br_type,
br_code,
) )
) )
@ -910,10 +944,10 @@ async def confirm_summary(
items=items or (), items=items or (),
fee_items=(), fee_items=(),
account_items=info_items or (), account_items=info_items or (),
br_type=br_type,
br_code=br_code,
) )
) )
# TODO br_type,
# TODO br_code,
) )
@ -1109,7 +1143,7 @@ async def confirm_modify_output(
address_layout.page_count(), address_layout.page_count(),
) )
address_layout.request_complete_repaint() address_layout.request_complete_repaint()
await raise_if_not_confirmed(ctx_wait(address_layout)) await raise_if_not_confirmed(address_layout)
if send_button_request: if send_button_request:
send_button_request = False send_button_request = False
@ -1119,7 +1153,7 @@ async def confirm_modify_output(
modify_layout.page_count(), modify_layout.page_count(),
) )
modify_layout.request_complete_repaint() modify_layout.request_complete_repaint()
result = await ctx_wait(modify_layout) result = await modify_layout
if result is CONFIRMED: if result is CONFIRMED:
break break
@ -1134,11 +1168,11 @@ async def with_info(
await button_request(br_type, br_code, pages=main_layout.page_count()) await button_request(br_type, br_code, pages=main_layout.page_count())
while True: while True:
result = await ctx_wait(main_layout) result = await main_layout
if result is INFO: if result is INFO:
info_layout.request_complete_repaint() info_layout.request_complete_repaint()
result = await ctx_wait(info_layout) result = await info_layout
assert result is CANCELLED assert result is CANCELLED
main_layout.request_complete_repaint() main_layout.request_complete_repaint()
continue continue
@ -1266,8 +1300,8 @@ async def confirm_signverify(
address_layout, info_layout, br_type, br_code=BR_TYPE_OTHER address_layout, info_layout, br_type, br_code=BR_TYPE_OTHER
) )
if result is not CONFIRMED: if result is not CONFIRMED:
result = await ctx_wait( result = await RustLayout(
RustLayout(trezorui2.show_mismatch(title=TR.addr_mismatch__mismatch)) trezorui2.show_mismatch(title=TR.addr_mismatch__mismatch)
) )
assert result in (CONFIRMED, CANCELLED) assert result in (CONFIRMED, CANCELLED)
# Right button aborts action, left goes back to showing address. # Right button aborts action, left goes back to showing address.

View File

@ -3,7 +3,6 @@ from typing import Callable, Iterable
import trezorui2 import trezorui2
from trezor import TR from trezor import TR
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.wire.context import wait as ctx_wait
from ..common import interact from ..common import interact
from . import RustLayout, raise_if_not_confirmed from . import RustLayout, raise_if_not_confirmed
@ -17,7 +16,7 @@ async def _is_confirmed_info(
info_func: Callable, info_func: Callable,
) -> bool: ) -> bool:
while True: while True:
result = await ctx_wait(dialog) result = await dialog
if result is trezorui2.INFO: if result is trezorui2.INFO:
await info_func() await info_func()
@ -50,7 +49,7 @@ async def request_word(
) )
) )
word: str = await ctx_wait(keyboard) word: str = await keyboard
return word return word
@ -143,7 +142,7 @@ async def continue_recovery(
if info_func is not None: if info_func is not None:
return await _is_confirmed_info(homepage, info_func) return await _is_confirmed_info(homepage, info_func)
else: else:
result = await ctx_wait(homepage) result = await homepage
return result is CONFIRMED return result is CONFIRMED

View File

@ -4,7 +4,6 @@ import trezorui2
from trezor import TR from trezor import TR
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.wire import ActionCancelled from trezor.wire import ActionCancelled
from trezor.wire.context import wait as ctx_wait
from ..common import interact from ..common import interact
from . import RustLayout, raise_if_not_confirmed from . import RustLayout, raise_if_not_confirmed
@ -76,8 +75,7 @@ async def select_word(
while len(words) < 3: while len(words) < 3:
words.append(words[-1]) words.append(words[-1])
result = await ctx_wait( result = await RustLayout(
RustLayout(
trezorui2.select_word( trezorui2.select_word(
title=TR.reset__select_word_x_of_y_template.format( title=TR.reset__select_word_x_of_y_template.format(
checked_index + 1, count checked_index + 1, count
@ -86,7 +84,6 @@ async def select_word(
words=(words[0], words[1], words[2]), words=(words[0], words[1], words[2]),
) )
) )
)
if __debug__ and isinstance(result, str): if __debug__ and isinstance(result, str):
return result return result
assert isinstance(result, int) and 0 <= result <= 2 assert isinstance(result, int) and 0 <= result <= 2
@ -164,15 +161,13 @@ async def _prompt_number(
assert isinstance(value, int) assert isinstance(value, int)
return value return value
await ctx_wait( await RustLayout(
RustLayout(
trezorui2.show_simple( trezorui2.show_simple(
title=None, title=None,
description=info(value), description=info(value),
button=TR.buttons__ok_i_understand, button=TR.buttons__ok_i_understand,
) )
) )
)
num_input.request_complete_repaint() num_input.request_complete_repaint()