diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index e042e1e74c..f1e8523a25 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -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; diff --git a/core/embed/rust/src/ui/component/button_request.rs b/core/embed/rust/src/ui/component/button_request.rs index 8abb392f39..006a158793 100644 --- a/core/embed/rust/src/ui/component/button_request.rs +++ b/core/embed/rust/src/ui/component/button_request.rs @@ -63,3 +63,21 @@ pub trait ButtonRequestExt { } impl ButtonRequestExt for T {} + +#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))] +impl crate::ui::flow::Swipable for OneButtonRequest +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() + } +} diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs index 9e55a15978..d5b40aa91a 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs @@ -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 = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; let account_path: 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 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( diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs index 2b465394cd..83f5012980 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs @@ -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( diff --git a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs index d598cd378a..60480f8c71 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs @@ -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 = 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()) diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 9cbf6bfbd5..c101794cf3 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -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(), diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 94e8f6b6f0..4f2df01ec6 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -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 diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index c150b7fff1..67812965a6 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -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. diff --git a/core/src/trezor/ui/layouts/mercury/recovery.py b/core/src/trezor/ui/layouts/mercury/recovery.py index fd61afaecc..6b2641fd7b 100644 --- a/core/src/trezor/ui/layouts/mercury/recovery.py +++ b/core/src/trezor/ui/layouts/mercury/recovery.py @@ -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 diff --git a/core/src/trezor/ui/layouts/mercury/reset.py b/core/src/trezor/ui/layouts/mercury/reset.py index eb7cc062e2..0eade07084 100644 --- a/core/src/trezor/ui/layouts/mercury/reset.py +++ b/core/src/trezor/ui/layouts/mercury/reset.py @@ -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()