diff --git a/ci/build.yml b/ci/build.yml index 4f31ea04fc..f7ae891378 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -133,22 +133,6 @@ core fw btconly production build: - firmware-T2T1-btconly-production-*.*.*-$CI_COMMIT_SHORT_SHA.bin expire_in: 1 week -core fw btconly t1 build: - stage: build - <<: *gitlab_caching - needs: [] - variables: - BITCOIN_ONLY: "1" - TREZOR_MODEL: "1" - script: - - nix-shell --run "poetry run make -C core build_firmware" - - cp core/build/firmware/firmware.bin firmware-T1B1-btconly-t1-$CORE_VERSION-$CI_COMMIT_SHORT_SHA.bin - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" - paths: - - firmware-T1B1-btconly-t1-*.*.*-$CI_COMMIT_SHORT_SHA.bin - expire_in: 1 week - core fw R btconly debug build: stage: build <<: *gitlab_caching @@ -370,22 +354,6 @@ core unix frozen debug build arm: tags: - docker_darwin_arm -core unix frozen btconly debug t1 build: - stage: build - <<: *gitlab_caching - needs: [] - variables: - BITCOIN_ONLY: "1" - TREZOR_MODEL: "1" - script: - - nix-shell --run "poetry run make -C core build_unix_frozen" - - mv core/build/unix/trezor-emu-core core/build/unix/trezor-emu-core-bitcoinonly - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" - paths: - - core/build/unix # most of it needed by test_rust - expire_in: 1 week - core macos frozen regular build: stage: build <<: *gitlab_caching diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index e74f4d6b13..67d80724b5 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -120,6 +120,7 @@ static void _librust_qstrs(void) { MP_QSTR_time_ms; MP_QSTR_app_name; MP_QSTR_icon_name; + MP_QSTR_account; MP_QSTR_accounts; MP_QSTR_indeterminate; MP_QSTR_notification; diff --git a/core/embed/rust/src/ui/model_tr/component/button.rs b/core/embed/rust/src/ui/model_tr/component/button.rs index 577eb59795..579feaeb8c 100644 --- a/core/embed/rust/src/ui/model_tr/component/button.rs +++ b/core/embed/rust/src/ui/model_tr/component/button.rs @@ -9,8 +9,6 @@ use crate::{ }, }; -use heapless::String; - use super::theme; const HALF_SCREEN_BUTTON_WIDTH: i16 = constant::WIDTH / 2 - 1; @@ -262,18 +260,6 @@ impl Component for Button { } } -#[cfg(feature = "ui_debug")] -impl crate::trace::Trace for Button { - fn trace(&self, t: &mut dyn crate::trace::Tracer) { - t.open("Button"); - match &self.content { - ButtonContent::Text(text) => t.field("text", text), - ButtonContent::Icon(icon) => t.field("icon", icon), - } - t.close(); - } -} - #[derive(PartialEq, Eq)] enum State { Released, @@ -453,7 +439,7 @@ impl ButtonDetails { self } - /// Default duration of the hold-to-confirm. + /// Default duration of the hold-to-confirm - 1 second. pub fn with_default_duration(mut self) -> Self { self.duration = Some(Duration::from_millis(1000)); self @@ -511,11 +497,11 @@ impl ButtonLayout { /// Default button layout for all three buttons - icons. pub fn default_three_icons() -> Self { - Self::three_icons_middle_text("SELECT".into()) + Self::arrow_armed_icon("SELECT".into()) } /// Special middle text for default icon layout. - pub fn three_icons_middle_text(middle_text: StrBuffer) -> Self { + pub fn arrow_armed_icon(middle_text: StrBuffer) -> Self { Self::new( Some(ButtonDetails::left_arrow_icon()), Some(ButtonDetails::armed_text(middle_text)), @@ -523,8 +509,35 @@ impl ButtonLayout { ) } + /// Left cancel, armed text and next right arrow. + pub fn cancel_armed_arrow(middle_text: StrBuffer) -> Self { + Self::new( + Some(ButtonDetails::cancel_icon()), + Some(ButtonDetails::armed_text(middle_text)), + Some(ButtonDetails::right_arrow_icon()), + ) + } + + /// Left cancel, armed text and right text. + pub fn cancel_armed_text(middle_text: StrBuffer, right_text: StrBuffer) -> Self { + Self::new( + Some(ButtonDetails::cancel_icon()), + Some(ButtonDetails::armed_text(middle_text)), + Some(ButtonDetails::text(right_text)), + ) + } + + /// Left back arrow and middle armed text. + pub fn arrow_armed_none(middle_text: StrBuffer) -> Self { + Self::new( + Some(ButtonDetails::left_arrow_icon()), + Some(ButtonDetails::armed_text(middle_text)), + None, + ) + } + /// Left and right texts. - pub fn left_right_text(text_left: StrBuffer, text_right: StrBuffer) -> Self { + pub fn text_none_text(text_left: StrBuffer, text_right: StrBuffer) -> Self { Self::new( Some(ButtonDetails::text(text_left)), None, @@ -533,12 +546,12 @@ impl ButtonLayout { } /// Only right text. - pub fn only_right_text(text_right: StrBuffer) -> Self { + pub fn none_none_text(text_right: StrBuffer) -> Self { Self::new(None, None, Some(ButtonDetails::text(text_right))) } /// Left and right arrow icons for navigation. - pub fn left_right_arrows() -> Self { + pub fn arrow_none_arrow() -> Self { Self::new( Some(ButtonDetails::left_arrow_icon()), None, @@ -546,8 +559,26 @@ impl ButtonLayout { ) } + /// Left arrow and right text. + pub fn arrow_none_text(text_right: StrBuffer) -> Self { + Self::new( + Some(ButtonDetails::left_arrow_icon()), + None, + Some(ButtonDetails::text(text_right)), + ) + } + + /// Up arrow left and right text. + pub fn up_arrow_none_text(text_right: StrBuffer) -> Self { + Self::new( + Some(ButtonDetails::up_arrow_icon()), + None, + Some(ButtonDetails::text(text_right)), + ) + } + /// Cancel cross on left and right arrow. - pub fn cancel_and_arrow() -> Self { + pub fn cancel_none_arrow() -> Self { Self::new( Some(ButtonDetails::cancel_icon()), None, @@ -556,7 +587,7 @@ impl ButtonLayout { } /// Cancel cross on left and right arrow facing down. - pub fn cancel_and_arrow_down() -> Self { + pub fn cancel_none_arrow_wide() -> Self { Self::new( Some(ButtonDetails::cancel_icon()), None, @@ -565,7 +596,7 @@ impl ButtonLayout { } /// Cancel cross on left and text on the right. - pub fn cancel_and_text(text: StrBuffer) -> Self { + pub fn cancel_none_text(text: StrBuffer) -> Self { Self::new( Some(ButtonDetails::cancel_icon()), None, @@ -574,39 +605,30 @@ impl ButtonLayout { } /// Cancel cross on left and hold-to-confirm text on the right. - pub fn cancel_and_htc_text(text: StrBuffer, duration: Duration) -> Self { + pub fn cancel_none_htc(text: StrBuffer) -> Self { Self::new( Some(ButtonDetails::cancel_icon()), None, - Some(ButtonDetails::text(text).with_duration(duration)), + Some(ButtonDetails::text(text).with_default_duration()), ) } /// Arrow back on left and hold-to-confirm text on the right. - pub fn back_and_htc_text(text: StrBuffer, duration: Duration) -> Self { + pub fn arrow_none_htc(text: StrBuffer) -> Self { Self::new( Some(ButtonDetails::left_arrow_icon()), None, - Some(ButtonDetails::text(text).with_duration(duration)), - ) - } - - /// Arrow back on left and text on the right. - pub fn back_and_text(text: StrBuffer) -> Self { - Self::new( - Some(ButtonDetails::left_arrow_icon()), - None, - Some(ButtonDetails::text(text)), + Some(ButtonDetails::text(text).with_default_duration()), ) } /// Only armed text in the middle. - pub fn middle_armed_text(text: StrBuffer) -> Self { + pub fn none_armed_none(text: StrBuffer) -> Self { Self::new(None, Some(ButtonDetails::armed_text(text)), None) } /// Only hold-to-confirm with text on the right. - pub fn htc_only(text: StrBuffer, duration: Duration) -> Self { + pub fn none_none_htc(text: StrBuffer, duration: Duration) -> Self { Self::new( None, None, @@ -614,30 +636,14 @@ impl ButtonLayout { ) } - /// Only right arrow facing down. - pub fn only_arrow_down() -> Self { - Self::new(None, None, Some(ButtonDetails::down_arrow_icon_wide())) + /// Only left arrow. + pub fn arrow_none_none() -> Self { + Self::new(Some(ButtonDetails::left_arrow_icon()), None, None) } -} -#[cfg(feature = "ui_debug")] -impl crate::trace::Trace for ButtonDetails { - fn trace(&self, t: &mut dyn crate::trace::Tracer) { - t.open("ButtonDetails"); - let mut btn_text: String<30> = String::new(); - if let Some(text) = &self.text { - btn_text.push_str(text.as_ref()).unwrap(); - } else if let Some(icon) = &self.icon { - btn_text.push_str("Icon:").unwrap(); - btn_text.push_str(icon.text.as_ref()).unwrap(); - } - if let Some(duration) = &self.duration { - btn_text.push_str(" (HTC:").unwrap(); - btn_text.push_str(inttostr!(duration.to_millis())).unwrap(); - btn_text.push_str(")").unwrap(); - } - t.button(btn_text.as_ref()); - t.close(); + /// Only right arrow facing down. + pub fn none_none_arrow_wide() -> Self { + Self::new(None, None, Some(ButtonDetails::down_arrow_icon_wide())) } } @@ -667,6 +673,195 @@ pub enum ButtonAction { Action(&'static str), } +/// Storing actions for all three possible buttons. +#[derive(Clone, Copy)] +pub struct ButtonActions { + pub left: Option, + pub middle: Option, + pub right: Option, +} + +impl ButtonActions { + pub fn new( + left: Option, + middle: Option, + right: Option, + ) -> Self { + Self { + left, + middle, + right, + } + } + + /// Going back with left, going further with right + pub fn prev_none_next() -> Self { + Self::new( + Some(ButtonAction::PrevPage), + None, + Some(ButtonAction::NextPage), + ) + } + + /// Going back with left, going further with middle + pub fn prev_next_none() -> Self { + Self::new( + Some(ButtonAction::PrevPage), + Some(ButtonAction::NextPage), + None, + ) + } + + /// Previous with left, confirming with right + pub fn prev_none_confirm() -> Self { + Self::new( + Some(ButtonAction::PrevPage), + None, + Some(ButtonAction::Confirm), + ) + } + + /// Previous with left, confirming with middle + pub fn prev_confirm_none() -> Self { + Self::new( + Some(ButtonAction::PrevPage), + Some(ButtonAction::Confirm), + None, + ) + } + + /// Going to last page with left, to the next page with right + pub fn last_none_next() -> Self { + Self::new( + Some(ButtonAction::GoToIndex(-1)), + None, + Some(ButtonAction::NextPage), + ) + } + + /// Going to last page with left, to the next page with right and confirm + /// with middle + pub fn last_confirm_next() -> Self { + Self::new( + Some(ButtonAction::GoToIndex(-1)), + Some(ButtonAction::Confirm), + Some(ButtonAction::NextPage), + ) + } + + /// Going to previous page with left, to the next page with right and + /// confirm with middle + pub fn prev_confirm_next() -> Self { + Self::new( + Some(ButtonAction::PrevPage), + Some(ButtonAction::Confirm), + Some(ButtonAction::NextPage), + ) + } + + /// Cancelling with left, going to the next page with right + pub fn cancel_none_next() -> Self { + Self::new( + Some(ButtonAction::Cancel), + None, + Some(ButtonAction::NextPage), + ) + } + + /// Only going to the next page with right + pub fn none_none_next() -> Self { + Self::new(None, None, Some(ButtonAction::NextPage)) + } + + /// Only going to the prev page with left + pub fn prev_none_none() -> Self { + Self::new(Some(ButtonAction::PrevPage), None, None) + } + + /// Cancelling with left, confirming with right + pub fn cancel_none_confirm() -> Self { + Self::new( + Some(ButtonAction::Cancel), + None, + Some(ButtonAction::Confirm), + ) + } + + /// Cancelling with left, confirming with middle and next with right + pub fn cancel_confirm_next() -> Self { + Self::new( + Some(ButtonAction::Cancel), + Some(ButtonAction::Confirm), + Some(ButtonAction::NextPage), + ) + } + + /// Going to the beginning with left, confirming with right + pub fn beginning_none_confirm() -> Self { + Self::new( + Some(ButtonAction::GoToIndex(0)), + None, + Some(ButtonAction::Confirm), + ) + } + + /// Going to the beginning with left, cancelling with right + pub fn beginning_none_cancel() -> Self { + Self::new( + Some(ButtonAction::GoToIndex(0)), + None, + Some(ButtonAction::Cancel), + ) + } + + /// Having access to appropriate action based on the `ButtonPos` + pub fn get_action(&self, pos: ButtonPos) -> Option { + match pos { + ButtonPos::Left => self.left, + ButtonPos::Middle => self.middle, + ButtonPos::Right => self.right, + } + } +} + +// DEBUG-ONLY SECTION BELOW + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Button { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.open("Button"); + match &self.content { + ButtonContent::Text(text) => t.field("text", text), + ButtonContent::Icon(icon) => t.field("icon", icon), + } + t.close(); + } +} + +#[cfg(feature = "ui_debug")] +use heapless::String; + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for ButtonDetails { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.open("ButtonDetails"); + let mut btn_text: String<30> = String::new(); + if let Some(text) = &self.text { + btn_text.push_str(text.as_ref()).unwrap(); + } else if let Some(icon) = &self.icon { + btn_text.push_str("Icon:").unwrap(); + btn_text.push_str(icon.text.as_ref()).unwrap(); + } + if let Some(duration) = &self.duration { + btn_text.push_str(" (HTC:").unwrap(); + btn_text.push_str(inttostr!(duration.to_millis())).unwrap(); + btn_text.push_str(")").unwrap(); + } + t.button(btn_text.as_ref()); + t.close(); + } +} + #[cfg(feature = "ui_debug")] impl ButtonAction { /// Describing the action as a string. Debug-only. @@ -697,126 +892,3 @@ impl ButtonAction { "None".into() } } - -/// Storing actions for all three possible buttons. -#[derive(Clone, Copy)] -pub struct ButtonActions { - pub left: Option, - pub middle: Option, - pub right: Option, -} - -impl ButtonActions { - pub fn new( - left: Option, - middle: Option, - right: Option, - ) -> Self { - Self { - left, - middle, - right, - } - } - - /// Going back with left, going further with right - pub fn prev_next() -> Self { - Self::new( - Some(ButtonAction::PrevPage), - None, - Some(ButtonAction::NextPage), - ) - } - - /// Going back with left, going further with middle - pub fn prev_next_with_middle() -> Self { - Self::new( - Some(ButtonAction::PrevPage), - Some(ButtonAction::NextPage), - None, - ) - } - - /// Previous with left, confirming with right - pub fn prev_confirm() -> Self { - Self::new( - Some(ButtonAction::PrevPage), - None, - Some(ButtonAction::Confirm), - ) - } - - /// Going to last page with left, to the next page with right - pub fn last_next() -> Self { - Self::new( - Some(ButtonAction::GoToIndex(-1)), - None, - Some(ButtonAction::NextPage), - ) - } - - /// Going to last page with left, to the next page with right and confirm - /// with middle - pub fn last_confirm_next() -> Self { - Self::new( - Some(ButtonAction::GoToIndex(-1)), - Some(ButtonAction::Confirm), - Some(ButtonAction::NextPage), - ) - } - - /// Cancelling with left, going to the next page with right - pub fn cancel_next() -> Self { - Self::new( - Some(ButtonAction::Cancel), - None, - Some(ButtonAction::NextPage), - ) - } - - /// Only going to the next page with right - pub fn only_next() -> Self { - Self::new(None, None, Some(ButtonAction::NextPage)) - } - - /// Only going to the prev page with left - pub fn only_prev() -> Self { - Self::new(Some(ButtonAction::PrevPage), None, None) - } - - /// Cancelling with left, confirming with right - pub fn cancel_confirm() -> Self { - Self::new( - Some(ButtonAction::Cancel), - None, - Some(ButtonAction::Confirm), - ) - } - - /// Going to the beginning with left, confirming with right - pub fn beginning_confirm() -> Self { - Self::new( - Some(ButtonAction::GoToIndex(0)), - None, - Some(ButtonAction::Confirm), - ) - } - - /// Going to the beginning with left, cancelling with right - pub fn beginning_cancel() -> Self { - Self::new( - Some(ButtonAction::GoToIndex(0)), - None, - Some(ButtonAction::Cancel), - ) - } - - /// Having access to appropriate action based on the `ButtonPos` - pub fn get_action(&self, pos: ButtonPos) -> Option { - match pos { - ButtonPos::Left => self.left, - ButtonPos::Middle => self.middle, - ButtonPos::Right => self.right, - } - } -} diff --git a/core/embed/rust/src/ui/model_tr/component/button_controller.rs b/core/embed/rust/src/ui/model_tr/component/button_controller.rs index e2f2507a29..453f680402 100644 --- a/core/embed/rust/src/ui/model_tr/component/button_controller.rs +++ b/core/embed/rust/src/ui/model_tr/component/button_controller.rs @@ -11,8 +11,6 @@ use crate::{ }, }; -use heapless::String; - /// All possible states buttons (left and right) can be at. #[derive(Copy, Clone, PartialEq, Eq)] enum ButtonState { @@ -435,8 +433,12 @@ impl Component for ButtonController { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] use super::ButtonContent; +#[cfg(feature = "ui_debug")] +use heapless::String; #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ButtonContainer { diff --git a/core/embed/rust/src/ui/model_tr/component/flow.rs b/core/embed/rust/src/ui/model_tr/component/flow.rs index 0c023ed3a2..38387008c6 100644 --- a/core/embed/rust/src/ui/model_tr/component/flow.rs +++ b/core/embed/rust/src/ui/model_tr/component/flow.rs @@ -14,6 +14,7 @@ use super::{ /// To be returned directly from Flow. pub enum FlowMsg { Confirmed, + ConfirmedIndex(u8), Cancelled, } @@ -30,6 +31,7 @@ pub struct Flow { pad: Pad, buttons: Child, page_counter: u8, + return_confirmed_index: bool, } impl Flow @@ -51,6 +53,7 @@ where // `content.page_count()`. buttons: Child::new(ButtonController::new(ButtonLayout::empty())), page_counter: 0, + return_confirmed_index: false, } } @@ -61,6 +64,12 @@ where self } + /// Causing the Flow to return the index of the page that was confirmed. + pub fn with_return_confirmed_index(mut self) -> Self { + self.return_confirmed_index = true; + self + } + /// Getting new current page according to page counter. /// Also updating the possible title and moving the scrollbar to correct /// position. @@ -222,7 +231,13 @@ where return None; } ButtonAction::Cancel => return Some(FlowMsg::Cancelled), - ButtonAction::Confirm => return Some(FlowMsg::Confirmed), + ButtonAction::Confirm => { + if self.return_confirmed_index { + return Some(FlowMsg::ConfirmedIndex(self.page_counter)); + } else { + return Some(FlowMsg::Confirmed); + } + } ButtonAction::Select => {} ButtonAction::Action(_) => {} } @@ -247,6 +262,8 @@ where } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] use heapless::String; diff --git a/core/embed/rust/src/ui/model_tr/component/flow_pages.rs b/core/embed/rust/src/ui/model_tr/component/flow_pages.rs index d00208bed5..329b28249e 100644 --- a/core/embed/rust/src/ui/model_tr/component/flow_pages.rs +++ b/core/embed/rust/src/ui/model_tr/component/flow_pages.rs @@ -332,6 +332,8 @@ impl Paginate for Page { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] pub mod trace { use crate::ui::model_tr::component::flow_pages_poc_helpers::TraceSink; diff --git a/core/embed/rust/src/ui/model_tr/component/frame.rs b/core/embed/rust/src/ui/model_tr/component/frame.rs index f24ee46916..96cd54bde8 100644 --- a/core/embed/rust/src/ui/model_tr/component/frame.rs +++ b/core/embed/rust/src/ui/model_tr/component/frame.rs @@ -96,6 +96,8 @@ where } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Frame where diff --git a/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs b/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs index 06ac22199b..64b743edba 100644 --- a/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs +++ b/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs @@ -108,6 +108,8 @@ impl Component for HoldToConfirm { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for HoldToConfirm { fn trace(&self, d: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/homescreen.rs b/core/embed/rust/src/ui/model_tr/component/homescreen.rs index d159cf0104..8c6769a918 100644 --- a/core/embed/rust/src/ui/model_tr/component/homescreen.rs +++ b/core/embed/rust/src/ui/model_tr/component/homescreen.rs @@ -85,17 +85,6 @@ impl Component for Homescreen { } } -#[cfg(feature = "ui_debug")] -impl crate::trace::Trace for Homescreen { - fn trace(&self, d: &mut dyn crate::trace::Tracer) { - d.open("Homescreen"); - d.kw_pair("active_page", "0"); - d.kw_pair("page_count", "1"); - d.field("label", &self.label.as_ref()); - d.close(); - } -} - pub struct Lockscreen { label: StrBuffer, bootscreen: bool, @@ -137,6 +126,19 @@ impl Component for Lockscreen { } } +// DEBUG-ONLY SECTION BELOW + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Homescreen { + fn trace(&self, d: &mut dyn crate::trace::Tracer) { + d.open("Homescreen"); + d.kw_pair("active_page", "0"); + d.kw_pair("page_count", "1"); + d.field("label", &self.label.as_ref()); + d.close(); + } +} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Lockscreen { fn trace(&self, d: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/bip39.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/bip39.rs index 613c17b858..2bc8779003 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/bip39.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/bip39.rs @@ -58,11 +58,8 @@ impl ChoiceFactory for ChoiceFactoryBIP39 { match self { Self::Letters(letter_choices) => { if choice_index >= letter_choices.len() as u8 { - ChoiceItem::new( - "DELETE", - ButtonLayout::three_icons_middle_text("CONFIRM".into()), - ) - .with_icon(Icon::new(theme::ICON_DELETE)) + ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_icon("CONFIRM".into())) + .with_icon(Icon::new(theme::ICON_DELETE)) } else { let letter = letter_choices[choice_index as usize]; ChoiceItem::new( @@ -73,11 +70,9 @@ impl ChoiceFactory for ChoiceFactoryBIP39 { } Self::Words(word_choices) => { if choice_index >= word_choices.len() as u8 { - let mut item = ChoiceItem::new( - "DELETE", - ButtonLayout::three_icons_middle_text("CONFIRM".into()), - ) - .with_icon(Icon::new(theme::ICON_DELETE)); + let mut item = + ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_icon("CONFIRM".into())) + .with_icon(Icon::new(theme::ICON_DELETE)); item.set_right_btn(None); item } else { @@ -223,6 +218,8 @@ impl Component for Bip39Entry { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] use super::super::{ButtonAction, ButtonPos}; #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs index 5d8e695d42..e6919642f5 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs @@ -421,6 +421,8 @@ where } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ChoicePage where diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs index fed3534c1d..68981a7661 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs @@ -198,6 +198,8 @@ impl Choice for ChoiceItem { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ChoiceItem { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs index 385015b189..7784e11596 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs @@ -135,10 +135,7 @@ impl ChoiceFactoryPassphrase { /// return back fn get_character_item(&self, choice_index: u8) -> ChoiceItem { if is_menu_choice(&self.current_category, choice_index) { - ChoiceItem::new( - "MENU", - ButtonLayout::three_icons_middle_text("RETURN".into()), - ) + ChoiceItem::new("MENU", ButtonLayout::arrow_armed_icon("RETURN".into())) } else { let ch = get_char(&self.current_category, choice_index); ChoiceItem::new(char_to_string::<1>(ch), ButtonLayout::default_three_icons()) @@ -304,6 +301,8 @@ impl Component for PassphraseEntry { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] use super::super::{ButtonAction, ButtonPos}; #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs index 63bed6ad40..71ed4dc12d 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs @@ -215,6 +215,8 @@ impl Component for PinEntry { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] use super::super::{ButtonAction, ButtonPos}; diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs index 832972aa4b..7a0a523169 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs @@ -9,9 +9,6 @@ use crate::{ use super::super::{ButtonLayout, ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg}; use heapless::{String, Vec}; -#[cfg(feature = "ui_debug")] -use super::super::{ButtonAction, ButtonPos}; - pub enum SimpleChoiceMsg { Result(String<50>), } @@ -95,6 +92,11 @@ impl Component for SimpleChoice { } } +// DEBUG-ONLY SECTION BELOW + +#[cfg(feature = "ui_debug")] +use super::super::{ButtonAction, ButtonPos}; + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for SimpleChoice { fn get_btn_action(&self, pos: ButtonPos) -> String<25> { diff --git a/core/embed/rust/src/ui/model_tr/component/loader.rs b/core/embed/rust/src/ui/model_tr/component/loader.rs index 2eb97dda96..60b6b4d478 100644 --- a/core/embed/rust/src/ui/model_tr/component/loader.rs +++ b/core/embed/rust/src/ui/model_tr/component/loader.rs @@ -241,6 +241,8 @@ impl LoaderStyleSheet { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Loader { fn trace(&self, d: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/mod.rs b/core/embed/rust/src/ui/model_tr/component/mod.rs index d6b91fa4b8..029ea05c68 100644 --- a/core/embed/rust/src/ui/model_tr/component/mod.rs +++ b/core/embed/rust/src/ui/model_tr/component/mod.rs @@ -13,7 +13,6 @@ mod loader; mod no_btn_dialog; mod page; mod progress; -mod qr_code; mod result_anim; mod result_popup; mod scrollbar; @@ -45,7 +44,6 @@ pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet}; pub use no_btn_dialog::{NoBtnDialog, NoBtnDialogMsg}; pub use page::ButtonPage; pub use progress::Progress; -pub use qr_code::{QRCodePage, QRCodePageMessage}; pub use result_anim::{ResultAnim, ResultAnimMsg}; pub use result_popup::{ResultPopup, ResultPopupMsg}; pub use scrollbar::ScrollBar; diff --git a/core/embed/rust/src/ui/model_tr/component/no_btn_dialog.rs b/core/embed/rust/src/ui/model_tr/component/no_btn_dialog.rs index 824a4f9b90..07cb284823 100644 --- a/core/embed/rust/src/ui/model_tr/component/no_btn_dialog.rs +++ b/core/embed/rust/src/ui/model_tr/component/no_btn_dialog.rs @@ -55,6 +55,8 @@ where } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for NoBtnDialog where diff --git a/core/embed/rust/src/ui/model_tr/component/page.rs b/core/embed/rust/src/ui/model_tr/component/page.rs index a3889c508b..4e94973740 100644 --- a/core/embed/rust/src/ui/model_tr/component/page.rs +++ b/core/embed/rust/src/ui/model_tr/component/page.rs @@ -217,6 +217,8 @@ where } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] use super::ButtonAction; #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tr/component/progress.rs b/core/embed/rust/src/ui/model_tr/component/progress.rs index bfb3a07139..156adb4bdc 100644 --- a/core/embed/rust/src/ui/model_tr/component/progress.rs +++ b/core/embed/rust/src/ui/model_tr/component/progress.rs @@ -120,6 +120,8 @@ impl Component for Progress { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Progress { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/qr_code.rs b/core/embed/rust/src/ui/model_tr/component/qr_code.rs deleted file mode 100644 index 1e5b0a229e..0000000000 --- a/core/embed/rust/src/ui/model_tr/component/qr_code.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{ - micropython::buffer::StrBuffer, - ui::{ - component::{Child, Component, Event, EventCtx}, - display::{self, Font}, - geometry::Rect, - }, -}; - -use super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos}; - -pub enum QRCodePageMessage { - Confirmed, - Cancelled, -} - -pub struct QRCodePage { - title: StrBuffer, - title_area: Rect, - qr_code: T, - buttons: Child, -} - -impl QRCodePage { - pub fn new(title: StrBuffer, qr_code: T, btn_layout: ButtonLayout) -> Self { - Self { - title, - title_area: Rect::zero(), - qr_code, - buttons: Child::new(ButtonController::new(btn_layout)), - } - } -} - -impl Component for QRCodePage -where - T: Component, -{ - type Msg = QRCodePageMessage; - - fn place(&mut self, bounds: Rect) -> Rect { - let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT); - let (qr_code_area, title_area) = content_area.split_left(theme::QR_SIDE_MAX); - self.title_area = title_area; - self.qr_code.place(qr_code_area); - self.buttons.place(button_area); - bounds - } - - fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - let button_event = self.buttons.event(ctx, event); - - if let Some(ButtonControllerMsg::Triggered(pos)) = button_event { - match pos { - ButtonPos::Left => { - return Some(QRCodePageMessage::Cancelled); - } - ButtonPos::Right => { - return Some(QRCodePageMessage::Confirmed); - } - _ => {} - } - } - - None - } - - fn paint(&mut self) { - self.qr_code.paint(); - // TODO: add the Label from Suite - display::text_multiline( - self.title_area, - self.title.as_ref(), - Font::MONO, - theme::FG, - theme::BG, - ); - self.buttons.paint(); - } -} - -#[cfg(feature = "ui_debug")] -use super::ButtonAction; -#[cfg(feature = "ui_debug")] -use heapless::String; - -#[cfg(feature = "ui_debug")] -impl crate::trace::Trace for QRCodePage { - fn get_btn_action(&self, pos: ButtonPos) -> String<25> { - match pos { - ButtonPos::Left => ButtonAction::Cancel.string(), - ButtonPos::Right => ButtonAction::Confirm.string(), - ButtonPos::Middle => ButtonAction::empty(), - } - } - - fn trace(&self, t: &mut dyn crate::trace::Tracer) { - t.open("QRCodePage"); - t.kw_pair("active_page", "0"); - t.kw_pair("page_count", "1"); - self.report_btn_actions(t); - t.content_flag(); - t.string("QR CODE"); - t.string(self.title.as_ref()); - t.content_flag(); - t.field("buttons", &self.buttons); - t.close(); - } -} diff --git a/core/embed/rust/src/ui/model_tr/component/result_anim.rs b/core/embed/rust/src/ui/model_tr/component/result_anim.rs index 9cd03a0259..b1ca0512d9 100644 --- a/core/embed/rust/src/ui/model_tr/component/result_anim.rs +++ b/core/embed/rust/src/ui/model_tr/component/result_anim.rs @@ -140,6 +140,8 @@ impl Component for ResultAnim { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ResultAnim { fn trace(&self, d: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/result_popup.rs b/core/embed/rust/src/ui/model_tr/component/result_popup.rs index 22c5774a1e..d543189e18 100644 --- a/core/embed/rust/src/ui/model_tr/component/result_popup.rs +++ b/core/embed/rust/src/ui/model_tr/component/result_popup.rs @@ -49,7 +49,7 @@ impl ResultPopup { .with_placement(LinearPlacement::vertical().align_at_center()); let buttons = button_text.map(|text| { - let btn_layout = ButtonLayout::only_right_text(text.into()); + let btn_layout = ButtonLayout::none_none_text(text.into()); Child::new(ButtonController::new(btn_layout)) }); @@ -155,6 +155,8 @@ impl Component for ResultPopup { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ResultPopup { fn trace(&self, d: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/component/share_words.rs b/core/embed/rust/src/ui/model_tr/component/share_words.rs index 8a1675ee10..73b7087c78 100644 --- a/core/embed/rust/src/ui/model_tr/component/share_words.rs +++ b/core/embed/rust/src/ui/model_tr/component/share_words.rs @@ -142,6 +142,8 @@ impl Paginate for ShareWords { } } +// DEBUG-ONLY SECTION BELOW + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ShareWords { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 19b40969df..0b11e6b050 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -6,14 +6,15 @@ use crate::{ error::Error, micropython::{ buffer::StrBuffer, + gc::Gc, iter::{Iter, IterBuf}, + list::List, map::Map, module::Module, obj::Obj, qstr::Qstr, util, }, - time::Duration, ui::{ component::{ base::Component, @@ -38,7 +39,7 @@ use super::{ Bip39Entry, Bip39EntryMsg, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, Flow, FlowMsg, FlowPages, Frame, Homescreen, HomescreenMsg, Lockscreen, NoBtnDialog, NoBtnDialogMsg, Page, PassphraseEntry, PassphraseEntryMsg, PinEntry, PinEntryMsg, Progress, - QRCodePage, QRCodePageMessage, ShareWords, SimpleChoice, SimpleChoiceMsg, + ShareWords, SimpleChoice, SimpleChoiceMsg, }, constant, theme, }; @@ -94,18 +95,7 @@ where match msg { FlowMsg::Confirmed => Ok(CONFIRMED.as_obj()), FlowMsg::Cancelled => Ok(CANCELLED.as_obj()), - } - } -} - -impl ComponentMsgObj for QRCodePage -where - T: Component, -{ - fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { - match msg { - QRCodePageMessage::Confirmed => Ok(CONFIRMED.as_obj()), - QRCodePageMessage::Cancelled => Ok(CANCELLED.as_obj()), + FlowMsg::ConfirmedIndex(page) => Ok(page.into()), } } } @@ -181,11 +171,13 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M let action: Option = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?; let description: Option = kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let verb: Option = kwargs.get(Qstr::MP_QSTR_verb)?.try_into_option()?; - let verb_cancel: Option = - kwargs.get(Qstr::MP_QSTR_verb_cancel)?.try_into_option()?; - let reverse: bool = kwargs.get(Qstr::MP_QSTR_reverse)?.try_into()?; - let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?; + let verb: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_verb, "CONFIRM".into())?; + let verb_cancel: Option = kwargs + .get(Qstr::MP_QSTR_verb_cancel) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; + let reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?; + let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let paragraphs = { let action = action.unwrap_or_default(); @@ -215,7 +207,6 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M }; // Right button - text or nothing. - let verb = verb.unwrap_or_default(); let mut confirm_btn = if verb.len() > 0 { Some(ButtonDetails::text(verb)) } else { @@ -293,24 +284,16 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M match page_index { 0 => { // RECIPIENT + address - let btn_layout = ButtonLayout::new( - Some(ButtonDetails::cancel_icon()), - None, - Some(ButtonDetails::text("CONFIRM".into())), - ); - let btn_actions = ButtonActions::cancel_next(); + let btn_layout = ButtonLayout::cancel_none_text("CONFIRM".into()); + let btn_actions = ButtonActions::cancel_none_next(); Page::<10>::new(btn_layout, btn_actions, Font::MONO) .with_title(address_title) .text_mono(address) } 1 => { // AMOUNT + amount - let btn_layout = ButtonLayout::new( - Some(ButtonDetails::up_arrow_icon()), - None, - Some(ButtonDetails::text("CONFIRM".into())), - ); - let btn_actions = ButtonActions::cancel_confirm(); + let btn_layout = ButtonLayout::up_arrow_none_text("CONFIRM".into()); + let btn_actions = ButtonActions::prev_none_confirm(); Page::<10>::new(btn_layout, btn_actions, Font::MONO) .with_title(amount_title) .newline() @@ -341,12 +324,8 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma // Total amount + fee assert!(page_index == 0); - let btn_layout = ButtonLayout::new( - Some(ButtonDetails::cancel_icon()), - None, - Some(ButtonDetails::text("HOLD TO CONFIRM".into()).with_default_duration()), - ); - let btn_actions = ButtonActions::cancel_confirm(); + let btn_layout = ButtonLayout::cancel_none_htc("HOLD TO CONFIRM".into()); + let btn_actions = ButtonActions::cancel_none_confirm(); let mut flow_page = Page::<15>::new(btn_layout, btn_actions, Font::MONO) .text_bold(total_label) @@ -386,11 +365,7 @@ extern "C" fn new_show_receive_address(n_args: usize, args: *const Obj, kwargs: match page_index { 0 => { // RECEIVE ADDRESS - let btn_layout = ButtonLayout::new( - Some(ButtonDetails::cancel_icon()), - Some(ButtonDetails::armed_text("CONFIRM".into())), - Some(ButtonDetails::text("i".into())), - ); + let btn_layout = ButtonLayout::cancel_armed_text("CONFIRM".into(), "i".into()); let btn_actions = ButtonActions::last_confirm_next(); Page::<15>::new(btn_layout, btn_actions, Font::BOLD) .text_bold(title) @@ -400,12 +375,8 @@ extern "C" fn new_show_receive_address(n_args: usize, args: *const Obj, kwargs: } 1 => { // QR CODE - let btn_layout = ButtonLayout::new( - Some(ButtonDetails::left_arrow_icon()), - None, - Some(ButtonDetails::right_arrow_icon()), - ); - let btn_actions = ButtonActions::prev_next(); + let btn_layout = ButtonLayout::arrow_none_arrow(); + let btn_actions = ButtonActions::prev_none_next(); Page::<15>::new(btn_layout, btn_actions, Font::MONO).qr_code( address_qr, theme::QR_SIDE_MAX, @@ -415,9 +386,8 @@ extern "C" fn new_show_receive_address(n_args: usize, args: *const Obj, kwargs: } 2 => { // ADDRESS INFO - let btn_layout = - ButtonLayout::new(Some(ButtonDetails::left_arrow_icon()), None, None); - let btn_actions = ButtonActions::only_prev(); + let btn_layout = ButtonLayout::arrow_none_none(); + let btn_actions = ButtonActions::prev_none_none(); Page::<15>::new(btn_layout, btn_actions, Font::MONO) .text_bold("Account:".into()) .newline() @@ -429,12 +399,8 @@ extern "C" fn new_show_receive_address(n_args: usize, args: *const Obj, kwargs: } 3 => { // ADDRESS MISMATCH - let btn_layout = ButtonLayout::new( - Some(ButtonDetails::left_arrow_icon()), - None, - Some(ButtonDetails::text("QUIT".into())), - ); - let btn_actions = ButtonActions::beginning_cancel(); + let btn_layout = ButtonLayout::arrow_none_text("QUIT".into()); + let btn_actions = ButtonActions::beginning_none_cancel(); Page::<15>::new(btn_layout, btn_actions, Font::MONO) .text_bold("ADDRESS MISMATCH?".into()) .newline() @@ -482,6 +448,73 @@ extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let app_name: StrBuffer = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; + let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; + + // Cache the page count so that we can move `accounts` into the closure. + let page_count = accounts.len(); + + let title: StrBuffer = if page_count > 1 { + "IMPORT".into() + } else { + "IMPORT CREDENTIAL".into() + }; + + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let get_page = move |page_index| { + let account_obj = unwrap!(accounts.get(page_index as usize)); + let account = account_obj.try_into().unwrap_or_else(|_| "".into()); + + let (btn_layout, btn_actions) = if page_count == 1 { + // There is only one page + ( + ButtonLayout::cancel_none_text("CONFIRM".into()), + ButtonActions::cancel_none_confirm(), + ) + } else if page_index == 0 { + // First page + ( + ButtonLayout::cancel_armed_arrow("SELECT".into()), + ButtonActions::cancel_confirm_next(), + ) + } else if page_index as usize == page_count - 1 { + // Last page + ( + ButtonLayout::arrow_armed_none("SELECT".into()), + ButtonActions::prev_confirm_none(), + ) + } else { + // Page in the middle + ( + ButtonLayout::arrow_armed_icon("SELECT".into()), + ButtonActions::prev_confirm_next(), + ) + }; + + Page::<10>::new(btn_layout, btn_actions, Font::MONO) + .newline() + .text_mono(app_name) + .newline() + .text_bold(account) + }; + + let pages = FlowPages::new(get_page, page_count as u8); + + // Returning the page index in case of confirmation. + let obj = LayoutObj::new( + Flow::new(pages) + .with_common_title(title) + .with_return_confirmed_index(), + )?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + /// General pattern of most tutorial screens. /// (title, text, btn_layout, btn_actions) fn tutorial_screen( @@ -523,47 +556,47 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj tutorial_screen( "HELLO".into(), "Welcome to Trezor.\nPress right to continue.".into(), - ButtonLayout::cancel_and_arrow(), - ButtonActions::last_next(), + ButtonLayout::cancel_none_arrow(), + ButtonActions::last_none_next(), ) }, 1 => { tutorial_screen( "".into(), "Use Trezor by clicking left and right.\n\nContinue right.".into(), - ButtonLayout::left_right_arrows(), - ButtonActions::prev_next(), + ButtonLayout::arrow_none_arrow(), + ButtonActions::prev_none_next(), ) }, 2 => { tutorial_screen( "HOLD TO CONFIRM".into(), "Press and hold right to approve important operations.".into(), - ButtonLayout::back_and_htc_text("HOLD TO CONFIRM".into(), Duration::from_millis(1000)), - ButtonActions::prev_next(), + ButtonLayout::arrow_none_htc("HOLD TO CONFIRM".into()), + ButtonActions::prev_none_next(), ) }, 3 => { tutorial_screen( "SCREEN SCROLL".into(), "Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.".into(), - ButtonLayout::back_and_text("GOT IT".into()), - ButtonActions::prev_next(), + ButtonLayout::arrow_none_text("GOT IT".into()), + ButtonActions::prev_none_next(), ) }, 4 => { tutorial_screen( "CONFIRM".into(), "Press both left and right at the same time to confirm.".into(), - ButtonLayout::middle_armed_text("CONFIRM".into()), - ButtonActions::prev_next_with_middle(), + ButtonLayout::none_armed_none("CONFIRM".into()), + ButtonActions::prev_next_none(), ) }, // This page is special 5 => { Page::<10>::new( - ButtonLayout::left_right_text("AGAIN".into(), "FINISH".into()), - ButtonActions::beginning_confirm(), + ButtonLayout::text_none_text("AGAIN".into(), "FINISH".into()), + ButtonActions::beginning_none_confirm(), Font::MONO, ) .newline() @@ -577,8 +610,8 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj tutorial_screen( "SKIP TUTORIAL".into(), "Are you sure you want to skip the tutorial?".into(), - ButtonLayout::cancel_and_text("SKIP".into()), - ButtonActions::beginning_cancel(), + ButtonLayout::cancel_none_text("SKIP".into()), + ButtonActions::beginning_none_cancel(), ) }, _ => unreachable!(), @@ -773,9 +806,9 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def confirm_action( /// *, /// title: str, - /// action: str | None = None, - /// description: str | None = None, - /// verb: str | None = None, + /// action: str | None, + /// description: str | None, + /// verb: str = "CONFIRM", /// verb_cancel: str | None = None, /// hold: bool = False, /// hold_danger: bool = False, # unused on TR @@ -837,6 +870,17 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Info modal.""" Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(), + /// def confirm_fido( + /// *, + /// app_name: str, + /// accounts: list[str | None], + /// ) -> int | object: + /// """FIDO confirmation. + /// + /// Returns page index in case of confirmation and CANCELLED otherwise. + /// """ + Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), + /// def tutorial() -> object: /// """Show user how to interact with the device.""" Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(), diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 6300085d17..f6d3571f4d 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1330,7 +1330,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// title: str, /// action: str | None, /// description: str | None, - /// verb: str | None = None, + /// verb: str = "CONFIRM", /// verb_cancel: str | None = None, /// hold: bool = False, /// hold_danger: bool = False, diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index f516f4d8fe..b9a93d9058 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -13,9 +13,9 @@ def disable_animation(disable: bool) -> None: def confirm_action( *, title: str, - action: str | None = None, - description: str | None = None, - verb: str | None = None, + action: str | None, + description: str | None, + verb: str = "CONFIRM", verb_cancel: str | None = None, hold: bool = False, hold_danger: bool = False, # unused on TR @@ -82,6 +82,17 @@ def show_info( """Info modal.""" +# rust/src/ui/model_tr/layout.rs +def confirm_fido( + *, + app_name: str, + accounts: list[str | None], +) -> int | object: + """FIDO confirmation. + Returns page index in case of confirmation and CANCELLED otherwise. + """ + + # rust/src/ui/model_tr/layout.rs def tutorial() -> object: """Show user how to interact with the device.""" @@ -197,7 +208,7 @@ def confirm_action( title: str, action: str | None, description: str | None, - verb: str | None = None, + verb: str = "CONFIRM", verb_cancel: str | None = None, hold: bool = False, hold_danger: bool = False, diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index c659425a0b..6bd9257c9d 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -115,7 +115,7 @@ class Approver: self, txo: TxOutput, script_pubkey: bytes, - index: int | None, + index: int | None = None, orig_txo: TxOutput | None = None, ) -> None: self._add_output(txo, script_pubkey) @@ -169,7 +169,7 @@ class BasicApprover(Approver): self, txo: TxOutput, script_pubkey: bytes, - index: int | None, + index: int | None = None, orig_txo: TxOutput | None = None, ) -> None: from trezor.enums import OutputScriptType diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index 9a61a2363e..4d7cab2916 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -469,7 +469,7 @@ async def get_bool( title: str, data: str | None = None, description: str | None = None, - verb: str | None = "CONFIRM", + verb: str = "CONFIRM", verb_cancel: str | None = "", hold: bool = False, reverse: bool = False, @@ -723,7 +723,7 @@ async def _show_modal( title=header.upper(), action=subheader, description=content, - verb=button_confirm, + verb=button_confirm or "", verb_cancel=button_cancel, exc=exc, ) diff --git a/core/src/trezor/ui/layouts/tr/fido.py b/core/src/trezor/ui/layouts/tr/fido.py index 9a09006cfa..1c7279f167 100644 --- a/core/src/trezor/ui/layouts/tr/fido.py +++ b/core/src/trezor/ui/layouts/tr/fido.py @@ -1,5 +1,12 @@ from typing import TYPE_CHECKING +from trezor.enums import ButtonRequestType + +import trezorui2 + +from ..common import interact +from . import RustLayout + if TYPE_CHECKING: from trezor.wire import GenericContext @@ -14,8 +21,37 @@ async def confirm_fido( accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - raise NotImplementedError + confirm = RustLayout( + trezorui2.confirm_fido( # type: ignore [Arguments missing] + app_name=app_name, + accounts=accounts, + ) + ) + + if ctx is None: + result = await confirm + else: + result = await interact(ctx, confirm, "confirm_fido", ButtonRequestType.Other) + + # The Rust side returns either an int or `CANCELLED`. We detect the int situation + # and assume cancellation otherwise. + if isinstance(result, int): + return result + + # Late import won't get executed on the happy path. + from trezor.wire import ActionCancelled + + raise ActionCancelled async def confirm_fido_reset() -> bool: - raise NotImplementedError + confirm = RustLayout( + trezorui2.confirm_action( + title="FIDO2 RESET", + description="Do you really want to erase all credentials?", + action=None, + verb_cancel="", + verb="CONFIRM", + ) + ) + return (await confirm) is trezorui2.CONFIRMED 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 fef7c997aa..20bf91cb14 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py @@ -95,13 +95,13 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1), True, helpers.UiConfirmTotal(12300000, 11000, fee_rate, coin, AmountUnit.BITCOIN), @@ -229,7 +229,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), 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 a10d6c482e..bd7c2ea45d 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 @@ -93,13 +93,13 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1), True, helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False), @@ -227,7 +227,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), 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 d643cd1284..da084c8d7e 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 @@ -92,13 +92,13 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1), True, helpers.UiConfirmTotal(123445789 + 11000, 11000, fee_rate, coin, AmountUnit.BITCOIN), @@ -223,7 +223,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), @@ -371,7 +371,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), 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 90e0849055..920fcd557d 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 @@ -93,13 +93,13 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1), True, helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False), @@ -226,7 +226,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=1, tx_hash=None), serialized=EMPTY_SERIALIZED), diff --git a/core/tests/test_apps.bitcoin.signtx.fee_threshold.py b/core/tests/test_apps.bitcoin.signtx.fee_threshold.py index bbd10a441e..9ff348d9db 100644 --- a/core/tests/test_apps.bitcoin.signtx.fee_threshold.py +++ b/core/tests/test_apps.bitcoin.signtx.fee_threshold.py @@ -85,7 +85,7 @@ class TestSignTxFeeThreshold(unittest.TestCase): TxAckPrevOutput(tx=TxAckPrevOutputWrapper(output=pout1)), TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=None), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0), True, helpers.UiConfirmFeeOverThreshold(100000, coin_bitcoin), True, @@ -146,7 +146,7 @@ class TestSignTxFeeThreshold(unittest.TestCase): True, TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0), True, helpers.UiConfirmTotal(300000 + 90000, 90000, fee_rate, coin_bitcoin, AmountUnit.BITCOIN), True, diff --git a/core/tests/test_apps.bitcoin.signtx.py b/core/tests/test_apps.bitcoin.signtx.py index 34a46dc9de..cbd4efa52a 100644 --- a/core/tests/test_apps.bitcoin.signtx.py +++ b/core/tests/test_apps.bitcoin.signtx.py @@ -111,7 +111,7 @@ class TestSignTx(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0), True, helpers.UiConfirmTotal(3_801_747, 50_000, fee_rate, coin_bitcoin, AmountUnit.BITCOIN), True, diff --git a/core/tests/test_apps.bitcoin.signtx_decred.py b/core/tests/test_apps.bitcoin.signtx_decred.py index 080ec5d4fb..e4b50f5a31 100644 --- a/core/tests/test_apps.bitcoin.signtx_decred.py +++ b/core/tests/test_apps.bitcoin.signtx_decred.py @@ -109,7 +109,7 @@ class TestSignTxDecred(unittest.TestCase): ), ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0), True, helpers.UiConfirmTotal( 200_000_000, 100_000, fee_rate, coin_decred, AmountUnit.BITCOIN diff --git a/core/tests/test_apps.bitcoin.signtx_grs.py b/core/tests/test_apps.bitcoin.signtx_grs.py index 553cfd387e..8764763322 100644 --- a/core/tests/test_apps.bitcoin.signtx_grs.py +++ b/core/tests/test_apps.bitcoin.signtx_grs.py @@ -70,7 +70,7 @@ class TestSignTx_GRS(unittest.TestCase): TxAckInput(tx=TxAckInputWrapper(input=inp1)), TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0), True, helpers.UiConfirmTotal(210016, 192, fee_rate, coin, AmountUnit.BITCOIN), True, diff --git a/tests/device_tests/webauthn/test_msg_webauthn.py b/tests/device_tests/webauthn/test_msg_webauthn.py index 4cbaaa981e..7c02c27cfd 100644 --- a/tests/device_tests/webauthn/test_msg_webauthn.py +++ b/tests/device_tests/webauthn/test_msg_webauthn.py @@ -30,9 +30,6 @@ RK_CAPACITY = 100 @pytest.mark.altcoin @pytest.mark.setup_client(mnemonic=MNEMONIC12) def test_add_remove(client: Client): - if client.features.model == "R": - pytest.skip("Webauthn is not supported on model R") - # Remove index 0 should fail. with pytest.raises(TrezorFailure): fido.remove_credential(client, 0)