From baccbcfd19bf9bd4515cf5dfb6989fa776a672c9 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Sun, 12 May 2024 20:37:14 +0200 Subject: [PATCH 1/5] feat(core/ui): T3T1 send flow [no changelog] --- core/embed/rust/librust_qstr.h | 13 +- .../generated/translated_string.rs | 43 ++-- .../src/ui/model_mercury/component/frame.rs | 1 + .../ui/model_mercury/flow/confirm_output.rs | 158 ++++++++++++ .../ui/model_mercury/flow/confirm_summary.rs | 169 +++++++++++++ .../rust/src/ui/model_mercury/flow/mod.rs | 6 + .../rust/src/ui/model_mercury/flow/util.rs | 236 ++++++++++++++++++ .../embed/rust/src/ui/model_mercury/layout.rs | 26 +- core/embed/rust/src/ui/model_tr/layout.rs | 2 +- core/mocks/generated/trezorui2.pyi | 24 ++ core/mocks/trezortranslate_keys.pyi | 14 +- core/src/apps/bitcoin/sign_tx/approvers.py | 6 +- core/src/apps/bitcoin/sign_tx/bitcoin.py | 4 +- core/src/apps/bitcoin/sign_tx/decred.py | 4 +- core/src/apps/bitcoin/sign_tx/helpers.py | 11 +- core/src/apps/bitcoin/sign_tx/layout.py | 6 +- core/src/apps/eos/layout.py | 2 +- .../src/trezor/ui/layouts/mercury/__init__.py | 152 +++++------ core/src/trezor/ui/layouts/tr/__init__.py | 9 +- core/src/trezor/ui/layouts/tt/__init__.py | 13 +- core/tests/test_apps.bitcoin.approver.py | 5 +- ...pps.bitcoin.segwit.signtx.native_p2wpkh.py | 6 +- ...bitcoin.segwit.signtx.native_p2wpkh_grs.py | 6 +- ...ps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py | 8 +- ...itcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py | 6 +- .../test_apps.bitcoin.signtx.fee_threshold.py | 2 +- core/tests/test_apps.bitcoin.signtx.py | 2 +- core/tests/test_apps.bitcoin.signtx_decred.py | 2 +- core/tests/test_apps.bitcoin.signtx_grs.py | 2 +- core/tools/translations/rules.json | 6 +- core/translations/cs.json | 8 +- core/translations/de.json | 8 +- core/translations/en.json | 16 +- core/translations/es.json | 8 +- core/translations/fr.json | 8 +- core/translations/order.json | 14 +- core/translations/signatures.json | 6 +- 37 files changed, 843 insertions(+), 169 deletions(-) create mode 100644 core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs create mode 100644 core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs create mode 100644 core/embed/rust/src/ui/model_mercury/flow/util.rs diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index dd1b85437b8..6f0e76e58a9 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -19,7 +19,9 @@ static void _librust_qstrs(void) { MP_QSTR___dict__; MP_QSTR___name__; MP_QSTR_account; + MP_QSTR_account_items; MP_QSTR_account_label; + MP_QSTR_account_path; MP_QSTR_accounts; MP_QSTR_action; MP_QSTR_active; @@ -174,6 +176,7 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_reset_device; MP_QSTR_confirm_total; MP_QSTR_confirm_total__fee_rate; + MP_QSTR_confirm_total__fee_rate_colon; MP_QSTR_confirm_total__sending_from_account; MP_QSTR_confirm_total__title_fee; MP_QSTR_confirm_total__title_sending_from; @@ -204,6 +207,7 @@ static void _librust_qstrs(void) { MP_QSTR_experimental_mode__title; MP_QSTR_extra; MP_QSTR_fee_amount; + MP_QSTR_fee_items; MP_QSTR_fee_label; MP_QSTR_fee_rate_amount; MP_QSTR_fee_title; @@ -211,9 +215,11 @@ static void _librust_qstrs(void) { MP_QSTR_fingerprint; MP_QSTR_firmware_update__title; MP_QSTR_firmware_update__title_fingerprint; + MP_QSTR_flow_confirm_output; MP_QSTR_flow_confirm_reset_create; MP_QSTR_flow_confirm_reset_recover; MP_QSTR_flow_confirm_set_new_pin; + MP_QSTR_flow_confirm_summary; MP_QSTR_flow_get_address; MP_QSTR_flow_prompt_backup; MP_QSTR_flow_show_share_words; @@ -247,6 +253,7 @@ static void _librust_qstrs(void) { MP_QSTR_inputs__space; MP_QSTR_instructions__continue_in_app; MP_QSTR_instructions__hold_to_confirm; + MP_QSTR_instructions__hold_to_sign; MP_QSTR_instructions__swipe_up; MP_QSTR_instructions__tap_to_confirm; MP_QSTR_is_type_of; @@ -520,11 +527,15 @@ static void _librust_qstrs(void) { MP_QSTR_select_word; MP_QSTR_select_word_count; MP_QSTR_send__address_path; + MP_QSTR_send__cancel_sign; MP_QSTR_send__confirm_sending; MP_QSTR_send__from_multiple_accounts; + MP_QSTR_send__incl_transaction_fee; MP_QSTR_send__including_fee; MP_QSTR_send__maximum_fee; MP_QSTR_send__receiving_to_multisig; + MP_QSTR_send__send_from; + MP_QSTR_send__sign_transaction; MP_QSTR_send__title_confirm_sending; MP_QSTR_send__title_joint_transaction; MP_QSTR_send__title_receiving_to; @@ -533,6 +544,7 @@ static void _librust_qstrs(void) { MP_QSTR_send__title_sending_to; MP_QSTR_send__to_the_total_amount; MP_QSTR_send__total_amount; + MP_QSTR_send__total_amount_colon; MP_QSTR_send__transaction_id; MP_QSTR_send__you_are_contributing; MP_QSTR_share_words; @@ -826,7 +838,6 @@ static void _librust_qstrs(void) { MP_QSTR_eos__requirement; MP_QSTR_eos__sell_ram; MP_QSTR_eos__sender; - MP_QSTR_eos__sign_transaction; MP_QSTR_eos__threshold; MP_QSTR_eos__to; MP_QSTR_eos__transfer; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index 8a1da684ef4..82572033242 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -351,9 +351,9 @@ pub enum TranslatedString { coinjoin__title_do_not_disconnect = 217, // "Do not disconnect your trezor!" coinjoin__title_progress = 218, // "Coinjoin in progress" coinjoin__waiting_for_others = 219, // "Waiting for others" - confirm_total__fee_rate = 220, // "Fee rate:" + confirm_total__fee_rate_colon = 220, // "Fee rate:" confirm_total__sending_from_account = 221, // "Sending from account:" - confirm_total__title_fee = 222, // "Fee information" + confirm_total__title_fee = 222, // "Fee info" confirm_total__title_sending_from = 223, // "Sending from" debug__loading_seed = 224, // "Loading seed" debug__loading_seed_not_recommended = 225, // "Loading private seed is not recommended." @@ -420,8 +420,7 @@ pub enum TranslatedString { eos__sell_ram = 258, // "Sell RAM" #[cfg(feature = "universal_fw")] eos__sender = 259, // "Sender:" - #[cfg(feature = "universal_fw")] - eos__sign_transaction = 260, // "Sign transaction" + send__sign_transaction = 260, // "Sign transaction" #[cfg(feature = "universal_fw")] eos__threshold = 261, // "Threshold:" #[cfg(feature = "universal_fw")] @@ -940,7 +939,7 @@ pub enum TranslatedString { send__title_sending_amount = 650, // "Sending amount" send__title_sending_to = 651, // "Sending to" send__to_the_total_amount = 652, // "To the total amount:" - send__total_amount = 653, // "Total amount:" + send__total_amount_colon = 653, // "Total amount:" send__transaction_id = 654, // "Transaction ID:" send__you_are_contributing = 655, // "You are contributing:" share_words__words_in_order = 656, // " words in order." @@ -1259,6 +1258,12 @@ pub enum TranslatedString { pin__cancel_description = 864, // "Continue without PIN" pin__cancel_info = 865, // "Without a PIN, anyone can access this device." pin__cancel_setup = 866, // "Cancel PIN setup" + send__cancel_sign = 867, // "Cancel sign" + send__send_from = 868, // "Send from" + instructions__hold_to_sign = 869, // "Hold to sign" + confirm_total__fee_rate = 870, // "Fee rate" + send__incl_transaction_fee = 871, // "incl. Transaction fee" + send__total_amount = 872, // "Total amount" } impl TranslatedString { @@ -1605,9 +1610,9 @@ impl TranslatedString { Self::coinjoin__title_do_not_disconnect => "Do not disconnect your trezor!", Self::coinjoin__title_progress => "Coinjoin in progress", Self::coinjoin__waiting_for_others => "Waiting for others", - Self::confirm_total__fee_rate => "Fee rate:", + Self::confirm_total__fee_rate_colon => "Fee rate:", Self::confirm_total__sending_from_account => "Sending from account:", - Self::confirm_total__title_fee => "Fee information", + Self::confirm_total__title_fee => "Fee info", Self::confirm_total__title_sending_from => "Sending from", Self::debug__loading_seed => "Loading seed", Self::debug__loading_seed_not_recommended => "Loading private seed is not recommended.", @@ -1674,8 +1679,7 @@ impl TranslatedString { Self::eos__sell_ram => "Sell RAM", #[cfg(feature = "universal_fw")] Self::eos__sender => "Sender:", - #[cfg(feature = "universal_fw")] - Self::eos__sign_transaction => "Sign transaction", + Self::send__sign_transaction => "Sign transaction", #[cfg(feature = "universal_fw")] Self::eos__threshold => "Threshold:", #[cfg(feature = "universal_fw")] @@ -2194,7 +2198,7 @@ impl TranslatedString { Self::send__title_sending_amount => "Sending amount", Self::send__title_sending_to => "Sending to", Self::send__to_the_total_amount => "To the total amount:", - Self::send__total_amount => "Total amount:", + Self::send__total_amount_colon => "Total amount:", Self::send__transaction_id => "Transaction ID:", Self::send__you_are_contributing => "You are contributing:", Self::share_words__words_in_order => " words in order.", @@ -2513,6 +2517,12 @@ impl TranslatedString { Self::pin__cancel_description => "Continue without PIN", Self::pin__cancel_info => "Without a PIN, anyone can access this device.", Self::pin__cancel_setup => "Cancel PIN setup", + Self::send__cancel_sign => "Cancel sign", + Self::send__send_from => "Send from", + Self::instructions__hold_to_sign => "Hold to sign", + Self::confirm_total__fee_rate => "Fee rate", + Self::send__incl_transaction_fee => "incl. Transaction fee", + Self::send__total_amount => "Total amount", } } @@ -2860,7 +2870,7 @@ impl TranslatedString { Qstr::MP_QSTR_coinjoin__title_do_not_disconnect => Some(Self::coinjoin__title_do_not_disconnect), Qstr::MP_QSTR_coinjoin__title_progress => Some(Self::coinjoin__title_progress), Qstr::MP_QSTR_coinjoin__waiting_for_others => Some(Self::coinjoin__waiting_for_others), - Qstr::MP_QSTR_confirm_total__fee_rate => Some(Self::confirm_total__fee_rate), + Qstr::MP_QSTR_confirm_total__fee_rate_colon => Some(Self::confirm_total__fee_rate_colon), Qstr::MP_QSTR_confirm_total__sending_from_account => Some(Self::confirm_total__sending_from_account), Qstr::MP_QSTR_confirm_total__title_fee => Some(Self::confirm_total__title_fee), Qstr::MP_QSTR_confirm_total__title_sending_from => Some(Self::confirm_total__title_sending_from), @@ -2929,8 +2939,7 @@ impl TranslatedString { Qstr::MP_QSTR_eos__sell_ram => Some(Self::eos__sell_ram), #[cfg(feature = "universal_fw")] Qstr::MP_QSTR_eos__sender => Some(Self::eos__sender), - #[cfg(feature = "universal_fw")] - Qstr::MP_QSTR_eos__sign_transaction => Some(Self::eos__sign_transaction), + Qstr::MP_QSTR_send__sign_transaction => Some(Self::send__sign_transaction), #[cfg(feature = "universal_fw")] Qstr::MP_QSTR_eos__threshold => Some(Self::eos__threshold), #[cfg(feature = "universal_fw")] @@ -3449,7 +3458,7 @@ impl TranslatedString { Qstr::MP_QSTR_send__title_sending_amount => Some(Self::send__title_sending_amount), Qstr::MP_QSTR_send__title_sending_to => Some(Self::send__title_sending_to), Qstr::MP_QSTR_send__to_the_total_amount => Some(Self::send__to_the_total_amount), - Qstr::MP_QSTR_send__total_amount => Some(Self::send__total_amount), + Qstr::MP_QSTR_send__total_amount_colon => Some(Self::send__total_amount_colon), Qstr::MP_QSTR_send__transaction_id => Some(Self::send__transaction_id), Qstr::MP_QSTR_send__you_are_contributing => Some(Self::send__you_are_contributing), Qstr::MP_QSTR_share_words__words_in_order => Some(Self::share_words__words_in_order), @@ -3768,6 +3777,12 @@ impl TranslatedString { Qstr::MP_QSTR_pin__cancel_description => Some(Self::pin__cancel_description), Qstr::MP_QSTR_pin__cancel_info => Some(Self::pin__cancel_info), Qstr::MP_QSTR_pin__cancel_setup => Some(Self::pin__cancel_setup), + Qstr::MP_QSTR_send__cancel_sign => Some(Self::send__cancel_sign), + Qstr::MP_QSTR_send__send_from => Some(Self::send__send_from), + Qstr::MP_QSTR_instructions__hold_to_sign => Some(Self::instructions__hold_to_sign), + Qstr::MP_QSTR_confirm_total__fee_rate => Some(Self::confirm_total__fee_rate), + Qstr::MP_QSTR_send__incl_transaction_fee => Some(Self::send__incl_transaction_fee), + Qstr::MP_QSTR_send__total_amount => Some(Self::send__total_amount), _ => None, } } diff --git a/core/embed/rust/src/ui/model_mercury/component/frame.rs b/core/embed/rust/src/ui/model_mercury/component/frame.rs index ba916ffbfdb..68bcce7bf01 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -104,6 +104,7 @@ where pub fn with_warning_button(self) -> Self { self.with_button(theme::ICON_WARNING, CancelInfoConfirmMsg::Info, false) + .button_styled(theme::button_danger()) } pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self { 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 new file mode 100644 index 00000000000..9e55a15978a --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs @@ -0,0 +1,158 @@ +use crate::{ + error, + micropython::qstr::Qstr, + strutil::TString, + translations::TR, + ui::{ + component::{ComponentExt, SwipeDirection}, + flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage}, + }, +}; + +use super::super::{ + component::{ + AddressDetails, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg, + }, + theme, +}; + +use super::util::ConfirmBlobParams; + +#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] +pub enum ConfirmOutput { + Address, + Amount, + // Tap, + Menu, + AccountInfo, + CancelTap, +} + +impl FlowState for ConfirmOutput { + fn handle_swipe(&self, direction: SwipeDirection) -> Decision { + match (self, direction) { + (ConfirmOutput::Address | ConfirmOutput::Amount, SwipeDirection::Left) => { + Decision::Goto(ConfirmOutput::Menu, direction) + } + (ConfirmOutput::Address, SwipeDirection::Up) => { + Decision::Goto(ConfirmOutput::Amount, direction) + } + (ConfirmOutput::Amount, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed), + (ConfirmOutput::Amount, SwipeDirection::Down) => { + Decision::Goto(ConfirmOutput::Address, direction) + } + (ConfirmOutput::Menu, SwipeDirection::Right) => { + Decision::Goto(ConfirmOutput::Address, direction) + } + (ConfirmOutput::Menu, SwipeDirection::Left) => { + Decision::Goto(ConfirmOutput::AccountInfo, direction) + } + (ConfirmOutput::AccountInfo | ConfirmOutput::CancelTap, SwipeDirection::Right) => { + Decision::Goto(ConfirmOutput::Menu, direction) + } + _ => Decision::Nothing, + } + } + + fn handle_event(&self, msg: FlowMsg) -> Decision { + match (self, msg) { + (_, FlowMsg::Info) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Left), + (ConfirmOutput::Menu, FlowMsg::Choice(0)) => { + Decision::Goto(ConfirmOutput::AccountInfo, SwipeDirection::Left) + } + (ConfirmOutput::Menu, FlowMsg::Choice(1)) => { + Decision::Goto(ConfirmOutput::CancelTap, SwipeDirection::Left) + } + (ConfirmOutput::Menu, FlowMsg::Cancelled) => { + Decision::Goto(ConfirmOutput::Address, SwipeDirection::Right) + } + (ConfirmOutput::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled), + (_, FlowMsg::Cancelled) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Right), + _ => Decision::Nothing, + } + } +} + +use crate::{ + micropython::{map::Map, obj::Obj, util}, + ui::layout::obj::LayoutObj, +}; + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmOutput::new_obj) } +} + +impl ConfirmOutput { + const EXTRA_PADDING: i16 = 6; + + fn new_obj(_args: &[Obj], kwargs: &Map) -> Result { + let title: Option = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?; + 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 address: Obj = kwargs.get(Qstr::MP_QSTR_address)?; + let amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?; + + let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; + + // Address + let content_address = ConfirmBlobParams::new(TR::words__address.into(), address, None) + .with_subtitle(title) + .with_menu_button() + .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); + + // Amount + let content_amount = ConfirmBlobParams::new(TR::words__amount.into(), amount, None) + .with_subtitle(title) + .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); + + // Menu + let content_menu = Frame::left_aligned( + "".into(), + VerticalMenu::empty() + .item(theme::ICON_CHEVRON_RIGHT, "Account info".into()) + .danger(theme::ICON_CANCEL, "Cancel sign".into()), + ) + .with_cancel_button() + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + // AccountInfo + let ad = AddressDetails::new(TR::send__send_from.into(), account, account_path)?; + let content_account = SwipePage::horizontal(ad).map(|_| Some(FlowMsg::Cancelled)); + + // CancelTap + let content_cancel_tap = Frame::left_aligned( + TR::send__cancel_sign.into(), + PromptScreen::new_tap_to_cancel(), + ) + .with_cancel_button() + .with_footer(TR::instructions__tap_to_confirm.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + let store = flow_store() + .add(content_address)? + .add(content_amount)? + .add(content_menu)? + .add(content_account)? + .add(content_cancel_tap)?; + let res = SwipeFlow::new(ConfirmOutput::Address, store)?; + Ok(LayoutObj::new(res)?.into()) + } +} 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 new file mode 100644 index 00000000000..2b465394cd3 --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs @@ -0,0 +1,169 @@ +use crate::{ + error, + micropython::{iter::IterBuf, qstr::Qstr}, + strutil::TString, + translations::TR, + ui::{ + component::{ComponentExt, SwipeDirection}, + flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, + }, +}; + +use super::super::{ + component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg}, + theme, +}; + +use super::util::ShowInfoParams; + +#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] +pub enum ConfirmSummary { + Summary, + Hold, + Menu, + FeeInfo, + AccountInfo, + CancelTap, +} + +impl FlowState for ConfirmSummary { + fn handle_swipe(&self, direction: SwipeDirection) -> Decision { + match (self, direction) { + (ConfirmSummary::Summary | ConfirmSummary::Hold, SwipeDirection::Left) => { + Decision::Goto(ConfirmSummary::Menu, direction) + } + (ConfirmSummary::Summary, SwipeDirection::Up) => { + Decision::Goto(ConfirmSummary::Hold, direction) + } + (ConfirmSummary::Hold, SwipeDirection::Down) => { + Decision::Goto(ConfirmSummary::Summary, direction) + } + (ConfirmSummary::Menu, SwipeDirection::Right) => { + Decision::Goto(ConfirmSummary::Summary, direction) + } + (ConfirmSummary::Menu, SwipeDirection::Left) => { + Decision::Goto(ConfirmSummary::FeeInfo, direction) + } + ( + ConfirmSummary::AccountInfo | ConfirmSummary::FeeInfo | ConfirmSummary::CancelTap, + SwipeDirection::Right, + ) => Decision::Goto(ConfirmSummary::Menu, direction), + _ => Decision::Nothing, + } + } + + fn handle_event(&self, msg: FlowMsg) -> Decision { + match (self, msg) { + (_, FlowMsg::Info) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Left), + (ConfirmSummary::Hold, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed), + (ConfirmSummary::Menu, FlowMsg::Choice(0)) => { + Decision::Goto(ConfirmSummary::FeeInfo, SwipeDirection::Left) + } + (ConfirmSummary::Menu, FlowMsg::Choice(1)) => { + Decision::Goto(ConfirmSummary::AccountInfo, SwipeDirection::Left) + } + (ConfirmSummary::Menu, FlowMsg::Choice(2)) => { + Decision::Goto(ConfirmSummary::CancelTap, SwipeDirection::Left) + } + (ConfirmSummary::Menu, FlowMsg::Cancelled) => { + Decision::Goto(ConfirmSummary::Summary, SwipeDirection::Right) + } + (ConfirmSummary::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled), + (_, FlowMsg::Cancelled) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Right), + _ => Decision::Nothing, + } + } +} + +use crate::{ + micropython::{map::Map, obj::Obj, util}, + ui::layout::obj::LayoutObj, +}; + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmSummary::new_obj) } +} + +impl ConfirmSummary { + fn new_obj(_args: &[Obj], kwargs: &Map) -> Result { + let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + 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)?; + + // Summary + let mut summary = ShowInfoParams::new(title) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None); + for pair in IterBuf::new().try_iterate(items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + summary = unwrap!(summary.add(label, value)); + } + let content_summary = summary.into_layout()?; + + // Hold to confirm + let content_hold = Frame::left_aligned( + TR::send__sign_transaction.into(), + PromptScreen::new_hold_to_confirm(), + ) + .with_menu_button() + .with_footer(TR::instructions__hold_to_sign.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Info), + }); + + // Menu + let content_menu = Frame::left_aligned( + "".into(), + VerticalMenu::empty() + .item(theme::ICON_CHEVRON_RIGHT, "Fee info".into()) + .item(theme::ICON_CHEVRON_RIGHT, "Account info".into()) + .danger(theme::ICON_CANCEL, "Cancel sign".into()), + ) + .with_cancel_button() + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + // FeeInfo + let mut fee = ShowInfoParams::new(TR::confirm_total__title_fee.into()).with_cancel_button(); + for pair in IterBuf::new().try_iterate(fee_items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + fee = unwrap!(fee.add(label, value)); + } + let content_fee = fee.into_layout()?; + + // AccountInfo + let mut account = ShowInfoParams::new(TR::send__send_from.into()).with_cancel_button(); + for pair in IterBuf::new().try_iterate(account_items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + account = unwrap!(account.add(label, value)); + } + let content_account = account.into_layout()?; + + // CancelTap + let content_cancel_tap = Frame::left_aligned( + TR::send__cancel_sign.into(), + PromptScreen::new_tap_to_cancel(), + ) + .with_cancel_button() + .with_footer(TR::instructions__tap_to_confirm.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + let store = flow_store() + .add(content_summary)? + .add(content_hold)? + .add(content_menu)? + .add(content_fee)? + .add(content_account)? + .add(content_cancel_tap)?; + let res = SwipeFlow::new(ConfirmSummary::Summary, store)?; + Ok(LayoutObj::new(res)?.into()) + } +} diff --git a/core/embed/rust/src/ui/model_mercury/flow/mod.rs b/core/embed/rust/src/ui/model_mercury/flow/mod.rs index cf7533821bc..ba950ea663c 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/mod.rs @@ -1,16 +1,22 @@ pub mod confirm_action; +pub mod confirm_output; pub mod confirm_reset_create; pub mod confirm_reset_recover; pub mod confirm_set_new_pin; +pub mod confirm_summary; pub mod get_address; pub mod prompt_backup; pub mod show_share_words; pub mod warning_hi_prio; pub use confirm_action::new_confirm_action; +mod util; + +pub use confirm_output::new_confirm_output; pub use confirm_reset_create::ConfirmResetCreate; pub use confirm_reset_recover::ConfirmResetRecover; pub use confirm_set_new_pin::SetNewPin; +pub use confirm_summary::new_confirm_summary; pub use get_address::GetAddress; pub use prompt_backup::PromptBackup; pub use show_share_words::ShowShareWords; diff --git a/core/embed/rust/src/ui/model_mercury/flow/util.rs b/core/embed/rust/src/ui/model_mercury/flow/util.rs new file mode 100644 index 00000000000..41c41fa7bc7 --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/util.rs @@ -0,0 +1,236 @@ +use super::super::{ + component::{Frame, FrameMsg}, + theme, +}; +use crate::{ + error::Error, + maybe_trace::MaybeTrace, + micropython::obj::Obj, + strutil::TString, + ui::{ + component::{ + base::ComponentExt, + text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt}, + Component, + }, + flow::{FlowMsg, Swipable, SwipePage}, + layout::util::ConfirmBlob, + }, +}; +use heapless::Vec; + +pub struct ConfirmBlobParams { + title: TString<'static>, + subtitle: Option>, + footer_instruction: Option>, + footer_description: Option>, + data: Obj, + description: Option>, + extra: Option>, + menu_button: bool, + chunkify: bool, + text_mono: bool, +} + +impl ConfirmBlobParams { + pub const fn new( + title: TString<'static>, + data: Obj, + description: Option>, + ) -> Self { + Self { + title, + subtitle: None, + footer_instruction: None, + footer_description: None, + data, + description, + extra: None, + menu_button: false, + chunkify: false, + text_mono: true, + } + } + + pub const fn with_extra(mut self, extra: Option>) -> Self { + self.extra = extra; + self + } + + pub const fn with_subtitle(mut self, subtitle: Option>) -> Self { + self.subtitle = subtitle; + self + } + + pub const fn with_menu_button(mut self) -> Self { + self.menu_button = true; + self + } + + pub const fn with_footer( + mut self, + instruction: TString<'static>, + description: Option>, + ) -> Self { + self.footer_instruction = Some(instruction); + self.footer_description = description; + self + } + + pub const fn with_chunkify(mut self, chunkify: bool) -> Self { + self.chunkify = chunkify; + self + } + + pub const fn with_text_mono(mut self, text_mono: bool) -> Self { + self.text_mono = text_mono; + self + } + + pub fn into_layout( + self, + ) -> Result + Swipable + MaybeTrace, Error> { + let paragraphs = ConfirmBlob { + description: self.description.unwrap_or("".into()), + extra: self.extra.unwrap_or("".into()), + data: self.data.try_into()?, + description_font: &theme::TEXT_NORMAL, + extra_font: &theme::TEXT_DEMIBOLD, + data_font: if self.chunkify { + let data: TString = self.data.try_into()?; + theme::get_chunkified_text_style(data.len()) + } else if self.text_mono { + &theme::TEXT_MONO + } else { + &theme::TEXT_NORMAL + }, + } + .into_paragraphs(); + + let page = SwipePage::vertical(paragraphs); + let mut frame = Frame::left_aligned(self.title, page); + if let Some(subtitle) = self.subtitle { + frame = frame.with_subtitle(subtitle); + } + if self.menu_button { + frame = frame.with_menu_button(); + } + if let Some(instruction) = self.footer_instruction { + frame = frame.with_footer(instruction, self.footer_description); + } + Ok(frame.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))) + } +} + +pub struct ShowInfoParams { + title: TString<'static>, + subtitle: Option>, + menu_button: bool, + cancel_button: bool, + footer_instruction: Option>, + footer_description: Option>, + chunkify: bool, + items: Vec<(TString<'static>, TString<'static>), 4>, +} + +impl ShowInfoParams { + pub const fn new(title: TString<'static>) -> Self { + Self { + title, + subtitle: None, + menu_button: false, + cancel_button: false, + footer_instruction: None, + footer_description: None, + chunkify: false, + items: Vec::new(), + } + } + + pub fn add(mut self, key: TString<'static>, value: TString<'static>) -> Option { + if self.items.push((key, value)).is_ok() { + Some(self) + } else { + None + } + } + + pub const fn with_subtitle(mut self, subtitle: Option>) -> Self { + self.subtitle = subtitle; + self + } + + pub const fn with_menu_button(mut self) -> Self { + self.menu_button = true; + self + } + + pub const fn with_cancel_button(mut self) -> Self { + self.cancel_button = true; + self + } + + pub const fn with_footer( + mut self, + instruction: TString<'static>, + description: Option>, + ) -> Self { + self.footer_instruction = Some(instruction); + self.footer_description = description; + self + } + + pub const fn with_chunkify(mut self, chunkify: bool) -> Self { + self.chunkify = chunkify; + self + } + + pub fn into_layout( + self, + ) -> Result + Swipable + MaybeTrace, Error> { + let mut paragraphs = ParagraphVecShort::new(); + let mut first: bool = true; + for item in self.items { + // FIXME: padding: + if !first { + paragraphs.add(Paragraph::new::>( + &theme::TEXT_SUB_GREY, + " ".into(), + )); + } + first = false; + paragraphs.add(Paragraph::new(&theme::TEXT_SUB_GREY, item.0).no_break()); + if self.chunkify { + paragraphs.add(Paragraph::new( + theme::get_chunkified_text_style(item.1.len()), + item.1, + )); + } else { + paragraphs.add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, item.1)); + } + } + + let mut frame = Frame::left_aligned( + self.title, + SwipePage::vertical(paragraphs.into_paragraphs()), + ); + if let Some(subtitle) = self.subtitle { + frame = frame.with_subtitle(subtitle); + } + if self.cancel_button { + frame = frame.with_cancel_button(); + } else if self.menu_button { + frame = frame.with_menu_button(); + } + if let Some(instruction) = self.footer_instruction { + frame = frame.with_footer(instruction, self.footer_description); + } + Ok(frame.map(move |msg| { + matches!(msg, FrameMsg::Button(_)).then_some(if self.cancel_button { + FlowMsg::Cancelled + } else { + FlowMsg::Info + }) + })) + } +} diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 5e498151f02..9cbf6bfbd56 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -893,6 +893,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; + let action: Option = kwargs.get(Qstr::MP_QSTR_button)?.try_into_option()?; let content = SwipeUpScreen::new(Paragraphs::new([ Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description), @@ -901,8 +902,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map let obj = LayoutObj::new( Frame::left_aligned(title, content) .with_warning_button() - .button_styled(theme::button_warning_low()) - .with_footer(TR::instructions__swipe_up.into(), None), + .with_footer(TR::instructions__swipe_up.into(), action), )?; Ok(obj.into()) }; @@ -1992,6 +1992,28 @@ pub static mp_module_trezorui2: Module = obj_module! { /// ) -> LayoutObj[UiResult]: /// """Warning modal with multiple steps to confirm.""" Qstr::MP_QSTR_flow_warning_hi_prio => obj_fn_kw!(0, flow::warning_hi_prio::new_warning_hi_prio).as_obj(), + + /// def flow_confirm_output( + /// *, + /// title: str | None, + /// address: str, + /// amount: str, + /// chunkify: bool, + /// account: str | None, + /// account_path: str | None, + /// ) -> LayoutObj[UiResult]: + /// """Confirm recipient.""" + Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, flow::new_confirm_output).as_obj(), + + /// def flow_confirm_summary( + /// *, + /// title: str, + /// items: Iterable[tuple[str, str]], + /// account_items: Iterable[tuple[str, str]], + /// fee_items: Iterable[tuple[str, 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(), }; #[cfg(test)] diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 36e2095237f..57248d90d13 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -658,7 +658,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma .newline() .newline() .newline_half() - .text_bold(TR::confirm_total__fee_rate) + .text_bold(TR::confirm_total__fee_rate_colon) .newline() .text_mono(fee_rate_amount); diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 99dbc0c204e..e26546dc929 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -549,6 +549,30 @@ def flow_warning_hi_prio( value: str = "", ) -> LayoutObj[UiResult]: """Warning modal with multiple steps to confirm.""" + + +# rust/src/ui/model_mercury/layout.rs +def flow_confirm_output( + *, + title: str | None, + address: str, + amount: str, + chunkify: bool, + account: str | None, + account_path: str | None, +) -> LayoutObj[UiResult]: + """Confirm recipient.""" + + +# rust/src/ui/model_mercury/layout.rs +def flow_confirm_summary( + *, + title: str, + items: Iterable[tuple[str, str]], + account_items: Iterable[tuple[str, str]], + fee_items: Iterable[tuple[str, str]], +) -> LayoutObj[UiResult]: + """Total summary and hold to confirm.""" CONFIRMED: UiResult CANCELLED: UiResult INFO: UiResult diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index f73eff1f548..61dd1eec656 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -229,9 +229,10 @@ class TR: coinjoin__title_do_not_disconnect: str = "Do not disconnect your trezor!" coinjoin__title_progress: str = "Coinjoin in progress" coinjoin__waiting_for_others: str = "Waiting for others" - confirm_total__fee_rate: str = "Fee rate:" + confirm_total__fee_rate: str = "Fee rate" + confirm_total__fee_rate_colon: str = "Fee rate:" confirm_total__sending_from_account: str = "Sending from account:" - confirm_total__title_fee: str = "Fee information" + confirm_total__title_fee: str = "Fee info" confirm_total__title_sending_from: str = "Sending from" debug__loading_seed: str = "Loading seed" debug__loading_seed_not_recommended: str = "Loading private seed is not recommended." @@ -269,7 +270,6 @@ class TR: eos__requirement: str = "Requirement:" eos__sell_ram: str = "Sell RAM" eos__sender: str = "Sender:" - eos__sign_transaction: str = "Sign transaction" eos__threshold: str = "Threshold:" eos__to: str = "To:" eos__transfer: str = "Transfer:" @@ -358,6 +358,7 @@ class TR: inputs__space: str = "SPACE" instructions__continue_in_app: str = "Continue in the app" instructions__hold_to_confirm: str = "Hold to confirm" + instructions__hold_to_sign: str = "Hold to sign" instructions__swipe_up: str = "Swipe up" instructions__tap_to_confirm: str = "Tap to confirm" joint__title: str = "Joint transaction" @@ -669,11 +670,15 @@ class TR: sd_card__wanna_format: str = "Do you really want to format the SD card?" sd_card__wrong_sd_card: str = "Wrong SD card." send__address_path: str = "address path" + send__cancel_sign: str = "Cancel sign" send__confirm_sending: str = "Sending amount" send__from_multiple_accounts: str = "Sending from multiple accounts." + send__incl_transaction_fee: str = "incl. Transaction fee" send__including_fee: str = "Including fee:" send__maximum_fee: str = "Maximum fee:" send__receiving_to_multisig: str = "Receiving to a multisig address." + send__send_from: str = "Send from" + send__sign_transaction: str = "Sign transaction" send__title_confirm_sending: str = "Confirm sending" send__title_joint_transaction: str = "Joint transaction" send__title_receiving_to: str = "Receiving to" @@ -681,7 +686,8 @@ class TR: send__title_sending_amount: str = "Sending amount" send__title_sending_to: str = "Sending to" send__to_the_total_amount: str = "To the total amount:" - send__total_amount: str = "Total amount:" + send__total_amount: str = "Total amount" + send__total_amount_colon: str = "Total amount:" send__transaction_id: str = "Transaction ID:" send__you_are_contributing: str = "You are contributing:" share_words__words_in_order: str = " words in order." diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index bdbadf80425..38078478baa 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -121,6 +121,7 @@ async def add_external_output( self, txo: TxOutput, script_pubkey: bytes, + tx_info: TxInfo | None, orig_txo: TxOutput | None = None, ) -> None: await self._add_output(txo, script_pubkey) @@ -193,11 +194,12 @@ async def add_external_output( self, txo: TxOutput, script_pubkey: bytes, + tx_info: TxInfo | None, orig_txo: TxOutput | None = None, ) -> None: from trezor.enums import OutputScriptType - await super().add_external_output(txo, script_pubkey, orig_txo) + await super().add_external_output(txo, script_pubkey, tx_info, orig_txo) if orig_txo: if txo.amount < orig_txo.amount: @@ -230,6 +232,7 @@ async def add_external_output( "Adding new OP_RETURN outputs in replacement transactions is not supported." ) elif txo.payment_req_index is None or self.show_payment_req_details: + source_path = tx_info.wallet_path.get_path() if tx_info else None # Ask user to confirm output, unless it is part of a payment # request, which gets confirmed separately. await helpers.confirm_output( @@ -238,6 +241,7 @@ async def add_external_output( self.amount_unit, self.external_output_index, self.chunkify, + source_path, ) self.external_output_index += 1 diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index ec31da941e0..08c3aea3919 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -530,7 +530,9 @@ async def approve_output( # Output is change and does not need approval. await approver.add_change_output(txo, script_pubkey) else: - await approver.add_external_output(txo, script_pubkey, orig_txo) + await approver.add_external_output( + txo, script_pubkey, self.tx_info, orig_txo + ) self.tx_info.add_output(txo, script_pubkey) diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index 5ba638d99f0..6c842f38ba9 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -92,7 +92,9 @@ async def add_decred_sstx_submission( ) -> None: # NOTE: The following calls Approver.add_external_output(), not BasicApprover.add_external_output(). # This is needed to skip calling helpers.confirm_output(), which is what BasicApprover would do. - await super(BasicApprover, self).add_external_output(txo, script_pubkey, None) + await super(BasicApprover, self).add_external_output( + txo, script_pubkey, None, None + ) await helpers.confirm_decred_sstx_submission(txo, self.coin, self.amount_unit) diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index a27ef12387c..5fa6f0a8a53 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -45,12 +45,14 @@ def __init__( amount_unit: AmountUnit, output_index: int, chunkify: bool, + address_n: Bip32Path | None, ): self.output = output self.coin = coin self.amount_unit = amount_unit self.output_index = output_index self.chunkify = chunkify + self.address_n = address_n def confirm_dialog(self) -> Awaitable[Any]: return layout.confirm_output( @@ -59,6 +61,7 @@ def confirm_dialog(self) -> Awaitable[Any]: self.amount_unit, self.output_index, self.chunkify, + self.address_n, ) @@ -241,8 +244,12 @@ def confirm_dialog(self) -> Awaitable[Any]: return layout.confirm_multiple_accounts() -def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool) -> Awaitable[None]: # type: ignore [awaitable-return-type] - return (yield UiConfirmOutput(output, coin, amount_unit, output_index, chunkify)) # type: ignore [awaitable-return-type] +def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool, address_n: Bip32Path | None) -> Awaitable[None]: # type: ignore [awaitable-return-type] + return ( + yield UiConfirmOutput( # type: ignore [awaitable-return-type] + output, coin, amount_unit, output_index, chunkify, address_n + ) + ) def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-return-type] diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index fdb55141ef4..cf4044cc8a3 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -62,6 +62,7 @@ async def confirm_output( amount_unit: AmountUnit, output_index: int, chunkify: bool, + address_n: Bip32Path | None, ) -> None: from trezor.enums import OutputScriptType @@ -119,6 +120,8 @@ async def confirm_output( address_label=address_label, output_index=output_index, chunkify=chunkify, + source_account=account_label(coin, address_n), + source_account_path=address_n_to_str(address_n) if address_n else None, ) await layout @@ -245,7 +248,8 @@ async def confirm_total( format_coin_amount(spending, coin, amount_unit), format_coin_amount(fee, coin, amount_unit), fee_rate_amount=format_fee_rate(fee_rate, coin) if fee_rate >= 0 else None, - account_label=account_label(coin, address_n), + source_account=account_label(coin, address_n), + source_account_path=address_n_to_str(address_n) if address_n else None, ) diff --git a/core/src/apps/eos/layout.py b/core/src/apps/eos/layout.py index dc12df31c2d..994394f0e8d 100644 --- a/core/src/apps/eos/layout.py +++ b/core/src/apps/eos/layout.py @@ -14,7 +14,7 @@ async def require_sign_tx(num_actions: int) -> None: await confirm_action( "confirm_tx", - TR.eos__sign_transaction, + TR.send__sign_transaction, description=TR.eos__about_to_sign_template, description_param=format_plural( "{count} {plural}", num_actions, TR.plurals__sign_x_actions diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index fc6c3f6e440..c150b7fff12 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -187,7 +187,7 @@ def _first_paint(self) -> None: # Turn the brightness on again. ui.backlight_fade(self.BACKLIGHT_LEVEL) - def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator] + def handle_input_and_rendering(self) -> loop.Task: from trezor import workflow touch = loop.wait(io.TOUCH) @@ -203,7 +203,7 @@ def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is raise ui.Result(msg) self._paint() - def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator] + def handle_timers(self) -> loop.Task: while True: # Using `yield` instead of `await` to avoid allocations. token = yield self.timer @@ -499,9 +499,9 @@ async def show_warning( interact( RustLayout( trezorui2.show_warning( - title=content, - description=subheader or "", - button=button, + title=TR.words__important, + value=content, + button=subheader or TR.words__continue_anyway, ) ), br_type, @@ -539,58 +539,35 @@ async def confirm_output( address_label: str | None = None, output_index: int | None = None, chunkify: bool = False, + source_account: str | None = None, + source_account_path: str | None = None, ) -> None: - if title is not None: - # TODO: handle translation - if title.upper().startswith("CONFIRM "): - title = title[len("CONFIRM ") :] - amount_title = title - recipient_title = title + if address_label is not None: + title = address_label + elif title is not None: + pass elif output_index is not None: - amount_title = f"{TR.words__amount} #{output_index + 1}" - recipient_title = f"{TR.words__recipient} #{output_index + 1}" + title = f"{TR.words__recipient} #{output_index + 1}" else: - amount_title = TR.send__confirm_sending - recipient_title = TR.send__title_sending_to + title = TR.send__title_sending_to - while True: - result = await interact( + # TODO: this should send 2x ButtonRequest + await raise_if_not_confirmed( + interact( RustLayout( - trezorui2.confirm_value( - title=recipient_title, - subtitle=address_label, - description=None, - value=address, - verb=TR.buttons__continue, - hold=False, - info_button=False, + trezorui2.flow_confirm_output( + address=address, + amount=amount, + title=title, chunkify=chunkify, + account=source_account, + account_path=source_account_path, ) ), "confirm_output", br_code, ) - if result is not CONFIRMED: - raise ActionCancelled - - result = await interact( - RustLayout( - trezorui2.confirm_value( - title=amount_title, - subtitle=None, - description=None, - value=amount, - verb=None if hold else TR.buttons__confirm, - verb_cancel="^", - hold=hold, - info_button=False, - ) - ), - "confirm_output", - br_code, - ) - if result is CONFIRMED: - return + ) async def should_show_payment_request_details( @@ -627,7 +604,7 @@ async def should_show_payment_request_details( async def should_show_more( title: str, - para: Iterable[tuple[int, str]], + para: Iterable[tuple[int, str | bytes]], button_text: str | None = None, br_type: str = "should_show_more", br_code: ButtonRequestType = BR_TYPE_OTHER, @@ -876,31 +853,42 @@ async def confirm_total( title: str | None = None, total_label: str | None = None, fee_label: str | None = None, - account_label: str | None = None, + source_account: str | None = None, + source_account_path: str | None = None, fee_rate_amount: str | None = None, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> None: title = title or TR.words__title_summary # def_arg total_label = total_label or TR.send__total_amount # def_arg - fee_label = fee_label or TR.send__including_fee # def_arg + fee_label = fee_label or TR.send__incl_transaction_fee # def_arg items = [ (total_label, total_amount), (fee_label, fee_amount), ] - info_items = [] - if account_label: - info_items.append((TR.confirm_total__sending_from_account, account_label)) + fee_items = [] + account_items = [] + if source_account: + account_items.append((TR.confirm_total__sending_from_account, source_account)) + if source_account_path: + account_items.append((TR.address_details__derivation_path, source_account_path)) if fee_rate_amount: - info_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) + fee_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) - await confirm_summary( - items, - TR.words__title_summary, - info_items=info_items, - br_type=br_type, - br_code=br_code, + 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, + ) ) @@ -912,23 +900,21 @@ async def confirm_summary( br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> None: + # TODO: info_title title = title or TR.words__title_summary # def_arg - total_layout = RustLayout( - trezorui2.confirm_total( - title=title, - items=items, - info_button=bool(info_items), - ) - ) - info_items = info_items or [] - info_layout = RustLayout( - trezorui2.show_info_with_cancel( - title=info_title if info_title else TR.words__title_information, - items=info_items, + await raise_if_not_confirmed( + RustLayout( + trezorui2.flow_confirm_summary( + title=title, + items=items or (), + fee_items=(), + account_items=info_items or (), + ) ) + # TODO br_type, + # TODO br_code, ) - await raise_if_not_confirmed(with_info(total_layout, info_layout, br_type, br_code)) if not utils.BITCOIN_ONLY: @@ -1046,20 +1032,14 @@ async def confirm_solana_tx( async def confirm_joint_total(spending_amount: str, total_amount: str) -> None: - await raise_if_not_confirmed( - interact( - RustLayout( - trezorui2.confirm_total( - title=TR.send__title_joint_transaction, - items=[ - (TR.send__you_are_contributing, spending_amount), - (TR.send__to_the_total_amount, total_amount), - ], - ) - ), - "confirm_joint_total", - ButtonRequestType.SignTx, - ) + await confirm_summary( + items=[ + (TR.send__you_are_contributing, spending_amount), + (TR.send__to_the_total_amount, total_amount), + ], + title=TR.send__title_joint_transaction, + br_type="confirm_joint_total", + br_code=ButtonRequestType.SignTx, ) diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index d8513f02e2f..fa752231737 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -695,6 +695,8 @@ async def confirm_output( address_label: str | None = None, output_index: int | None = None, chunkify: bool = False, + source_account: str | None = None, # ignored on safe 3 + source_account_path: str | None = None, # ignored on safe 3 ) -> None: title = title or TR.send__confirm_sending # def_arg address_title = TR.words__recipient @@ -1059,11 +1061,12 @@ def confirm_total( title: str | None = None, total_label: str | None = None, fee_label: str | None = None, - account_label: str | None = None, + source_account: str | None = None, + source_account_path: str | None = None, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> Awaitable[None]: - total_label = total_label or TR.send__total_amount # def_arg + total_label = total_label or TR.send__total_amount_colon # def_arg fee_label = fee_label or TR.send__including_fee # def_arg return raise_if_not_confirmed( interact( @@ -1073,7 +1076,7 @@ def confirm_total( total_amount=total_amount, # type: ignore [No parameter named] fee_amount=fee_amount, # type: ignore [No parameter named] fee_rate_amount=fee_rate_amount, # type: ignore [No parameter named] - account_label=account_label, # type: ignore [No parameter named] + account_label=source_account, # type: ignore [No parameter named] total_label=total_label, # type: ignore [No parameter named] fee_label=fee_label, # type: ignore [No parameter named] ) diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index ce520e2a655..3c8f5061307 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -604,6 +604,8 @@ async def confirm_output( address_label: str | None = None, output_index: int | None = None, chunkify: bool = False, + source_account: str | None = None, # ignored on model t + source_account_path: str | None = None, # ignored on model t ) -> None: if title is not None: # TODO: handle translation: @@ -941,13 +943,14 @@ def confirm_total( title: str | None = None, total_label: str | None = None, fee_label: str | None = None, - account_label: str | None = None, + source_account: str | None = None, + source_account_path: str | None = None, fee_rate_amount: str | None = None, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> Awaitable[None]: title = title or TR.words__title_summary # def_arg - total_label = total_label or TR.send__total_amount # def_arg + total_label = total_label or TR.send__total_amount_colon # def_arg fee_label = fee_label or TR.send__including_fee # def_arg items = [ @@ -955,10 +958,10 @@ def confirm_total( (fee_label, fee_amount), ] info_items = [] - if account_label: - info_items.append((TR.confirm_total__sending_from_account, account_label)) + if source_account: + info_items.append((TR.confirm_total__sending_from_account, source_account)) if fee_rate_amount: - info_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) + info_items.append((TR.confirm_total__fee_rate_colon, fee_rate_amount)) return confirm_summary( items, diff --git a/core/tests/test_apps.bitcoin.approver.py b/core/tests/test_apps.bitcoin.approver.py index fbbee64d6b5..41a2a98e333 100644 --- a/core/tests/test_apps.bitcoin.approver.py +++ b/core/tests/test_apps.bitcoin.approver.py @@ -175,6 +175,7 @@ def test_coinjoin_lots_of_inputs(self): authorization = CoinJoinAuthorization(self.msg_auth) approver = CoinJoinApprover(tx, self.coin, authorization) signer = Bitcoin(tx, None, self.coin, approver) + tx_info = TxInfo(signer, tx) for txi in inputs: if txi.script_type == InputScriptType.EXTERNAL: @@ -186,9 +187,9 @@ def test_coinjoin_lots_of_inputs(self): if txo.address_n: await_result(approver.add_change_output(txo, script_pubkey=bytes(22))) else: - await_result(approver.add_external_output(txo, script_pubkey=bytes(22))) + await_result(approver.add_external_output(txo, script_pubkey=bytes(22), tx_info=tx_info)) - await_result(approver.approve_tx(TxInfo(signer, tx), [], None)) + await_result(approver.approve_tx(tx_info, [], None)) def test_coinjoin_input_account_depth_mismatch(self): txi = TxInput( diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py index cd58acee78e..c7f0e42cd6b 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py @@ -112,7 +112,7 @@ def test_send_native_p2wpkh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -120,7 +120,7 @@ def test_send_native_p2wpkh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]), True, helpers.UiConfirmTotal( 12300000, 11000, fee_rate, coin, AmountUnit.BITCOIN, inp1.address_n[:3] @@ -309,7 +309,7 @@ def test_send_native_p2wpkh_change(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py index 0294a41f595..6860dd79772 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py @@ -123,7 +123,7 @@ def test_send_native_p2wpkh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(84), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -131,7 +131,7 @@ def test_send_native_p2wpkh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(84), H_(1), H_(0)]), True, helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False), True, @@ -332,7 +332,7 @@ def test_send_native_p2wpkh_change(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(84), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py index 228f4a80111..8e0f5928712 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py @@ -114,7 +114,7 @@ def test_send_p2wpkh_in_p2sh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -122,7 +122,7 @@ def test_send_p2wpkh_in_p2sh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]), True, helpers.UiConfirmTotal( 123445789 + 11000, @@ -317,7 +317,7 @@ def test_send_p2wpkh_in_p2sh_change(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -531,7 +531,7 @@ def test_send_p2wpkh_in_p2sh_attack_amount(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py index 7e45cf8a94e..0abf34cacbb 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py @@ -123,7 +123,7 @@ def test_send_p2wpkh_in_p2sh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -131,7 +131,7 @@ def test_send_p2wpkh_in_p2sh(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]), True, helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False), True, @@ -336,7 +336,7 @@ def test_send_p2wpkh_in_p2sh_change(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.signtx.fee_threshold.py b/core/tests/test_apps.bitcoin.signtx.fee_threshold.py index e5e0320bb43..9aebc7b07ac 100644 --- a/core/tests/test_apps.bitcoin.signtx.fee_threshold.py +++ b/core/tests/test_apps.bitcoin.signtx.fee_threshold.py @@ -180,7 +180,7 @@ def test_under_threshold(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False, None), True, helpers.UiConfirmMultipleAccounts(), True, diff --git a/core/tests/test_apps.bitcoin.signtx.py b/core/tests/test_apps.bitcoin.signtx.py index e9e094696b9..1d8a0339a62 100644 --- a/core/tests/test_apps.bitcoin.signtx.py +++ b/core/tests/test_apps.bitcoin.signtx.py @@ -111,7 +111,7 @@ def test_one_one_fee(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False, [H_(44), H_(0), H_(0)]), True, helpers.UiConfirmTotal( 3_801_747, diff --git a/core/tests/test_apps.bitcoin.signtx_decred.py b/core/tests/test_apps.bitcoin.signtx_decred.py index 001141b0151..0b8b6923f4a 100644 --- a/core/tests/test_apps.bitcoin.signtx_decred.py +++ b/core/tests/test_apps.bitcoin.signtx_decred.py @@ -112,7 +112,7 @@ def test_one_one_fee(self): ), ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0, False, [H_(44), H_(1), H_(0)]), True, helpers.UiConfirmTotal( 200_000_000, diff --git a/core/tests/test_apps.bitcoin.signtx_grs.py b/core/tests/test_apps.bitcoin.signtx_grs.py index 573e3017ff2..8fd6e7cd0d4 100644 --- a/core/tests/test_apps.bitcoin.signtx_grs.py +++ b/core/tests/test_apps.bitcoin.signtx_grs.py @@ -112,7 +112,7 @@ def test_one_one_fee(self): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(44), H_(17), H_(0)]), True, helpers.UiConfirmTotal( 210016, 192, fee_rate, coin, AmountUnit.BITCOIN, inp1.address_n[:3] diff --git a/core/tools/translations/rules.json b/core/tools/translations/rules.json index 5893eebdaad..7b87a61fcd6 100644 --- a/core/tools/translations/rules.json +++ b/core/tools/translations/rules.json @@ -219,7 +219,7 @@ "coinjoin__title_do_not_disconnect": "title,2", "coinjoin__title_progress": "title,1", "coinjoin__waiting_for_others": "text,1", - "confirm_total__fee_rate": "text,1", + "confirm_total__fee_rate_colon": "text,1", "confirm_total__sending_from_account": "text,1", "confirm_total__title_fee": "title,1", "confirm_total__title_sending_from": "title,1", @@ -259,7 +259,7 @@ "eos__requirement": "text,1", "eos__sell_ram": "text,1", "eos__sender": "text,1", - "eos__sign_transaction": "text,1", + "send__sign_transaction": "text,1", "eos__threshold": "text,1", "eos__to": "text,1", "eos__transfer": "text,1", @@ -654,7 +654,7 @@ "send__title_sending_amount": "title,1", "send__title_sending_to": "title,1", "send__to_the_total_amount": "text,1", - "send__total_amount": "text,1", + "send__total_amount_colon": "text,1", "send__transaction_id": "text,1", "send__you_are_contributing": "text,1", "share_words__words_in_order": "text,2", diff --git a/core/translations/cs.json b/core/translations/cs.json index 5e0605513bd..c2beae0e28c 100644 --- a/core/translations/cs.json +++ b/core/translations/cs.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "Neodpojujte trezor!", "coinjoin__title_progress": "Probíhá coinjoin", "coinjoin__waiting_for_others": "Čeká se na ostatní", - "confirm_total__fee_rate": "Výše poplatku:", + "confirm_total__fee_rate": "Výše poplatku", + "confirm_total__fee_rate_colon": "Výše poplatku:", "confirm_total__sending_from_account": "Odeslání z účtu:", "confirm_total__title_fee": "Info o poplatcích", "confirm_total__title_sending_from": "Odeslání z", @@ -297,7 +298,7 @@ "eos__requirement": "Požadavek:", "eos__sell_ram": "Prodat RAM", "eos__sender": "Odesílatel:", - "eos__sign_transaction": "Podepsat transakci", + "send__sign_transaction": "Podepsat transakci", "eos__threshold": "Počet částí zálohy pro obnovu:", "eos__to": "Komu:", "eos__transfer": "Převod:", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Odesílání částky", "send__title_sending_to": "Odesílání", "send__to_the_total_amount": "Do celkové částky:", - "send__total_amount": "Celková částka:", + "send__total_amount": "Celková částka", + "send__total_amount_colon": "Celková částka:", "send__transaction_id": "ID transakce:", "send__you_are_contributing": "Přispíváte:", "share_words__words_in_order": " slova v pořadí.", diff --git a/core/translations/de.json b/core/translations/de.json index 6fca28d28f9..683e5273c5d 100644 --- a/core/translations/de.json +++ b/core/translations/de.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "Trenne deinen trezor nicht!", "coinjoin__title_progress": "Coinjoin läuft", "coinjoin__waiting_for_others": "Auf andere warten", - "confirm_total__fee_rate": "Gebührensatz:", + "confirm_total__fee_rate": "Gebührensatz", + "confirm_total__fee_rate_colon": "Gebührensatz:", "confirm_total__sending_from_account": "Gesendet von Konto:", "confirm_total__title_fee": "Gebühren-info", "confirm_total__title_sending_from": "Gesendet von", @@ -297,7 +298,7 @@ "eos__requirement": "Anforderung:", "eos__sell_ram": "RAM verkaufen", "eos__sender": "Sender:", - "eos__sign_transaction": "Transaktion signieren", + "send__sign_transaction": "Transaktion signieren", "eos__threshold": "Schwelle:", "eos__to": "An:", "eos__transfer": "Überweisung:", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Betrag senden", "send__title_sending_to": "Senden an", "send__to_the_total_amount": "Gesamtbetrag:", - "send__total_amount": "Gesamtbetrag:", + "send__total_amount": "Gesamtbetrag", + "send__total_amount_colon": "Gesamtbetrag:", "send__transaction_id": "Transaktions-ID:", "send__you_are_contributing": "Dein Anteil:", "share_words__words_in_order": " Wörter der Reihe nach notiert.", diff --git a/core/translations/en.json b/core/translations/en.json index 2b49bf81458..b03d847ea8b 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -231,9 +231,10 @@ "coinjoin__title_do_not_disconnect": "Do not disconnect your trezor!", "coinjoin__title_progress": "Coinjoin in progress", "coinjoin__waiting_for_others": "Waiting for others", - "confirm_total__fee_rate": "Fee rate:", + "confirm_total__fee_rate": "Fee rate", + "confirm_total__fee_rate_colon": "Fee rate:", "confirm_total__sending_from_account": "Sending from account:", - "confirm_total__title_fee": "Fee information", + "confirm_total__title_fee": "Fee info", "confirm_total__title_sending_from": "Sending from", "debug__loading_seed": "Loading seed", "debug__loading_seed_not_recommended": "Loading private seed is not recommended.", @@ -271,7 +272,6 @@ "eos__requirement": "Requirement:", "eos__sell_ram": "Sell RAM", "eos__sender": "Sender:", - "eos__sign_transaction": "Sign transaction", "eos__threshold": "Threshold:", "eos__to": "To:", "eos__transfer": "Transfer:", @@ -359,9 +359,10 @@ "inputs__show": "SHOW", "inputs__space": "SPACE", "instructions__continue_in_app": "Continue in the app", + "instructions__hold_to_confirm": "Hold to confirm", + "instructions__hold_to_sign": "Hold to sign", "instructions__swipe_up": "Swipe up", "instructions__tap_to_confirm": "Tap to confirm", - "instructions__hold_to_confirm": "Hold to confirm", "joint__title": "Joint transaction", "joint__to_the_total_amount": "To the total amount:", "joint__you_are_contributing": "You are contributing:", @@ -671,11 +672,15 @@ "sd_card__wanna_format": "Do you really want to format the SD card?", "sd_card__wrong_sd_card": "Wrong SD card.", "send__address_path": "address path", + "send__cancel_sign": "Cancel sign", "send__confirm_sending": "Sending amount", "send__from_multiple_accounts": "Sending from multiple accounts.", + "send__incl_transaction_fee": "incl. Transaction fee", "send__including_fee": "Including fee:", "send__maximum_fee": "Maximum fee:", "send__receiving_to_multisig": "Receiving to a multisig address.", + "send__send_from": "Send from", + "send__sign_transaction": "Sign transaction", "send__title_confirm_sending": "Confirm sending", "send__title_joint_transaction": "Joint transaction", "send__title_receiving_to": "Receiving to", @@ -683,7 +688,8 @@ "send__title_sending_amount": "Sending amount", "send__title_sending_to": "Sending to", "send__to_the_total_amount": "To the total amount:", - "send__total_amount": "Total amount:", + "send__total_amount": "Total amount", + "send__total_amount_colon": "Total amount:", "send__transaction_id": "Transaction ID:", "send__you_are_contributing": "You are contributing:", "share_words__words_in_order": " words in order.", diff --git a/core/translations/es.json b/core/translations/es.json index 4f570432444..fb42195c670 100644 --- a/core/translations/es.json +++ b/core/translations/es.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "¡No desconectes el trezor!", "coinjoin__title_progress": "Coinjoin en curso", "coinjoin__waiting_for_others": "Esperando a los demás", - "confirm_total__fee_rate": "Comisión:", + "confirm_total__fee_rate": "Comisión", + "confirm_total__fee_rate_colon": "Comisión:", "confirm_total__sending_from_account": "Envío desde cuenta:", "confirm_total__title_fee": "Info. comisión", "confirm_total__title_sending_from": "Envío desde", @@ -297,7 +298,7 @@ "eos__requirement": "Requisito:", "eos__sell_ram": "Vender RAM", "eos__sender": "Remitente:", - "eos__sign_transaction": "Firmar transacción", + "send__sign_transaction": "Firmar transacción", "eos__threshold": "Umbral:", "eos__to": "Para:", "eos__transfer": "Transferencia:", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Importe envío", "send__title_sending_to": "Envío a", "send__to_the_total_amount": "Al importe total:", - "send__total_amount": "Importe total:", + "send__total_amount": "Importe total", + "send__total_amount_colon": "Importe total:", "send__transaction_id": "ID de la transacción:", "send__you_are_contributing": "Estás aportando:", "share_words__words_in_order": " palabras en orden.", diff --git a/core/translations/fr.json b/core/translations/fr.json index 2a33588dfcd..8ce8d483e50 100644 --- a/core/translations/fr.json +++ b/core/translations/fr.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "Ne déconnectez pas votre trezor !", "coinjoin__title_progress": "Coinjoin en cours", "coinjoin__waiting_for_others": "En attente des autres", - "confirm_total__fee_rate": "Taux des frais :", + "confirm_total__fee_rate": "Taux des frais", + "confirm_total__fee_rate_colon": "Taux des frais :", "confirm_total__sending_from_account": "Compte d'envoi :", "confirm_total__title_fee": "Infos sur les frais", "confirm_total__title_sending_from": "Envoi depuis", @@ -297,7 +298,7 @@ "eos__requirement": "Exigence :", "eos__sell_ram": "Vendre de la RAM", "eos__sender": "Expéditeur :", - "eos__sign_transaction": "Signer la transaction", + "send__sign_transaction": "Signer la transaction", "eos__threshold": "Seuil :", "eos__to": "À :", "eos__transfer": "Transfert :", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Montant de l'envoi", "send__title_sending_to": "Envoi à", "send__to_the_total_amount": "Au montant total :", - "send__total_amount": "Montant total :", + "send__total_amount": "Montant total", + "send__total_amount_colon": "Montant total :", "send__transaction_id": "ID de transaction :", "send__you_are_contributing": "Votre contribution :", "share_words__words_in_order": " mots dans l'ordre.", diff --git a/core/translations/order.json b/core/translations/order.json index adb73ccb9ac..d9a16b35631 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -219,7 +219,7 @@ "217": "coinjoin__title_do_not_disconnect", "218": "coinjoin__title_progress", "219": "coinjoin__waiting_for_others", - "220": "confirm_total__fee_rate", + "220": "confirm_total__fee_rate_colon", "221": "confirm_total__sending_from_account", "222": "confirm_total__title_fee", "223": "confirm_total__title_sending_from", @@ -259,7 +259,7 @@ "257": "eos__requirement", "258": "eos__sell_ram", "259": "eos__sender", - "260": "eos__sign_transaction", + "260": "send__sign_transaction", "261": "eos__threshold", "262": "eos__to", "263": "eos__transfer", @@ -652,7 +652,7 @@ "650": "send__title_sending_amount", "651": "send__title_sending_to", "652": "send__to_the_total_amount", - "653": "send__total_amount", + "653": "send__total_amount_colon", "654": "send__transaction_id", "655": "send__you_are_contributing", "656": "share_words__words_in_order", @@ -865,5 +865,11 @@ "863": "address__confirmed", "864": "pin__cancel_description", "865": "pin__cancel_info", - "866": "pin__cancel_setup" + "866": "pin__cancel_setup", + "867": "send__cancel_sign", + "868": "send__send_from", + "869": "instructions__hold_to_sign", + "870": "confirm_total__fee_rate", + "871": "send__incl_transaction_fee", + "872": "send__total_amount" } diff --git a/core/translations/signatures.json b/core/translations/signatures.json index 2e93c3afb8a..626bf5fa713 100644 --- a/core/translations/signatures.json +++ b/core/translations/signatures.json @@ -1,8 +1,8 @@ { "current": { - "merkle_root": "928438526b993d433d52359d86099848d570c13fbe6aac72a2f5a29a4e8e94c5", - "datetime": "2024-05-17T10:55:04.124405", - "commit": "1409ed27df07827a2a3e3756420db1b41fe108e5" + "merkle_root": "329b06dbf2564bf17ba46d2b3304f91df15abc42794763a55ea33bc04281ac42", + "datetime": "2024-05-20T15:37:23.831427", + "commit": "2a5dc6f8d54701e86c32451fb154d3e40a778ca9" }, "history": [ { From c84c28252ee56e0111a84ec03e162c8936276c0a Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 24 Apr 2024 23:09:00 +0200 Subject: [PATCH 2/5] DROP feat(core/ui): sending button requests from rust this is supposed to go into main via https://github.com/trezor/trezor-firmware/pull/3714 --- core/embed/rust/librust_qstr.h | 1 + core/embed/rust/src/ui/button_request.rs | 60 +++++++++ core/embed/rust/src/ui/component/base.rs | 16 +++ .../rust/src/ui/component/button_request.rs | 65 +++++++++ core/embed/rust/src/ui/component/mod.rs | 2 + core/embed/rust/src/ui/layout/obj.rs | 21 +++ core/embed/rust/src/ui/mod.rs | 1 + core/embed/rust/src/ui/model_tt/layout.rs | 3 + core/mocks/generated/trezorui2.pyi | 2 + core/src/trezor/ui/layouts/common.py | 2 +- core/src/trezor/ui/layouts/tr/__init__.py | 127 +++++++++++------- core/src/trezor/ui/layouts/tt/__init__.py | 101 +++++++++----- core/src/trezor/ui/layouts/tt/recovery.py | 7 +- core/src/trezor/ui/layouts/tt/reset.py | 29 ++-- core/src/trezor/wire/context.py | 17 ++- .../test_apps.ethereum.sign_typed_data.py | 11 ++ 16 files changed, 361 insertions(+), 104 deletions(-) create mode 100644 core/embed/rust/src/ui/button_request.rs create mode 100644 core/embed/rust/src/ui/component/button_request.rs diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 6f0e76e58a9..478866ddc74 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -103,6 +103,7 @@ static void _librust_qstrs(void) { MP_QSTR_bounds; MP_QSTR_button; MP_QSTR_button_event; + MP_QSTR_button_request; MP_QSTR_buttons__abort; MP_QSTR_buttons__access; MP_QSTR_buttons__again; diff --git a/core/embed/rust/src/ui/button_request.rs b/core/embed/rust/src/ui/button_request.rs new file mode 100644 index 00000000000..a9529debfdd --- /dev/null +++ b/core/embed/rust/src/ui/button_request.rs @@ -0,0 +1,60 @@ +use crate::strutil::TString; +use num_traits::FromPrimitive; + +// ButtonRequestType from messages-common.proto +// Eventually this should be generated +#[derive(Clone, Copy, FromPrimitive)] +#[repr(u16)] +pub enum ButtonRequestCode { + Other = 1, + FeeOverThreshold = 2, + ConfirmOutput = 3, + ResetDevice = 4, + ConfirmWord = 5, + WipeDevice = 6, + ProtectCall = 7, + SignTx = 8, + FirmwareCheck = 9, + Address = 10, + PublicKey = 11, + MnemonicWordCount = 12, + MnemonicInput = 13, + UnknownDerivationPath = 15, + RecoveryHomepage = 16, + Success = 17, + Warning = 18, + PassphraseEntry = 19, + PinEntry = 20, +} + +impl ButtonRequestCode { + pub fn num(&self) -> u16 { + *self as u16 + } + + pub fn with_type(self, br_type: &'static str) -> ButtonRequest { + ButtonRequest::new(self, br_type.into()) + } + + pub fn from(i: u16) -> Self { + unwrap!(Self::from_u16(i)) + } +} + +const MAX_TYPE_LEN: usize = 32; + +#[derive(Clone)] +pub struct ButtonRequest { + pub code: ButtonRequestCode, + pub br_type: TString<'static>, +} + +impl ButtonRequest { + pub fn new(code: ButtonRequestCode, br_type: TString<'static>) -> Self { + ButtonRequest { code, br_type } + } + + pub fn from_tstring(code: u16, br_type: TString<'static>) -> Self { + ButtonRequest::new(ButtonRequestCode::from(code), br_type) + } +} diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index 5e575ad5d65..397fcd6c8b7 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -6,6 +6,7 @@ use crate::{ strutil::TString, time::Duration, ui::{ + button_request::{ButtonRequest, ButtonRequestCode}, component::{maybe::PaintOverlapping, MsgMap}, display::{self, Color}, geometry::{Offset, Rect}, @@ -487,6 +488,7 @@ pub struct EventCtx { paint_requested: bool, anim_frame_scheduled: bool, page_count: Option, + button_request: Option, root_repaint_requested: bool, } @@ -513,6 +515,7 @@ impl EventCtx { * `Child::marked_for_paint` being true. */ anim_frame_scheduled: false, page_count: None, + button_request: None, root_repaint_requested: false, } } @@ -570,6 +573,16 @@ impl EventCtx { self.page_count } + pub fn send_button_request(&mut self, code: ButtonRequestCode, br_type: TString<'static>) { + #[cfg(feature = "ui_debug")] + assert!(self.button_request.is_none()); + self.button_request = Some(ButtonRequest::new(code, br_type)); + } + + pub fn button_request(&mut self) -> Option { + self.button_request.take() + } + pub fn pop_timer(&mut self) -> Option<(TimerToken, Duration)> { self.timers.pop() } @@ -579,6 +592,9 @@ impl EventCtx { self.paint_requested = false; self.anim_frame_scheduled = false; self.page_count = None; + #[cfg(feature = "ui_debug")] + assert!(self.button_request.is_none()); + self.button_request = None; self.root_repaint_requested = false; } diff --git a/core/embed/rust/src/ui/component/button_request.rs b/core/embed/rust/src/ui/component/button_request.rs new file mode 100644 index 00000000000..193bd11dba1 --- /dev/null +++ b/core/embed/rust/src/ui/component/button_request.rs @@ -0,0 +1,65 @@ +use crate::ui::{ + button_request::ButtonRequest, + component::{Component, Event, EventCtx}, + geometry::Rect, +}; + +/// Component that sends a ButtonRequest after receiving Event::Attach. The +/// request is only sent once. +#[derive(Clone)] +pub struct OneButtonRequest { + button_request: Option, + pub inner: T, +} + +impl OneButtonRequest { + pub fn new(button_request: ButtonRequest, inner: T) -> Self { + Self { + button_request: Some(button_request), + inner, + } + } +} + +impl Component for OneButtonRequest { + type Msg = T::Msg; + + fn place(&mut self, bounds: Rect) -> Rect { + self.inner.place(bounds) + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + if matches!(event, Event::Attach) { + if let Some(button_request) = self.button_request.take() { + ctx.send_button_request(button_request.code, button_request.br_type) + } + } + self.inner.event(ctx, event) + } + + fn paint(&mut self) { + self.inner.paint() + } + + fn render<'s>(&'s self, target: &mut impl crate::ui::shape::Renderer<'s>) { + self.inner.render(target) + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for OneButtonRequest { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + self.inner.trace(t) + } +} + +pub trait ButtonRequestExt { + fn one_button_request(self, br: ButtonRequest) -> OneButtonRequest + where + Self: Sized, + { + OneButtonRequest::new(br, self) + } +} + +impl ButtonRequestExt for T {} diff --git a/core/embed/rust/src/ui/component/mod.rs b/core/embed/rust/src/ui/component/mod.rs index bc1e36dbb32..3d891580f22 100644 --- a/core/embed/rust/src/ui/component/mod.rs +++ b/core/embed/rust/src/ui/component/mod.rs @@ -3,6 +3,7 @@ pub mod bar; pub mod base; pub mod border; +pub mod button_request; pub mod connect; pub mod empty; pub mod image; @@ -24,6 +25,7 @@ pub mod timeout; pub use bar::Bar; pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken}; pub use border::Border; +pub use button_request::{ButtonRequestExt, OneButtonRequest}; pub use empty::Empty; #[cfg(all(feature = "jpeg", feature = "micropython"))] pub use jpeg::Jpeg; diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index b7c15eb6f51..01130a3ccc3 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -17,6 +17,7 @@ use crate::{ }, time::Duration, ui::{ + button_request::ButtonRequest, component::{Component, Event, EventCtx, Never, Root, TimerToken}, constant, display::sync, @@ -248,6 +249,17 @@ impl LayoutObj { self.inner.borrow().page_count.into() } + fn obj_button_request(&self) -> Result { + let inner = &mut *self.inner.borrow_mut(); + + match inner.event_ctx.button_request() { + None => Ok(Obj::const_none()), + Some(ButtonRequest { code, br_type }) => { + (code.num().into(), br_type.try_into()?).try_into() + } + } + } + #[cfg(feature = "ui_debug")] fn obj_bounds(&self) { use crate::ui::display; @@ -280,6 +292,7 @@ impl LayoutObj { Qstr::MP_QSTR_trace => obj_fn_2!(ui_layout_trace).as_obj(), Qstr::MP_QSTR_bounds => obj_fn_1!(ui_layout_bounds).as_obj(), Qstr::MP_QSTR_page_count => obj_fn_1!(ui_layout_page_count).as_obj(), + Qstr::MP_QSTR_button_request => obj_fn_1!(ui_layout_button_request).as_obj(), }), }; &TYPE @@ -470,6 +483,14 @@ extern "C" fn ui_layout_page_count(this: Obj) -> Obj { unsafe { util::try_or_raise(block) } } +extern "C" fn ui_layout_button_request(this: Obj) -> Obj { + let block = || { + let this: Gc = this.try_into()?; + this.obj_button_request() + }; + unsafe { util::try_or_raise(block) } +} + #[cfg(feature = "ui_debug")] #[no_mangle] pub extern "C" fn ui_debug_layout_type() -> &'static Type { diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index 75c14bbf748..9e1518c8a08 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -2,6 +2,7 @@ pub mod macros; pub mod animation; +pub mod button_request; pub mod component; pub mod constant; pub mod display; diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index a981b04cac3..48e87a4ef2b 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1652,6 +1652,9 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def page_count(self) -> int: /// """Return the number of pages in the layout object.""" /// + /// def button_request(self) -> tuple[int, str] | None: + /// """Return (code, type) of button request made during the last event or timer pass.""" + /// /// class UiResult: /// """Result of a UI operation.""" /// pass diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index e26546dc929..94e8f6b6f0c 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -1079,6 +1079,8 @@ class LayoutObj(Generic[T]): """Paint bounds of individual components on screen.""" def page_count(self) -> int: """Return the number of pages in the layout object.""" + def button_request(self) -> tuple[int, str] | None: + """Return (code, type) of button request made during the last event or timer pass.""" # rust/src/ui/model_tt/layout.rs diff --git a/core/src/trezor/ui/layouts/common.py b/core/src/trezor/ui/layouts/common.py index f16dcc16e85..86ce7a47fb3 100644 --- a/core/src/trezor/ui/layouts/common.py +++ b/core/src/trezor/ui/layouts/common.py @@ -39,4 +39,4 @@ async def interact( # 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_type, br_code, pages) - return await context.wait(layout) + return await layout diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index fa752231737..8101160d52d 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__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 @@ -38,9 +38,11 @@ class RustLayout(LayoutParentType[T]): # pylint: disable=super-init-not-called def __init__(self, layout: trezorui2.LayoutObj[T]): + 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) @@ -62,13 +64,23 @@ def _paint(self) -> None: from trezor.enums import DebugPhysicalButton def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: - return ( - self.handle_input_and_rendering(), - self.handle_timers(), - self.handle_swipe_signal(), - self.handle_button_signal(), - self.handle_result_signal(), - ) + if context.CURRENT_CONTEXT: + return ( + self.handle_input_and_rendering(), + self.handle_timers(), + self.handle_swipe_signal(), + self.handle_button_signal(), + self.handle_result_signal(), + self.handle_usb(context.get_context()), + ) + else: + return ( + self.handle_input_and_rendering(), + self.handle_timers(), + self.handle_swipe_signal(), + self.handle_button_signal(), + self.handle_result_signal(), + ) async def handle_result_signal(self) -> None: """Enables sending arbitrary input - ui.Result. @@ -98,30 +110,41 @@ def callback(*args: Any) -> None: async def _press_left(self, hold_ms: int | None) -> Any: """Triggers left button press.""" self.layout.button_event(io.BUTTON_PRESSED, io.BUTTON_LEFT) + self._send_button_request() self._paint() if hold_ms is not None: await loop.sleep(hold_ms) - return self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_LEFT) + r = self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_LEFT) + self._send_button_request() + return r async def _press_right(self, hold_ms: int | None) -> Any: """Triggers right button press.""" self.layout.button_event(io.BUTTON_PRESSED, io.BUTTON_RIGHT) + self._send_button_request() self._paint() if hold_ms is not None: await loop.sleep(hold_ms) - return self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_RIGHT) + r = self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_RIGHT) + self._send_button_request() + return r async def _press_middle(self, hold_ms: int | None) -> Any: """Triggers middle button press.""" self.layout.button_event(io.BUTTON_PRESSED, io.BUTTON_LEFT) + self._send_button_request() self._paint() self.layout.button_event(io.BUTTON_PRESSED, io.BUTTON_RIGHT) + self._send_button_request() self._paint() if hold_ms is not None: await loop.sleep(hold_ms) self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_LEFT) + self._send_button_request() self._paint() - return self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_RIGHT) + r = self.layout.button_event(io.BUTTON_RELEASED, io.BUTTON_RIGHT) + self._send_button_request() + return r async def _press_button( self, @@ -197,7 +220,11 @@ async def handle_button_signal(self) -> None: else: def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: - return self.handle_timers(), self.handle_input_and_rendering() + return ( + self.handle_timers(), + self.handle_input_and_rendering(), + self.handle_usb(context.get_context()), + ) def _first_paint(self) -> None: self._paint() @@ -233,6 +260,7 @@ def handle_input_and_rendering(self) -> loop.Task: msg = None if event in (io.BUTTON_PRESSED, io.BUTTON_RELEASED): msg = self.layout.button_event(event, button_num) + self._send_button_request() if msg is not None: raise ui.Result(msg) self._paint() @@ -242,6 +270,7 @@ def handle_timers(self) -> loop.Task: # 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() @@ -250,6 +279,20 @@ def page_count(self) -> int: """How many paginated pages current screen has.""" 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: trezorui2.LayoutObj[Any]) -> None: # Simple drawing not supported for layouts that set timers. @@ -513,7 +556,7 @@ async def show_address( pages=layout.page_count(), ) layout.request_complete_repaint() - result = await ctx_wait(layout) + result = await layout # User confirmed with middle button. if result is CONFIRMED: @@ -532,27 +575,23 @@ def xpub_title(i: int) -> str: ) return result - result = await ctx_wait( - RustLayout( - trezorui2.show_address_details( - qr_title="", # unused on this model - address=address if address_qr is None else address_qr, - case_sensitive=case_sensitive, - details_title="", # unused on this model - account=account, - path=path, - xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)], - ) - ), + result = await RustLayout( + trezorui2.show_address_details( + qr_title="", # unused on this model + address=address if address_qr is None else address_qr, + case_sensitive=case_sensitive, + details_title="", # unused on this model + account=account, + path=path, + xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)], + ) ) # Can only go back from the address details. assert result is CANCELLED # User pressed left cancel button, show mismatch dialogue. else: - result = await ctx_wait( - RustLayout(trezorui2.show_mismatch(title=mismatch_title)) - ) + result = await RustLayout(trezorui2.show_mismatch(title=mismatch_title)) assert result in (CONFIRMED, CANCELLED) # Right button aborts action, left goes back to showing address. if result is CONFIRMED: @@ -1029,24 +1068,22 @@ async def confirm_value( should_show_more_layout.page_count(), ) - result = await ctx_wait(should_show_more_layout) + result = await should_show_more_layout if result is CONFIRMED: return elif result is INFO: info_title, info_value = info_items_list[0] - await ctx_wait( - RustLayout( - trezorui2.confirm_blob( - title=info_title, - data=info_value, - description=description, - extra=None, - verb="", - verb_cancel="<", - hold=False, - chunkify=chunkify_info, - ) + await RustLayout( + trezorui2.confirm_blob( + title=info_title, + data=info_value, + description=description, + extra=None, + verb="", + verb_cancel="<", + hold=False, + chunkify=chunkify_info, ) ) else: @@ -1287,7 +1324,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 @@ -1297,7 +1334,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 diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index 3c8f5061307..aa954e13c97 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__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 @@ -40,9 +40,11 @@ class RustLayout(LayoutParentType[T]): # pylint: disable=super-init-not-called def __init__(self, layout: trezorui2.LayoutObj[T]): + 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) @@ -63,13 +65,23 @@ def _paint(self) -> None: 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. @@ -116,6 +128,7 @@ async def handle_swipe(self): (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) @@ -135,10 +148,12 @@ async def _click( 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 @@ -165,7 +180,17 @@ async def handle_click_signal(self) -> None: 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) @@ -205,6 +230,7 @@ def handle_input_and_rendering(self) -> loop.Task: 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() @@ -214,6 +240,7 @@ def handle_timers(self) -> loop.Task: # 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() @@ -221,6 +248,20 @@ def handle_timers(self) -> loop.Task: 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: trezorui2.LayoutObj[Any]) -> None: # Simple drawing not supported for layouts that set timers. @@ -460,7 +501,7 @@ async def show_address( pages=layout.page_count(), ) layout.request_complete_repaint() - result = await ctx_wait(layout) + result = await layout # User pressed right button. if result is CONFIRMED: @@ -478,25 +519,21 @@ def xpub_title(i: int) -> str: ) return result - result = await ctx_wait( - RustLayout( - trezorui2.show_address_details( - qr_title=title, - address=address if address_qr is None else address_qr, - case_sensitive=case_sensitive, - details_title=details_title, - account=account, - path=path, - xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)], - ) + result = await RustLayout( + trezorui2.show_address_details( + qr_title=title, + address=address if address_qr is None else address_qr, + case_sensitive=case_sensitive, + details_title=details_title, + account=account, + path=path, + xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)], ) ) assert result is CANCELLED else: - result = await ctx_wait( - RustLayout(trezorui2.show_mismatch(title=mismatch_title)) - ) + result = await RustLayout(trezorui2.show_mismatch(title=mismatch_title)) assert result in (CONFIRMED, CANCELLED) # Right button aborts action, left goes back to showing address. if result is CONFIRMED: @@ -1198,7 +1235,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 @@ -1208,7 +1245,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 @@ -1223,11 +1260,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 @@ -1355,8 +1392,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/tt/recovery.py b/core/src/trezor/ui/layouts/tt/recovery.py index a3c99e4fc86..82310480a9a 100644 --- a/core/src/trezor/ui/layouts/tt/recovery.py +++ b/core/src/trezor/ui/layouts/tt/recovery.py @@ -3,7 +3,6 @@ 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/tt/reset.py b/core/src/trezor/ui/layouts/tt/reset.py index c5a08d10f68..19757706378 100644 --- a/core/src/trezor/ui/layouts/tt/reset.py +++ b/core/src/trezor/ui/layouts/tt/reset.py @@ -4,7 +4,6 @@ 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 @@ -95,15 +94,13 @@ async def select_word( while len(words) < 3: words.append(words[-1]) - result = await ctx_wait( - RustLayout( - 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]), - ) + result = await RustLayout( + 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]), ) ) if __debug__ and isinstance(result, str): @@ -183,13 +180,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() diff --git a/core/src/trezor/wire/context.py b/core/src/trezor/wire/context.py index 08eaab3474f..b2f9afa5e4f 100644 --- a/core/src/trezor/wire/context.py +++ b/core/src/trezor/wire/context.py @@ -157,6 +157,17 @@ async def write(self, msg: protobuf.MessageType) -> None: memoryview(buffer)[:msg_size], ) + async def call( + self, + msg: protobuf.MessageType, + expected_type: type[LoadedMessageType], + ) -> LoadedMessageType: + assert expected_type.MESSAGE_WIRE_TYPE is not None + + await self.write(msg) + del msg + return await self.read((expected_type.MESSAGE_WIRE_TYPE,), expected_type) + CURRENT_CONTEXT: Context | None = None @@ -186,11 +197,7 @@ async def call( if CURRENT_CONTEXT is None: raise RuntimeError("No wire context") - assert expected_type.MESSAGE_WIRE_TYPE is not None - - await CURRENT_CONTEXT.write(msg) - del msg - return await CURRENT_CONTEXT.read((expected_type.MESSAGE_WIRE_TYPE,), expected_type) + return await CURRENT_CONTEXT.call(msg, expected_type) async def call_any( diff --git a/core/tests/test_apps.ethereum.sign_typed_data.py b/core/tests/test_apps.ethereum.sign_typed_data.py index bf6f3772c9b..e8a261886a1 100644 --- a/core/tests/test_apps.ethereum.sign_typed_data.py +++ b/core/tests/test_apps.ethereum.sign_typed_data.py @@ -41,6 +41,17 @@ async def write(self, request) -> None: async def read(self, _resp_types, _resp_type): return EthereumTypedDataValueAck(value=self.next_response) + async def call( + self, + msg: protobuf.MessageType, + expected_type: type[LoadedMessageType], + ) -> LoadedMessageType: + assert expected_type.MESSAGE_WIRE_TYPE is not None + + await self.write(msg) + del msg + return await self.read((expected_type.MESSAGE_WIRE_TYPE,), expected_type) + # Helper functions from trezorctl to build expected type data structures # TODO: it could be better to group these functions into a class, to visibly differentiate it From 54e87693509eb9207cf34081f8a433e9f538da0c Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Mon, 20 May 2024 18:30:57 +0200 Subject: [PATCH 3/5] fix(core/ui): T3T1 ButtonRequests from rust [no changelog] --- core/embed/rust/librust_qstr.h | 2 + .../rust/src/ui/component/button_request.rs | 18 ++ .../ui/model_mercury/flow/confirm_output.rs | 13 +- .../ui/model_mercury/flow/confirm_summary.rs | 9 +- .../src/ui/model_mercury/flow/get_address.rs | 10 +- .../embed/rust/src/ui/model_mercury/layout.rs | 6 + core/mocks/generated/trezorui2.pyi | 6 + .../src/trezor/ui/layouts/mercury/__init__.py | 154 +++++++++++------- .../src/trezor/ui/layouts/mercury/recovery.py | 7 +- core/src/trezor/ui/layouts/mercury/reset.py | 29 ++-- 10 files changed, 163 insertions(+), 91 deletions(-) diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 478866ddc74..d1eb8b4b838 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 193bd11dba1..cfac92d45e0 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"))] +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 9e55a15978a..d5b40aa91a9 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 2b465394cd3..83f5012980f 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 d598cd378ae..60480f8c711 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 9cbf6bfbd56..c101794cf38 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 94e8f6b6f0c..4f2df01ec67 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 c150b7fff12..67812965a6e 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 @@ def _paint(self) -> None: 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 @@ async def handle_swipe(self): (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 @@ async def _click( 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 @@ async def handle_click_signal(self) -> None: 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 @@ def handle_input_and_rendering(self) -> loop.Task: 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 @@ def handle_timers(self) -> loop.Task: # 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 @@ def handle_timers(self) -> loop.Task: 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 @@ def xpub_title(i: int) -> str: 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 fd61afaeccc..6b2641fd7b2 100644 --- a/core/src/trezor/ui/layouts/mercury/recovery.py +++ b/core/src/trezor/ui/layouts/mercury/recovery.py @@ -3,7 +3,6 @@ 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 eb7cc062e27..e8ced5b504d 100644 --- a/core/src/trezor/ui/layouts/mercury/reset.py +++ b/core/src/trezor/ui/layouts/mercury/reset.py @@ -4,7 +4,6 @@ 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 = 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() From cc60b518ee9888243d7033cbd49d690d0b30b3d6 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 21 May 2024 00:17:33 +0200 Subject: [PATCH 4/5] chore(core): update fixtures.json --- tests/ui_tests/fixtures.json | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index d60853265c8..4ddb739e75a 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -11345,7 +11345,7 @@ "T2T1_en_bitcoin-test_signtx.py::test_information": "44e044c3f8b299aeed155f0aec66f60510aa7dcd3986d340c76ef128de6a428a", "T2T1_en_bitcoin-test_signtx.py::test_information_cancel": "d242ec98f96d0db41ebcd7a44ce31bac38637ebd1103727445a068118c51956b", "T2T1_en_bitcoin-test_signtx.py::test_information_mixed": "c76e1d787c589090bd950101fda86f62dff83e70d4f879ea1fb0519158c1e95a", -"T2T1_en_bitcoin-test_signtx.py::test_information_replacement": "ffce40135620e220930713ec3ec9554e6ae801aea34235ed0ff89dbb805c891c", +"T2T1_en_bitcoin-test_signtx.py::test_information_replacement": "6978800b4c7d2fb354f1b65427b3c5813c673c4b048d511062367dd21958e337", "T2T1_en_bitcoin-test_signtx.py::test_lock_time[1-4294967295]": "924ed54c06fd791910d8a43e0b7c6f39852b3406167045211dca4e495dc69b05", "T2T1_en_bitcoin-test_signtx.py::test_lock_time[499999999-4294967294]": "8eb547d98a20c6333c50f507b62bce5536ece3b9c2a6ecbbf58f31d2fc45b003", "T2T1_en_bitcoin-test_signtx.py::test_lock_time[500000000-4294967294]": "5deae35138650618ac4df74251ecb300a416addb1af5d7495b0090a23ceede5b", @@ -11997,20 +11997,20 @@ "T2T1_en_ethereum-test_signtx.py::test_signtx_eip1559[True-nodata]": "ac146a14d60488c3f201e11398fb6ad810c088614a9ce1d529319812e6d4d86e", "T2T1_en_ethereum-test_signtx.py::test_signtx_eip1559_access_list": "243010310ac5a4c70c627507ea8501cc61c2e20728eb06bc796f093132bebb4f", "T2T1_en_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "243010310ac5a4c70c627507ea8501cc61c2e20728eb06bc796f093132bebb4f", -"T2T1_en_ethereum-test_signtx.py::test_signtx_fee_info": "714e4c5f6e6b45fa3e78f74c7ee5e3332f39686f8b708a4f56232105bde0c3e4", +"T2T1_en_ethereum-test_signtx.py::test_signtx_fee_info": "dc447d776e60c98da4eada56e7dd443952f893a05c328157dc1fdbea3083edd3", "T2T1_en_ethereum-test_signtx.py::test_signtx_go_back_from_summary": "8bc38a773c40a70c1eb9b91a5d02ce0a61591ce9e42bd0073bc1395f560f2490", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-claim_holesky]": "4b89fe403777b910e365031ff4da4ddf844d8d9385ee6d562d8582e7ef270b8d", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-claim_mainnet]": "a82c3762d114bf542e9753faf7c54027cfb02c0fddfb7ba914eedac69366fbfb", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-stake_holesky]": "afb28033e3093ee6d38266defb14bfe1ae36b727869b631e0b055694ab9097d7", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-stake_main]": "c4ef05473b0b238e68385cda289a26b8ecf8c86db213cc6b346a225a0f323332", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-unstake_holesky]": "de100f2d3c8cc88672a09795ea9324368bf2c6e5e6b5b76ee04f2b3e69a80444", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-unstake_main]": "0c819d43c537b170b91edd29dc1ec88b78577131c6c4a84d14de7cdc42c3042f", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-claim_holesky]": "207fb30e30123a92291f95b3769a217e05110ddf9f8c3446970459f2fe50b44a", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-claim_mainnet]": "405d4ac4856c34af5606e49a742235062b1550b4fcaec4fc4ac3ac4bbe998256", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-stake_holesky]": "c133a3b7addfda617ccd9401ae28a12b1681e9ce47c28ddee8ae78a3ebccbd6d", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-stake_main]": "e924f9c247e732fa9b727d71b844bdd734be9afcb4a26ebb74a4450153229f95", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-unstake_holesky]": "fac986a6b54fd0ed850fd3810df55de3dc411ebfa9520b8e51ec255bffaf0467", -"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-unstake_main]": "106a4cf1f71595f65c4ed2f12b4f868ba0c5f8b6dce75805489be8e6df3f39cf", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-claim_holesky]": "eaf826887d901a967822e0ce957536ad9d27b4ab0df8eacbd0a0065df26da7ff", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-claim_mainnet]": "ba8c8952343485898f889f6d57088313c64220628b6ccd595498f6f8adf897fd", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-stake_holesky]": "7a371c9f1d75ea8f920b968928b7829c06218cf022c30d99d83293418332b18c", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-stake_main]": "748295d1339c4689a7c6848e7bf03599c0c6b2a9ec1137ebb3acf8cefbeb53da", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-unstake_holesky]": "9ffe2b5aa5c2a1aa6901f9904672cfadf39af8b5821a556e5ed2d2bab016d4e1", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[False-unstake_main]": "5c704168ea1078561764fbbd2d45560a5c10a755a535c687986645cb13959eca", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-claim_holesky]": "b0dca5d00da5e2d43f7805d49bc7793ecf8d02a0118a497072f04decfea9e223", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-claim_mainnet]": "e1c9489f523558a3a9c558bdc932ce0a4b4edaa3c61aa70eacead76ac636d0e5", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-stake_holesky]": "e1059dcf037421fb8f5346b7e402a646c905b3002f879d913b4d7b3be3ab8257", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-stake_main]": "7ab3157f0c5b4f500e714c388c21ac2692411af682a96152ac251f668bbc9e2f", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-unstake_holesky]": "cc5c238a2fb1c473bd0c2ab39a1a42197c9dca4a5b656684b109341f4a88de44", +"T2T1_en_ethereum-test_signtx.py::test_signtx_staking[True-unstake_main]": "eb1d6e321fa99bc0db2099898390cd7bd93592c535f8bfe5783de5aa34cfc8d1", "T2T1_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[claim_bad_inputs_1]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b", "T2T1_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_1]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b", "T2T1_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_2]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b", @@ -12111,9 +12111,9 @@ "T2T1_en_reset_recovery-test_recovery_bip39_t2.py::test_tt_pin_passphrase": "9d161076574c957b3708ec34771026a970b1ad90ac7bbf5b1492ded0de1f5ba4", "T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_abort": "264cc069a974286064781c22c175e54bdb28000909b5dc671acfa03ab272b426", "T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_extra_share_entered": "7d0f0cac581360c5a8b64e229171f0c6a92972adff3c3ed334c483076021a041", -"T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "2561ac9a4a209c181d8cf35579db0fa36f752cb628067d59d3456d64f82f93cc", +"T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_group_threshold_reached": "980c374bd173485c06ffc3ad2f3874762ef065762ce063440791383f1bb01b08", "T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_noabort": "2c4f3600b0f26c4da64add476618ce5bb9d759d914afe89c5a75eec3cd7ebeac", -"T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "b86d2d1c561fac4055ed1c437bcdd69d2dd7f43553af6bb5630259430983f55a", +"T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "a1ad9e10356bd6223b37949538fbb560a4487aa9463d19a9001ad35c4834d8fd", "T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "9271c61c3740658029b0fe3b375377233aaad70a97d929d984f6b1a16b3c1ef7", "T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "c0886a9bef6b448d193ddf032c14b614069c5f742aacdfab0d4d21c53a5dd331", "T2T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "4f90f47e88cca3af88975a817baa01937b21647c19d7799acea6795309bba28b", @@ -12123,15 +12123,15 @@ "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "73de5bb9f1528b81396ebfd321df67f773f925d6cbd7e616a58ce30eaea27ef0", "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_abort": "264cc069a974286064781c22c175e54bdb28000909b5dc671acfa03ab272b426", "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_invalid_mnemonic_first_share": "59786fd2798a68e0546515ccd7ec0d7e4c3276098545615850911899953681e1", -"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_invalid_mnemonic_second_share": "bcab24b36d8ac28c76f6ebcf54f416024d3b32ef03862234a88d9128d1e16548", +"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_invalid_mnemonic_second_share": "703ed967f59738cf2a41aa6780656d1a8c77ab3c2cc558820981421a9e6475df", "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_noabort": "12ea0985454ab09cbec3d1f9e4246f0e6c098cc8ea69774ea253a473065e6938", "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_recover_with_pin_passphrase": "66c45751002ae4d84e0ffb3a80349585604b5f19f87d08368e806ff0ac0d6cd5", -"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "7d6b8c0008568f8a9fac9e6bb543b3b60845eb6aa5cc715efc5ed75f95aa27cb", +"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_same_share": "b5d6d520a1fa9d7c8e1d074dbb9646c62f0f5ff8c4fc192f5547b671d6bf7913", "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "443ab1ba8f9bc006865e978418b4ccdf931f88ad5c13397467cfc01ed3b3e406", "T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39-a50896b7": "a16d8e9838b762534c0a2d8c82c277ba81e49f018506dc26624a4a0d8cce97c6", -"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[0]": "c8f48570ea01b8046ac1deb84f3a12d1b02718ef28fcb47402e9f98745769d3e", -"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[1]": "d31779beb275065c26d2ad3fab64682b4c55c9c0bee2e2ddab6c607762d1e551", -"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "35b9bc382e6e0c8778be8456f7dda77ea693ed5e8bc1168213c9ca5b1b1d5720", +"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[0]": "f9e8df5075762c89e5f9eacc171ecb1592c9e905ee87d07066f8d2863f7c8598", +"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[1]": "041e5ffdafb2812db3c37a6bcd89f50fb1906563aecffd95dbc02bceb8084172", +"T2T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "4b32bef901796d64fe03b215688da4af7c24d013b67ecd5b0d561043fa47dc44", "T2T1_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "5f3413af77cc76413fe1ec3e00e17850f8de504499b5bf9d753ed4d9351931e5", "T2T1_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "2b866e19489d98b9924398080478e2126fe9769b057ea725e7ba795584adb1ad", "T2T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "e5468c5e24463f2e7f0bd7eec981bb7e942cce7dbadcdbd7c5cf3bd1df229a4f", @@ -12311,11 +12311,11 @@ "T2T1_en_test_basic.py::test_ping": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", "T2T1_en_test_busy_state.py::test_busy_expiry": "6509e7c943f2d09e3a5a7c69faee74c75b321ed1614d881d1ebdb57693c7f69a", "T2T1_en_test_busy_state.py::test_busy_state": "13fce27fd795f6f622614fde678ba3f717129a27ecc6d0c2cbd389ede4a2798f", -"T2T1_en_test_cancel.py::test_cancel_message_via_cancel[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", -"T2T1_en_test_cancel.py::test_cancel_message_via_cancel[message1]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", -"T2T1_en_test_cancel.py::test_cancel_message_via_initialize[message0]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", -"T2T1_en_test_cancel.py::test_cancel_message_via_initialize[message1]": "1bd3157d54327e33542f89dcac6c7cd23808f7c9aa1b0adb390e5fcc1fd858a5", -"T2T1_en_test_cancel.py::test_cancel_on_paginated": "498ffee2ea02e2783466cbb993f0cd83eedd3b67afde37b5d1f713fc996c1455", +"T2T1_en_test_cancel.py::test_cancel_message_via_cancel[message0]": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739", +"T2T1_en_test_cancel.py::test_cancel_message_via_cancel[message1]": "3ed24b99952d25fcf0d9bd6d307c576862fe870ddb98288fa89b227f0e192073", +"T2T1_en_test_cancel.py::test_cancel_message_via_initialize[message0]": "d702b0f90581cf17e0f77b4d318324a002deec42c2c5cb8860d51f6cb50f5739", +"T2T1_en_test_cancel.py::test_cancel_message_via_initialize[message1]": "3ed24b99952d25fcf0d9bd6d307c576862fe870ddb98288fa89b227f0e192073", +"T2T1_en_test_cancel.py::test_cancel_on_paginated": "5c198919403dab5ba394a87544aaa95bdafe82959fbabdbe918c81e2d226b051", "T2T1_en_test_debuglink.py::test_softlock_instability": "55cb4cbeec68bd8ccee460034677cf8053f8f688d5c3559f360c38a205b34e37", "T2T1_en_test_firmware_hash.py::test_firmware_hash_emu": "2a63f0bd10ba99e223f571482d4af635653bb8a3bddc1d8400777ee5519bc605", "T2T1_en_test_firmware_hash.py::test_firmware_hash_hw": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3", @@ -12367,7 +12367,7 @@ "T2T1_en_test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "db9ffa189464d1c0ba7d3ea2533306e25e6895355083066c2b6f9b19efb54406", "T2T1_en_test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "96a18ef7ba92b18fd9d6d3856f737e8b1418df79221d6750723109c90b2d5807", "T2T1_en_test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "f478a15ed2dfde070953b1f14c6054452b0ac27ef8ab5cf9c1ea233a43676a32", -"T2T1_en_test_msg_changepin_t2.py::test_change_failed": "1526ada2ec21dc5219ac42698a1d6a9314b11f79adf37465d226ffb37b603124", +"T2T1_en_test_msg_changepin_t2.py::test_change_failed": "72e36642bd0b8832bec9e3b171da5a7fe5e2a1b44282c521a69d4fc0b5254828", "T2T1_en_test_msg_changepin_t2.py::test_change_invalid_current": "e9925836465ef35c3d5364e90780626274a92a1123451b572b9ea11a9cd2180a", "T2T1_en_test_msg_changepin_t2.py::test_change_pin": "256483cab21191ee110281e9f547c3fa08968f4d49fa38896f8141f5b8eb4701", "T2T1_en_test_msg_changepin_t2.py::test_remove_pin": "6a606987a97b9d50d991288f1222adf2c819846076b2014e91909c9907a0e427", @@ -19446,7 +19446,7 @@ "T3T1_en_ethereum-test_signtx.py::test_signtx_eip1559[True-nodata]": "47754417202531ba0f2a6c36a29c3921e01664e6cc5a90ccdce47e83c4162fd6", "T3T1_en_ethereum-test_signtx.py::test_signtx_eip1559_access_list": "a7acdae2d10ec55ca0e2c0232051b09178f72c6dd50bda0f9a7e63d485591b3c", "T3T1_en_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "a7acdae2d10ec55ca0e2c0232051b09178f72c6dd50bda0f9a7e63d485591b3c", -"T3T1_en_ethereum-test_signtx.py::test_signtx_fee_info": "4c00fa1b557e9169149291bd082370d783fccf1c2e7ab6c24671d5d6ab2f413c", +"T3T1_en_ethereum-test_signtx.py::test_signtx_fee_info": "2b9f4979e553a2b403436a0d467088072f64b0dd5ecc1e336617f10cc3bfda8b", "T3T1_en_ethereum-test_signtx.py::test_signtx_go_back_from_summary": "9af0b36f5fd89cde9f0921d8a02f2717252e2c5a1a42f456917831c65cecdd65", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking[False-claim_holesky]": "ea89893d68d31efcc65427ae1e80b325795bcf99f991e3225f43464791f5f097", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking[False-claim_mainnet]": "ff04115190827db1f26c54488bc66125273ea4c9c03c13e9843bc104c02eb395", @@ -19465,11 +19465,11 @@ "T3T1_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_2]": "4e4b12b09d26753b22694a38776b6b40d81aa8f55a8a8d0b647d046f1177e214", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[unstake_bad_inputs_1]": "4e4b12b09d26753b22694a38776b6b40d81aa8f55a8a8d0b647d046f1177e214", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[unstake_bad_inputs_2]": "4e4b12b09d26753b22694a38776b6b40d81aa8f55a8a8d0b647d046f1177e214", -"T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_holesky]": "4a8b61f3197e2476278ae5802767207618b473c03d893c9305466de1086cf2d7", -"T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_mainnet]": "4a8b61f3197e2476278ae5802767207618b473c03d893c9305466de1086cf2d7", +"T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_holesky]": "ff87cdaed57eab2ad1bfbff9cc17397ea06eef73ca56ab6c86f9d96f8d6ab72d", +"T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_mainnet]": "ff87cdaed57eab2ad1bfbff9cc17397ea06eef73ca56ab6c86f9d96f8d6ab72d", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[stake_holesky]": "0f0b5cd392115509de09538d6cf4fab8749c18796e10792ca425b1c4142649c4", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[stake_main]": "0f0b5cd392115509de09538d6cf4fab8749c18796e10792ca425b1c4142649c4", -"T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_holesky]": "7df45085653ab15aa00042dcafecc8b805cd47c28d360ff12a0b423466f94ebf", +"T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_holesky]": "59f9016075c49a91e74e0c56db6353b37a7b7bac79ab7d654d33ab6360e47f96", "T3T1_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_main]": "7df45085653ab15aa00042dcafecc8b805cd47c28d360ff12a0b423466f94ebf", "T3T1_en_misc-test_cosi.py::test_cosi_different_key": "944f7bd7533295a23f6b24836772acbfbd096390096e77f5e36964c591139c22", "T3T1_en_misc-test_cosi.py::test_cosi_nonce": "a92d222c9329a715cae09165420cfdc2c67e2920e0c2d9a0870f682acfe48c26", From 79a697d3da051c3e26abdfb53c5af26c6637de99 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 21 May 2024 00:42:33 +0200 Subject: [PATCH 5/5] refactor(core/ui): fix clippy issues --- .../src/ui/model_mercury/bootloader/mod.rs | 10 +++--- .../model_mercury/component/keyboard/pin.rs | 2 +- .../src/ui/model_mercury/component/loader.rs | 10 +++--- .../ui/model_mercury/component/progress.rs | 19 +++++++---- .../ui/model_mercury/flow/confirm_action.rs | 8 ++--- .../embed/rust/src/ui/model_mercury/shapes.rs | 32 +++++++++++-------- 6 files changed, 48 insertions(+), 33 deletions(-) diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs index c535349fbc0..37d0d380a10 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs @@ -38,7 +38,7 @@ use crate::ui::{ constant, display::{toif::Toif, LOADER_MAX}, geometry::{Alignment, Alignment2D}, - model_mercury::shapes::render_loader, + model_mercury::shapes::{render_loader, LoaderRange}, shape, shape::render_on_display, }; @@ -130,9 +130,11 @@ impl ModelMercuryFeatures { inactive_color, fg_color, bg_color, - 0, - end, - progress >= LOADER_MAX, + if progress >= LOADER_MAX { + LoaderRange::Full + } else { + LoaderRange::FromTo(0, end) + }, target, ); diff --git a/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs b/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs index c7f14200f48..1d4e0136680 100644 --- a/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs +++ b/core/embed/rust/src/ui/model_mercury/component/keyboard/pin.rs @@ -378,7 +378,7 @@ impl PinDots { } // Greyed out dot. - if digits >= MAX_VISIBLE_DOTS + 1 { + if digits > MAX_VISIBLE_DOTS { shape::ToifImage::new(cursor, theme::DOT_ACTIVE.toif) .with_align(Alignment2D::CENTER_LEFT) .with_fg(theme::GREY) diff --git a/core/embed/rust/src/ui/model_mercury/component/loader.rs b/core/embed/rust/src/ui/model_mercury/component/loader.rs index d77f055c5cc..27467b10f18 100644 --- a/core/embed/rust/src/ui/model_mercury/component/loader.rs +++ b/core/embed/rust/src/ui/model_mercury/component/loader.rs @@ -7,7 +7,7 @@ use crate::{ component::{Component, Event, EventCtx, Pad}, display::{self, toif::Icon, Color, LOADER_MAX}, geometry::{Alignment2D, Offset, Rect}, - model_mercury::shapes::render_loader, + model_mercury::shapes::{render_loader, LoaderRange}, shape::{self, Renderer}, util::animation_disabled, }, @@ -231,9 +231,11 @@ impl Component for Loader { inactive_color, active_color, background_color, - start, - end, - progress >= LOADER_MAX, + if progress >= LOADER_MAX { + LoaderRange::Full + } else { + LoaderRange::FromTo(start, end) + }, target, ); diff --git a/core/embed/rust/src/ui/model_mercury/component/progress.rs b/core/embed/rust/src/ui/model_mercury/component/progress.rs index aaa1cd6bd47..c2af34a0aa9 100644 --- a/core/embed/rust/src/ui/model_mercury/component/progress.rs +++ b/core/embed/rust/src/ui/model_mercury/component/progress.rs @@ -11,7 +11,10 @@ use crate::{ }, display::{self, Font, LOADER_MAX}, geometry::{Insets, Offset, Rect}, - model_mercury::{constant, shapes::render_loader}, + model_mercury::{ + constant, + shapes::{render_loader, LoaderRange}, + }, shape, shape::Renderer, util::animation_disabled, @@ -116,15 +119,19 @@ impl Component for Progress { let background_color = theme::BG; let inactive_color = theme::GREY_EXTRA_DARK; - let (start, end) = if self.indeterminate { + let range = if self.indeterminate { let start = (self.value - 100) % 1000; let end = (self.value + 100) % 1000; let start = ((start as i32 * 8 * shape::PI4 as i32) / 1000) as i16; let end = ((end as i32 * 8 * shape::PI4 as i32) / 1000) as i16; - (start, end) + LoaderRange::FromTo(start, end) } else { let end = ((self.value as i32 * 8 * shape::PI4 as i32) / 1000) as i16; - (0, end) + if self.value >= LOADER_MAX { + LoaderRange::Full + } else { + LoaderRange::FromTo(0, end) + } }; render_loader( @@ -132,9 +139,7 @@ impl Component for Progress { inactive_color, active_color, background_color, - start, - end, - !self.indeterminate && self.value >= LOADER_MAX, + range, target, ); diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs index 64f956b9634..2244f3cc2d7 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_action.rs @@ -155,7 +155,7 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result Result Result( center: Point, inactive_color: Color, active_color: Color, background_color: Color, - start: i16, - end: i16, - full: bool, + range: LoaderRange, target: &mut impl Renderer<'s>, ) { shape::Circle::new(center, constant::LOADER_OUTER) .with_bg(inactive_color) .render(target); - if full { - shape::Circle::new(center, constant::LOADER_OUTER) - .with_bg(active_color) - .render(target); - } else { - shape::Circle::new(center, constant::LOADER_OUTER) - .with_bg(active_color) - .with_start_angle(start) - .with_end_angle(end) - .render(target); + match range { + LoaderRange::Full => { + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(active_color) + .render(target); + } + LoaderRange::FromTo(start, end) => { + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(active_color) + .with_start_angle(start) + .with_end_angle(end) + .render(target); + } } shape::Circle::new(center, constant::LOADER_INNER + 2)