1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 12:28: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_bootscreen;
MP_QSTR_bounds;
MP_QSTR_br_code;
MP_QSTR_br_type;
MP_QSTR_button;
MP_QSTR_button_event;
MP_QSTR_button_request;

View File

@ -63,3 +63,21 @@ pub trait ButtonRequestExt {
}
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,
translations::TR,
ui::{
component::{ComponentExt, SwipeDirection},
button_request::ButtonRequest,
component::{ButtonRequestExt, ComponentExt, SwipeDirection},
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_path: Option<TString> =
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 amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?;
@ -105,8 +108,8 @@ impl ConfirmOutput {
.with_footer(TR::instructions__swipe_up.into(), None)
.with_chunkify(chunkify)
.with_text_mono(text_mono)
.into_layout()?;
// .one_button_request(ButtonRequestCode::ConfirmOutput, br_type);
.into_layout()?
.one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Amount
let content_amount = ConfirmBlobParams::new(TR::words__amount.into(), amount, None)
@ -114,8 +117,8 @@ impl ConfirmOutput {
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_text_mono(text_mono)
.into_layout()?;
// .one_button_request(ButtonRequestCode::ConfirmOutput, br_type);
.into_layout()?
.one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Menu
let content_menu = Frame::left_aligned(

View File

@ -4,7 +4,8 @@ use crate::{
strutil::TString,
translations::TR,
ui::{
component::{ComponentExt, SwipeDirection},
button_request::ButtonRequest,
component::{ButtonRequestExt, ComponentExt, SwipeDirection},
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 account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_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
let mut summary = ShowInfoParams::new(title)
@ -100,7 +103,9 @@ impl ConfirmSummary {
let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
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
let content_hold = Frame::left_aligned(

View File

@ -4,9 +4,10 @@ use crate::{
strutil::TString,
translations::TR,
ui::{
button_request::ButtonRequest,
component::{
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
ComponentExt, Qr, SwipeDirection,
ButtonRequestExt, ComponentExt, Qr, SwipeDirection,
},
flow::{
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 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
let data_style = if chunkify {
let address: TString = address.try_into()?;
@ -166,8 +170,8 @@ impl GetAddress {
let content_address = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
// .one_button_request(ButtonRequestCode::Address, "show_address");
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
.one_button_request(ButtonRequest::from_tstring(br_code, br_type));
// Tap
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,
/// path: str | None,
/// xpubs: list[tuple[str, str]],
/// br_code: ButtonRequestType,
/// br_type: str,
/// ) -> LayoutObj[UiResult]:
/// """Get address / receive funds."""
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,
/// account: str | None,
/// account_path: str | None,
/// br_code: ButtonRequestType,
/// br_type: str,
/// ) -> LayoutObj[UiResult]:
/// """Confirm recipient."""
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]],
/// account_items: Iterable[tuple[str, str]],
/// fee_items: Iterable[tuple[str, str]],
/// br_code: ButtonRequestType,
/// br_type: str,
/// ) -> LayoutObj[UiResult]:
/// """Total summary and hold to confirm."""
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,
path: str | None,
xpubs: list[tuple[str, str]],
br_code: ButtonRequestType,
br_type: str,
) -> LayoutObj[UiResult]:
"""Get address / receive funds."""
@ -560,6 +562,8 @@ def flow_confirm_output(
chunkify: bool,
account: str | None,
account_path: str | None,
br_code: ButtonRequestType,
br_type: str,
) -> LayoutObj[UiResult]:
"""Confirm recipient."""
@ -571,6 +575,8 @@ def flow_confirm_summary(
items: Iterable[tuple[str, str]],
account_items: Iterable[tuple[str, str]],
fee_items: Iterable[tuple[str, str]],
br_code: ButtonRequestType,
br_type: str,
) -> LayoutObj[UiResult]:
"""Total summary and hold to confirm."""
CONFIRMED: UiResult

View File

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

View File

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

View File

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