From 002976e16dd7aff3cd3295831d66cf770ad0c529 Mon Sep 17 00:00:00 2001 From: grdddj Date: Thu, 19 Jan 2023 17:04:12 +0100 Subject: [PATCH] WIP - move debug-trace processing logic to Debuglink --- common/protob/messages-debug.proto | 1 - core/embed/rust/src/trace.rs | 6 +- core/embed/rust/src/ui/layout/obj.rs | 4 +- .../rust/src/ui/model_tr/component/flow.rs | 37 +-- .../src/ui/model_tr/component/flow_pages.rs | 18 +- .../component/input_methods/choice.rs | 33 +-- .../component/input_methods/number_input.rs | 8 +- .../component/input_methods/passphrase.rs | 34 +-- .../model_tr/component/input_methods/pin.rs | 30 +- .../component/input_methods/simple_choice.rs | 16 +- .../component/input_methods/wordlist.rs | 14 +- .../rust/src/ui/model_tr/component/page.rs | 4 +- .../src/ui/model_tr/component/scrollbar.rs | 4 +- core/embed/rust/src/ui/model_tr/layout.rs | 11 +- core/src/apps/debug/__init__.py | 3 - core/src/trezor/enums/DebugSwipeDirection.py | 1 - core/src/trezor/enums/__init__.py | 1 - core/src/trezor/ui/__init__.py | 1 - core/src/trezor/ui/layouts/common.py | 9 +- core/src/trezor/ui/layouts/tr/__init__.py | 172 ++---------- core/src/trezor/ui/layouts/tt_v2/__init__.py | 1 + python/src/trezorlib/debuglink.py | 261 +++++++++++++----- python/src/trezorlib/messages.py | 1 - tests/click_tests/recovery.py | 22 +- tests/click_tests/reset.py | 26 +- tests/click_tests/test_autolock.py | 42 +-- tests/click_tests/test_lock.py | 4 +- tests/common.py | 49 +--- tests/device_tests/test_sdcard.py | 24 +- tests/input_flows.py | 104 +++---- .../test_shamir_persistence.py | 24 +- tests/upgrade_tests/test_firmware_upgrades.py | 6 +- 32 files changed, 445 insertions(+), 526 deletions(-) diff --git a/common/protob/messages-debug.proto b/common/protob/messages-debug.proto index d64f26129b..8968c30108 100644 --- a/common/protob/messages-debug.proto +++ b/common/protob/messages-debug.proto @@ -28,7 +28,6 @@ message DebugLinkDecision { DOWN = 1; LEFT = 2; RIGHT = 3; - ALL_THE_WAY_UP = 4; } /** diff --git a/core/embed/rust/src/trace.rs b/core/embed/rust/src/trace.rs index 0627f0520e..3980f9edad 100644 --- a/core/embed/rust/src/trace.rs +++ b/core/embed/rust/src/trace.rs @@ -14,7 +14,7 @@ pub trait Tracer { fn title(&mut self, title: &str); fn button(&mut self, button: &str); fn content_flag(&mut self); - fn kw_pair(&mut self, key: &str, value: &str); + fn kw_pair(&mut self, key: &str, value: &dyn Trace); fn close(&mut self); } @@ -143,10 +143,10 @@ mod tests { } /// Key-value pair for easy parsing - fn kw_pair(&mut self, key: &str, value: &str) { + fn kw_pair(&mut self, key: &str, value: &dyn Trace) { self.string(key); self.string("::"); - self.string(value); + value.trace(self); self.string(","); // mostly for human readability } diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 836ab3233a..00c55ed808 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -271,10 +271,10 @@ impl LayoutObj { self.string(crate::trace::CONTENT_TAG); } - fn kw_pair(&mut self, key: &str, value: &str) { + fn kw_pair(&mut self, key: &str, value: &dyn Trace) { self.string(key); self.string("::"); - self.string(value); + value.trace(self); self.string(","); // mostly for human readability } 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 0c9e4a38bf..bbce9ebab5 100644 --- a/core/embed/rust/src/ui/model_tr/component/flow.rs +++ b/core/embed/rust/src/ui/model_tr/component/flow.rs @@ -15,7 +15,7 @@ use super::{ /// To be returned directly from Flow. pub enum FlowMsg { Confirmed, - ConfirmedIndex(u8), + ConfirmedIndex(usize), Cancelled, } @@ -31,13 +31,13 @@ pub struct Flow { title_area: Rect, pad: Pad, buttons: Child, - page_counter: u8, + page_counter: usize, return_confirmed_index: bool, } impl Flow where - F: Fn(u8) -> Page, + F: Fn(usize) -> Page, { pub fn new(pages: FlowPages) -> Self { let current_page = pages.get(0); @@ -123,16 +123,16 @@ where /// Negative index means counting from the end. fn go_to_page_absolute(&mut self, index: i16, ctx: &mut EventCtx) { if index < 0 { - self.page_counter = (self.pages.count() as i16 + index) as u8; + self.page_counter = (self.pages.count() as i16 + index) as usize; } else { - self.page_counter = index as u8; + self.page_counter = index as usize; } self.update(ctx, true); } /// Jumping to another page relative to the current one. fn go_to_page_relative(&mut self, jump: i16, ctx: &mut EventCtx) { - self.page_counter = (self.page_counter as i16 + jump) as u8; + self.page_counter = (self.page_counter as i16 + jump) as usize; self.update(ctx, true); } @@ -176,7 +176,7 @@ where impl Component for Flow where - F: Fn(u8) -> Page, + F: Fn(usize) -> Page, { type Msg = FlowMsg; @@ -190,14 +190,15 @@ where }; self.content_area = content_area; - // Placing a scrollbar in case the title is there - if self.title.is_some() { - // Finding out the total amount of pages in this flow - let complete_page_count = self.pages.scrollbar_page_count(content_area); - self.scrollbar - .inner_mut() - .set_page_count(complete_page_count); + // Finding out the total amount of pages in this flow + let complete_page_count = self.pages.scrollbar_page_count(content_area); + self.scrollbar + .inner_mut() + .set_page_count(complete_page_count); + // Placing a title and scrollbar in case the title is there + // (scrollbar will be active - counting pages - even when not placed and painted) + if self.title.is_some() { let (title_area, scrollbar_area) = title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE); @@ -288,7 +289,7 @@ use heapless::String; #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Flow where - F: Fn(u8) -> Page, + F: Fn(usize) -> Page, { /// Accounting for the possibility that button is connected with the /// currently paginated flow_page (only Prev or Next in that case). @@ -309,15 +310,15 @@ where fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("Flow"); - t.kw_pair("flow_page", inttostr!(self.page_counter)); - t.kw_pair("flow_page_count", inttostr!(self.pages.count())); + t.kw_pair("flow_page", &self.page_counter); + t.kw_pair("flow_page_count", &self.pages.count()); self.report_btn_actions(t); if self.title.is_some() { t.field("title", &self.title); } - t.field("content_area", &self.content_area); + t.field("scrollbar", &self.scrollbar); t.field("buttons", &self.buttons); t.field("flow_page", &self.current_page); t.close() 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 be4c8bd9d4..ffa202de18 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 @@ -32,14 +32,14 @@ pub struct FlowPages { /// Function/closure that will return appropriate page on demand. get_page: F, /// Number of pages in the flow. - page_count: u8, + page_count: usize, } impl FlowPages where - F: Fn(u8) -> Page, + F: Fn(usize) -> Page, { - pub fn new(get_page: F, page_count: u8) -> Self { + pub fn new(get_page: F, page_count: usize) -> Self { Self { get_page, page_count, @@ -47,12 +47,12 @@ where } /// Returns a page on demand on a specified index. - pub fn get(&self, page_index: u8) -> Page { + pub fn get(&self, page_index: usize) -> Page { (self.get_page)(page_index) } /// Total amount of pages. - pub fn count(&self) -> u8 { + pub fn count(&self) -> usize { self.page_count } @@ -63,8 +63,8 @@ where } /// Active scrollbar position connected with the beginning of a specific - /// page index - pub fn scrollbar_page_index(&self, bounds: Rect, page_index: u8) -> usize { + /// page index. + pub fn scrollbar_page_index(&self, bounds: Rect, page_index: usize) -> usize { let mut page_count = 0; for i in 0..page_index { let mut current_page = self.get(i); @@ -364,8 +364,8 @@ pub mod trace { impl crate::trace::Trace for Page { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("Page"); - t.kw_pair("active_page", inttostr!(self.current_page as u8)); - t.kw_pair("page_count", inttostr!(self.page_count as u8)); + t.kw_pair("active_page", &self.current_page); + t.kw_pair("page_count", &self.page_count); t.field("content", &trace::TraceText(self)); t.close(); } 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 77d3df4f7c..7705772353 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 @@ -8,7 +8,7 @@ use crate::ui::{ use super::super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos}; pub enum ChoicePageMsg { - Choice(u8), + Choice(usize), LeftMost, RightMost, } @@ -46,8 +46,8 @@ pub trait ChoiceFactory { type Item: Choice + Trace; #[cfg(not(feature = "ui_debug"))] type Item: Choice; - fn count(&self) -> u8; - fn get(&self, index: u8) -> Self::Item; + fn count(&self) -> usize; + fn get(&self, index: usize) -> Self::Item; } /// General component displaying a set of items on the screen @@ -70,7 +70,7 @@ where choices: F, pad: Pad, buttons: Child, - page_counter: u8, + page_counter: usize, /// How many pixels from top should we render the items. y_baseline: i16, /// How many pixels are between the items. @@ -110,7 +110,7 @@ where /// Set the page counter at the very beginning. /// Need to update the initial button layout. - pub fn with_initial_page_counter(mut self, page_counter: u8) -> Self { + pub fn with_initial_page_counter(mut self, page_counter: usize) -> Self { self.page_counter = page_counter; let initial_btn_layout = self.choices.get(page_counter).btn_layout(); self.buttons = Child::new(ButtonController::new(initial_btn_layout)); @@ -156,7 +156,7 @@ where &mut self, ctx: &mut EventCtx, new_choices: F, - new_page_counter: Option, + new_page_counter: Option, is_carousel: bool, ) { self.choices = new_choices; @@ -168,7 +168,7 @@ where } /// Navigating to the chosen page index. - pub fn set_page_counter(&mut self, ctx: &mut EventCtx, page_counter: u8) { + pub fn set_page_counter(&mut self, ctx: &mut EventCtx, page_counter: usize) { self.page_counter = page_counter; self.update(ctx); } @@ -188,7 +188,7 @@ where // Getting the remaining left and right areas. let (left_area, _center_area, right_area) = - available_area.split_center(self.choices.get(self.page_counter as u8).width_center()); + available_area.split_center(self.choices.get(self.page_counter).width_center()); // Possibly drawing on the left side. if self.has_previous_choice() || self.is_carousel { @@ -214,8 +214,8 @@ where } /// Index of the last page. - fn last_page_index(&self) -> u8 { - self.choices.count() as u8 - 1 + fn last_page_index(&self) -> usize { + self.choices.count() - 1 } /// Whether there is a previous choice (on the left). @@ -243,6 +243,7 @@ where /// Display all the choices fitting on the left side. /// Going as far as possible. fn show_left_choices(&self, area: Rect) { + // page index can get negative here, so having it as i16 instead of usize let mut page_index = self.page_counter as i16 - 1; let mut x_offset = 0; loop { @@ -261,7 +262,7 @@ where if let Some(width) = self .choices - .get(page_index as u8) + .get(page_index as usize) .paint_left(current_area, self.show_incomplete) { // Updating loop variables. @@ -293,7 +294,7 @@ where if let Some(width) = self .choices - .get(page_index as u8) + .get(page_index as usize) .paint_right(current_area, self.show_incomplete) { // Updating loop variables. @@ -326,7 +327,7 @@ where } /// Get current page counter. - pub fn page_index(&self) -> u8 { + pub fn page_index(&self) -> usize { self.page_counter } @@ -430,9 +431,9 @@ where { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("ChoicePage"); - t.kw_pair("active_page", inttostr!(self.page_counter)); - t.kw_pair("page_count", inttostr!(self.choices.count() as u8)); - t.kw_pair("is_carousel", booltostr!(self.is_carousel)); + t.kw_pair("active_page", &self.page_counter); + t.kw_pair("page_count", &self.choices.count()); + t.kw_pair("is_carousel", &booltostr!(self.is_carousel)); if self.has_previous_choice() { t.field("prev_choice", &self.choices.get(self.page_counter - 1)); diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs index cfb6757e00..7ad1c773ad 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs @@ -24,11 +24,11 @@ impl ChoiceFactoryNumberInput { impl ChoiceFactory for ChoiceFactoryNumberInput { type Item = ChoiceItem; - fn count(&self) -> u8 { - (self.max - self.min + 1) as u8 + fn count(&self) -> usize { + (self.max - self.min + 1) as usize } - fn get(&self, choice_index: u8) -> ChoiceItem { + fn get(&self, choice_index: usize) -> ChoiceItem { let num = self.min + choice_index as u32; let text: String<10> = String::from(num); let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons()); @@ -59,7 +59,7 @@ impl NumberInput { let initial_page = init_value - min; Self { min, - choice_page: ChoicePage::new(choices).with_initial_page_counter(initial_page as u8), + choice_page: ChoicePage::new(choices).with_initial_page_counter(initial_page as usize), } } } 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 46f8d49dfc..a1ff09d6cc 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 @@ -62,7 +62,7 @@ const MENU: [&str; MENU_LENGTH] = [ ]; /// Get a character at a specified index for a specified category. -fn get_char(current_category: &ChoiceCategory, index: u8) -> char { +fn get_char(current_category: &ChoiceCategory, index: usize) -> char { let index = index as usize; match current_category { ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS[index], @@ -74,7 +74,7 @@ fn get_char(current_category: &ChoiceCategory, index: u8) -> char { } /// Return category from menu based on page index. -fn get_category_from_menu(page_index: u8) -> ChoiceCategory { +fn get_category_from_menu(page_index: usize) -> ChoiceCategory { match page_index as usize { LOWERCASE_INDEX => ChoiceCategory::LowercaseLetter, UPPERCASE_INDEX => ChoiceCategory::UppercaseLetter, @@ -86,18 +86,18 @@ fn get_category_from_menu(page_index: u8) -> ChoiceCategory { /// How many choices are available for a specified category. /// (does not count the extra MENU choice for characters) -fn get_category_length(current_category: &ChoiceCategory) -> u8 { +fn get_category_length(current_category: &ChoiceCategory) -> usize { match current_category { - ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS.len() as u8, - ChoiceCategory::UppercaseLetter => UPPERCASE_LETTERS.len() as u8, - ChoiceCategory::Digit => DIGITS.len() as u8, - ChoiceCategory::SpecialSymbol => SPECIAL_SYMBOLS.len() as u8, - ChoiceCategory::Menu => MENU.len() as u8, + ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS.len(), + ChoiceCategory::UppercaseLetter => UPPERCASE_LETTERS.len(), + ChoiceCategory::Digit => DIGITS.len(), + ChoiceCategory::SpecialSymbol => SPECIAL_SYMBOLS.len(), + ChoiceCategory::Menu => MENU.len(), } } /// Whether this index is the MENU index - the last one in the list. -fn is_menu_choice(current_category: &ChoiceCategory, page_index: u8) -> bool { +fn is_menu_choice(current_category: &ChoiceCategory, page_index: usize) -> bool { if let ChoiceCategory::Menu = current_category { unreachable!() } @@ -120,7 +120,7 @@ impl ChoiceFactoryPassphrase { } /// MENU choices with accept and cancel hold-to-confirm side buttons. - fn get_menu_item(&self, choice_index: u8) -> ChoiceItem { + fn get_menu_item(&self, choice_index: usize) -> ChoiceItem { let choice_index = choice_index as usize; // More options for CANCEL/DELETE button @@ -165,7 +165,7 @@ impl ChoiceFactoryPassphrase { /// Character choices with a BACK to MENU choice at the end (visible from /// start) to return back - fn get_character_item(&self, choice_index: u8) -> ChoiceItem { + fn get_character_item(&self, choice_index: usize) -> ChoiceItem { if is_menu_choice(&self.current_category, choice_index) { ChoiceItem::new("BACK", ButtonLayout::arrow_armed_arrow("RETURN".into())) .with_icon(Icon::new(theme::ICON_ARROW_BACK_UP)) @@ -178,7 +178,7 @@ impl ChoiceFactoryPassphrase { impl ChoiceFactory for ChoiceFactoryPassphrase { type Item = ChoiceItem; - fn count(&self) -> u8 { + fn count(&self) -> usize { let length = get_category_length(&self.current_category); // All non-MENU categories have an extra item for returning back to MENU match self.current_category { @@ -186,7 +186,7 @@ impl ChoiceFactory for ChoiceFactoryPassphrase { _ => length + 1, } } - fn get(&self, choice_index: u8) -> ChoiceItem { + fn get(&self, choice_index: usize) -> ChoiceItem { match self.current_category { ChoiceCategory::Menu => self.get_menu_item(choice_index), _ => self.get_character_item(choice_index), @@ -201,7 +201,7 @@ pub struct PassphraseEntry { show_plain_passphrase: bool, textbox: TextBox, current_category: ChoiceCategory, - menu_position: u8, // position in the menu so we can return back + menu_position: usize, // position in the menu so we can return back } impl PassphraseEntry { @@ -209,7 +209,7 @@ impl PassphraseEntry { Self { choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true)) .with_carousel(true) - .with_initial_page_counter(LOWERCASE_INDEX as u8), + .with_initial_page_counter(LOWERCASE_INDEX), passphrase_dots: Child::new(ChangingTextLine::center_mono(String::new())), show_plain_passphrase: false, textbox: TextBox::empty(), @@ -298,7 +298,7 @@ impl Component for PassphraseEntry { self.update_passphrase_dots(ctx); if self.is_empty() { // Allowing for DELETE/CANCEL change - self.menu_position = CANCEL_DELETE_INDEX as u8; + self.menu_position = CANCEL_DELETE_INDEX; self.show_menu_page(ctx); } ctx.request_paint(); @@ -410,7 +410,7 @@ impl crate::trace::Trace for PassphraseEntry { t.open("PassphraseEntry"); // NOTE: `show_plain_passphrase` was not able to be transferred, // as it is true only for a very small amount of time - t.kw_pair("textbox", self.textbox.content()); + t.kw_pair("textbox", &self.textbox.content()); self.report_btn_actions(t); t.field("choice_page", &self.choice_page); t.close(); 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 b810adaa19..7b9f027ce6 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 @@ -42,31 +42,31 @@ impl ChoiceFactoryPIN { impl ChoiceFactory for ChoiceFactoryPIN { type Item = ChoiceItem; - fn get(&self, choice_index: u8) -> ChoiceItem { - let choice_str = CHOICES[choice_index as usize]; + fn get(&self, choice_index: usize) -> ChoiceItem { + let choice_str = CHOICES[choice_index]; let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons()); // Action buttons have different middle button text - if [DELETE_INDEX, SHOW_INDEX, ENTER_INDEX].contains(&(choice_index as usize)) { + if [DELETE_INDEX, SHOW_INDEX, ENTER_INDEX].contains(&(choice_index)) { let confirm_btn = ButtonDetails::armed_text("CONFIRM".into()); choice_item.set_middle_btn(Some(confirm_btn)); } // Adding icons for appropriate items - if choice_index == DELETE_INDEX as u8 { + if choice_index == DELETE_INDEX { choice_item = choice_item.with_icon(Icon::new(theme::ICON_DELETE)); - } else if choice_index == SHOW_INDEX as u8 { + } else if choice_index == SHOW_INDEX { choice_item = choice_item.with_icon(Icon::new(theme::ICON_EYE)); - } else if choice_index == ENTER_INDEX as u8 { + } else if choice_index == ENTER_INDEX { choice_item = choice_item.with_icon(Icon::new(theme::ICON_TICK)); } choice_item } - fn count(&self) -> u8 { - CHOICE_LENGTH as u8 + fn count(&self) -> usize { + CHOICE_LENGTH } } @@ -87,7 +87,7 @@ impl PinEntry { Self { // Starting at the digit 0 choice_page: ChoicePage::new(choices) - .with_initial_page_counter(NUMBER_START_INDEX as u8) + .with_initial_page_counter(NUMBER_START_INDEX) .with_carousel(true), pin_line: Child::new(ChangingTextLine::center_bold(String::from( prompt.clone().as_ref(), @@ -99,8 +99,8 @@ impl PinEntry { } } - fn append_new_digit(&mut self, ctx: &mut EventCtx, page_counter: u8) { - let digit = CHOICES[page_counter as usize]; + fn append_new_digit(&mut self, ctx: &mut EventCtx, page_counter: usize) { + let digit = CHOICES[page_counter]; self.textbox.append_slice(ctx, digit); } @@ -180,7 +180,7 @@ impl Component for PinEntry { let msg = self.choice_page.event(ctx, event); if let Some(ChoicePageMsg::Choice(page_counter)) = msg { // Performing action under specific index or appending new digit - match page_counter as usize { + match page_counter { DELETE_INDEX => { self.delete_last_digit(ctx); self.update(ctx); @@ -201,7 +201,7 @@ impl Component for PinEntry { page_counter as u32, ); self.choice_page - .set_page_counter(ctx, new_page_counter as u8); + .set_page_counter(ctx, new_page_counter as usize); self.update(ctx); } } @@ -229,7 +229,7 @@ impl crate::trace::Trace for PinEntry { ButtonPos::Left => ButtonAction::PrevPage.string(), ButtonPos::Right => ButtonAction::NextPage.string(), ButtonPos::Middle => { - let current_index = self.choice_page.page_index() as usize; + let current_index = self.choice_page.page_index(); match current_index { DELETE_INDEX => ButtonAction::Action("Delete last digit").string(), SHOW_INDEX => ButtonAction::Action("Show PIN").string(), @@ -245,7 +245,7 @@ impl crate::trace::Trace for PinEntry { // NOTE: `show_real_pin` was not able to be transferred, // as it is true only for a very small amount of time t.title(self.prompt.as_ref()); - t.kw_pair("textbox", self.textbox.content()); + t.kw_pair("textbox", &self.textbox.content()); self.report_btn_actions(t); t.field("choice_page", &self.choice_page); t.close(); 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 dc0dcdf6ee..cb7064aff8 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 @@ -11,7 +11,7 @@ use heapless::{String, Vec}; pub enum SimpleChoiceMsg { Result(String<50>), - Index(u8), + Index(usize), } struct ChoiceFactorySimple { @@ -28,12 +28,12 @@ impl ChoiceFactorySimple { impl ChoiceFactory for ChoiceFactorySimple { type Item = ChoiceItem; - fn count(&self) -> u8 { - N as u8 + fn count(&self) -> usize { + N } - fn get(&self, choice_index: u8) -> ChoiceItem { - let text = &self.choices[choice_index as usize]; + fn get(&self, choice_index: usize) -> ChoiceItem { + let text = &self.choices[choice_index]; let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons()); // Disabling prev/next buttons for the first/last choice when not in carousel. @@ -42,7 +42,7 @@ impl ChoiceFactory for ChoiceFactorySimple { if choice_index == 0 { choice_item.set_left_btn(None); } - if choice_index as usize == N - 1 { + if choice_index == N - 1 { choice_item.set_right_btn(None); } } @@ -102,7 +102,7 @@ impl Component for SimpleChoice { if self.return_index { Some(SimpleChoiceMsg::Index(page_counter)) } else { - let result = String::from(self.choices[page_counter as usize].as_ref()); + let result = String::from(self.choices[page_counter].as_ref()); Some(SimpleChoiceMsg::Result(result)) } } @@ -133,7 +133,7 @@ impl crate::trace::Trace for SimpleChoice { false => ButtonAction::empty(), }, ButtonPos::Middle => { - let current_index = self.choice_page.page_index() as usize; + let current_index = self.choice_page.page_index(); ButtonAction::select_item(self.choices[current_index].as_ref()) } } diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs index abb433bdbe..99c802c4e5 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs @@ -25,10 +25,10 @@ const MAX_LETTERS_LENGTH: usize = 26; const OFFER_WORDS_THRESHOLD: usize = 10; /// Where will be the DELETE option - at the first position -const DELETE_INDEX: u8 = 0; +const DELETE_INDEX: usize = 0; /// Which index will be used at the beginning. /// (Accounts for DELETE to be at index 0) -const INITIAL_PAGE_COUNTER: u8 = DELETE_INDEX + 1; +const INITIAL_PAGE_COUNTER: usize = DELETE_INDEX + 1; const PROMPT: &str = "_"; @@ -57,15 +57,15 @@ impl ChoiceFactoryWordlist { impl ChoiceFactory for ChoiceFactoryWordlist { type Item = ChoiceItem; - fn count(&self) -> u8 { + fn count(&self) -> usize { // Accounting for the DELETE option match self { - Self::Letters(letter_choices) => letter_choices.len() as u8 + 1, - Self::Words(word_choices) => word_choices.len() as u8 + 1, + Self::Letters(letter_choices) => letter_choices.len() + 1, + Self::Words(word_choices) => word_choices.len() + 1, } } - fn get(&self, choice_index: u8) -> ChoiceItem { + fn get(&self, choice_index: usize) -> ChoiceItem { // Letters have a carousel, words do not // Putting DELETE as the first option in both cases // (is a requirement for WORDS, doing it for LETTERS as well to unite it) @@ -281,7 +281,7 @@ impl crate::trace::Trace for WordlistEntry { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("Bip39Entry"); - t.kw_pair("textbox", self.textbox.content()); + t.kw_pair("textbox", &self.textbox.content()); self.report_btn_actions(t); 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 8ffe3f41df..c7d3acb53a 100644 --- a/core/embed/rust/src/ui/model_tr/component/page.rs +++ b/core/embed/rust/src/ui/model_tr/component/page.rs @@ -249,8 +249,8 @@ where fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("ButtonPage"); - t.kw_pair("active_page", inttostr!(self.active_page as u8)); - t.kw_pair("page_count", inttostr!(self.page_count as u8)); + t.kw_pair("active_page", &self.active_page); + t.kw_pair("page_count", &self.page_count); self.report_btn_actions(t); // TODO: it seems the button text is not updated when paginating (but actions // above are) diff --git a/core/embed/rust/src/ui/model_tr/component/scrollbar.rs b/core/embed/rust/src/ui/model_tr/component/scrollbar.rs index 00e73c7e79..900905d0db 100644 --- a/core/embed/rust/src/ui/model_tr/component/scrollbar.rs +++ b/core/embed/rust/src/ui/model_tr/component/scrollbar.rs @@ -247,8 +247,8 @@ impl Component for ScrollBar { impl crate::trace::Trace for ScrollBar { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("ScrollBar"); - t.field("page_count", &self.page_count); - t.field("active_page", &self.active_page); + t.kw_pair("scrollbar_page_count", &self.page_count); + t.kw_pair("scrollbar_active_page", &self.active_page); t.close(); } } diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 3bf873bd32..c59f631002 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -93,13 +93,13 @@ where impl ComponentMsgObj for Flow where - F: Fn(u8) -> Page, + F: Fn(usize) -> Page, { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { match msg { FlowMsg::Confirmed => Ok(CONFIRMED.as_obj()), FlowMsg::Cancelled => Ok(CANCELLED.as_obj()), - FlowMsg::ConfirmedIndex(index) => Ok(index.into()), + FlowMsg::ConfirmedIndex(index) => index.try_into(), } } } @@ -125,7 +125,7 @@ impl ComponentMsgObj for SimpleChoice { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { match msg { SimpleChoiceMsg::Result(choice) => choice.as_str().try_into(), - SimpleChoiceMsg::Index(index) => Ok(index.into()), + SimpleChoiceMsg::Index(index) => index.try_into(), } } } @@ -629,7 +629,7 @@ fn tutorial_screen( extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = |_args: &[Obj], _kwargs: &Map| { - const PAGE_COUNT: u8 = 7; + const PAGE_COUNT: usize = 7; let get_page = |page_index| { // Lazy-loaded list of screens to show, with custom content, @@ -806,8 +806,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map .text_bold(account) }; - let pages = FlowPages::new(get_page, page_count as u8); - + let pages = FlowPages::new(get_page, page_count); // Returning the page index in case of confirmation. let obj = LayoutObj::new( Flow::new(pages) diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py index 1e7c5a4c29..b78d1e66d3 100644 --- a/core/src/apps/debug/__init__.py +++ b/core/src/apps/debug/__init__.py @@ -73,7 +73,6 @@ if __debug__: SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT, - SWIPE_ALL_THE_WAY_UP, ) button = msg.button # local_cache_attribute @@ -99,8 +98,6 @@ if __debug__: await swipe_chan.put(SWIPE_LEFT) elif swipe == DebugSwipeDirection.RIGHT: await swipe_chan.put(SWIPE_RIGHT) - elif swipe == DebugSwipeDirection.ALL_THE_WAY_UP: - await swipe_chan.put(SWIPE_ALL_THE_WAY_UP) if msg.input is not None: await input_chan.put(Result(msg.input)) diff --git a/core/src/trezor/enums/DebugSwipeDirection.py b/core/src/trezor/enums/DebugSwipeDirection.py index 9b6073d99d..cd563cd13a 100644 --- a/core/src/trezor/enums/DebugSwipeDirection.py +++ b/core/src/trezor/enums/DebugSwipeDirection.py @@ -6,4 +6,3 @@ UP = 0 DOWN = 1 LEFT = 2 RIGHT = 3 -ALL_THE_WAY_UP = 4 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 7561cf28d0..0fd225cf21 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -449,7 +449,6 @@ if TYPE_CHECKING: DOWN = 1 LEFT = 2 RIGHT = 3 - ALL_THE_WAY_UP = 4 class DebugButton(IntEnum): NO = 0 diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py index 0daeee182e..454333b4ed 100644 --- a/core/src/trezor/ui/__init__.py +++ b/core/src/trezor/ui/__init__.py @@ -22,7 +22,6 @@ if __debug__: SWIPE_DOWN = const(0x02) SWIPE_LEFT = const(0x04) SWIPE_RIGHT = const(0x08) - SWIPE_ALL_THE_WAY_UP = const(0x10) # channel used to cancel layouts, see `Cancelled` exception diff --git a/core/src/trezor/ui/layouts/common.py b/core/src/trezor/ui/layouts/common.py index d984b08f43..30dc15c75b 100644 --- a/core/src/trezor/ui/layouts/common.py +++ b/core/src/trezor/ui/layouts/common.py @@ -37,14 +37,7 @@ async def interact( br_type: str, br_code: ButtonRequestType = ButtonRequestType.Other, ) -> Any: - if hasattr(layout, "in_unknown_flow") and layout.in_unknown_flow(): # type: ignore [Cannot access member "in_unknown_flow" for type "LayoutType"] - # We cannot recognize before-hand how many pages the layout will have - - # but we know for certain we want to paginate through them - # TODO: could do something less hacky than sending 0 as page count - # (create new ButtonRequest field) - await button_request(ctx, br_type, br_code, pages=0) - return await ctx.wait(layout) - elif hasattr(layout, "page_count") and layout.page_count() > 1: # type: ignore [Cannot access member "page_count" for type "LayoutType"] + if hasattr(layout, "page_count") and layout.page_count() > 1: # type: ignore [Cannot access member "page_count" for type "LayoutType"] # We know for certain how many pages the layout will have await button_request(ctx, br_type, br_code, pages=layout.page_count()) # type: ignore [Cannot access member "page_count" for type "LayoutType"] return await ctx.wait(layout) diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index ea8218999f..36fd8f6eeb 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -30,125 +30,23 @@ if __debug__: Used only in debug mode. """ - # How will some information be identified in the content - TITLE_TAG = " **TITLE** " - CONTENT_TAG = " **CONTENT** " - BTN_TAG = " **BTN** " - EMPTY_BTN = "---" - NEXT_BTN = "Next" - PREV_BTN = "Prev" + # TODO: used only because of `page_count` + # We could do it some other way + # TT does this: + # def page_count(self) -> int: + # return self.layout.page_count() def __init__(self, raw_content: list[str]) -> None: self.raw_content = raw_content self.str_content = " ".join(raw_content).replace(" ", " ") - print("str_content", self.str_content) - print(60 * "-") - print("active_page:", self.active_page()) - print("page_count:", self.page_count()) - print("flow_page:", self.flow_page()) - print("flow_page_count:", self.flow_page_count()) - print("can_go_next:", self.can_go_next()) - print("get_next_button:", self.get_next_button()) - print(30 * "/") - print(self.visible_screen()) - - def active_page(self) -> int: - """Current index of the active page. Should always be there.""" - return self.kw_pair_int("active_page") or 0 def page_count(self) -> int: """Overall number of pages in this screen. Should always be there.""" - return self.kw_pair_int("page_count") or 1 - - def in_flow(self) -> bool: - """Whether we are in flow.""" - return self.flow_page() is not None - - def flow_page(self) -> int | None: - """When in flow, on which page we are. Missing when not in flow.""" - return self.kw_pair_int("flow_page") - - def flow_page_count(self) -> int | None: - """When in flow, how many unique pages it has. Missing when not in flow.""" - return self.kw_pair_int("flow_page_count") - - def can_go_next(self) -> bool: - """Checking if there is a next page.""" - return self.get_next_button() is not None - - def get_next_button(self) -> str | None: - """Position of the next button, if any.""" - return self._get_btn_by_action(self.NEXT_BTN) - - def get_prev_button(self) -> str | None: - """Position of the previous button, if any.""" - return self._get_btn_by_action(self.PREV_BTN) - - def _get_btn_by_action(self, btn_action: str) -> str | None: - """Position of button described by some action. None if not found.""" - btn_names = ("left", "middle", "right") - for index, action in enumerate(self.button_actions()): - if action == btn_action: - return btn_names[index] - - return None - - def visible_screen(self) -> str: - """Getting all the visible screen content - header, content, buttons.""" - title_separator = f"\n{20*'-'}\n" - btn_separator = f"\n{20*'*'}\n" - - visible = "" - if self.title(): - visible += self.title() - visible += title_separator - visible += self.content() - visible += btn_separator - visible += ", ".join(self.buttons()) - - return visible - - def title(self) -> str: - """Getting text that is displayed as a title.""" - # there could be multiple of those - title and subtitle for example - title_strings = self._get_strings_inside_tag( - self.str_content, self.TITLE_TAG + return ( + self.kw_pair_int("scrollbar_page_count") + or self.kw_pair_int("page_count") + or 1 ) - return "\n".join(title_strings) - - def content(self) -> str: - """Getting text that is displayed in the main part of the screen.""" - content_strings = self._get_strings_inside_tag( - self.str_content, self.CONTENT_TAG - ) - # there are some unwanted spaces - strings = [ - s.replace(" \n ", "\n").replace("\n ", "\n").lstrip() - for s in content_strings - ] - return "\n".join(strings) - - def buttons(self) -> tuple[str, str, str]: - """Getting content and actions for all three buttons.""" - contents = self.buttons_content() - actions = self.button_actions() - return tuple(f"{contents[i]} [{actions[i]}]" for i in range(3)) - - def buttons_content(self) -> tuple[str, str, str]: - """Getting visual details for all three buttons. They should always be there.""" - if self.BTN_TAG not in self.str_content: - return ("None", "None", "None") - btns = self._get_strings_inside_tag(self.str_content, self.BTN_TAG) - assert len(btns) == 3 - return btns[0], btns[1], btns[2] - - def button_actions(self) -> tuple[str, str, str]: - """Getting actions for all three buttons. They should always be there.""" - if "_action" not in self.str_content: - return ("None", "None", "None") - action_ids = ("left_action", "middle_action", "right_action") - assert len(action_ids) == 3 - return tuple(self.kw_pair_compulsory(action) for action in action_ids) def kw_pair_int(self, key: str) -> int | None: """Getting the value of a key-value pair as an integer. None if missing.""" @@ -157,12 +55,6 @@ if __debug__: return None return int(val) - def kw_pair_compulsory(self, key: str) -> str: - """Getting value of a key that cannot be missing.""" - val = self.kw_pair(key) - assert val is not None - return val - def kw_pair(self, key: str) -> str | None: """Getting the value of a key-value pair. None if missing.""" # Pairs are sent in this format in the list: @@ -174,16 +66,6 @@ if __debug__: return None - @staticmethod - def _get_strings_inside_tag(string: str, tag: str) -> list[str]: - """Getting all strings that are inside two same tags.""" - parts = string.split(tag) - if len(parts) == 1: - return [] - else: - # returning all odd indexes in the list - return parts[1::2] - class RustLayout(ui.Layout): # pylint: disable=super-init-not-called @@ -227,12 +109,6 @@ class RustLayout(ui.Layout): if __debug__: from trezor.enums import DebugPhysicalButton - BTN_MAP = { - "left": DebugPhysicalButton.LEFT_BTN, - "middle": DebugPhysicalButton.MIDDLE_BTN, - "right": DebugPhysicalButton.RIGHT_BTN, - } - def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: # type: ignore [obscured-by-same-name] from apps.debug import confirm_signal, input_signal @@ -248,7 +124,8 @@ class RustLayout(ui.Layout): def read_content(self) -> list[str]: """Gets the visible content of the screen.""" self._place_layout() - return self._content_obj().visible_screen().split("\n") + raw = self._read_content_raw() + return " ".join(raw).split("\n") def _place_layout(self) -> None: """It is necessary to place the layout to get data about its screen content.""" @@ -315,18 +192,16 @@ class RustLayout(ui.Layout): SWIPE_UP, SWIPE_DOWN, ) - - content_obj = self._content_obj() + from trezor.enums import DebugPhysicalButton if direction == SWIPE_UP: - btn_to_press = content_obj.get_next_button() + btn_to_press = DebugPhysicalButton.RIGHT_BTN elif direction == SWIPE_DOWN: - btn_to_press = content_obj.get_prev_button() + btn_to_press = DebugPhysicalButton.LEFT_BTN else: raise Exception(f"Unsupported direction: {direction}") - assert btn_to_press is not None - self._press_button(self.BTN_MAP[btn_to_press]) + self._press_button(btn_to_press) async def handle_swipe(self) -> None: """Enables pagination through the current page/flow page. @@ -334,17 +209,10 @@ class RustLayout(ui.Layout): Waits for `swipe_signal` and carries it out. """ from apps.debug import swipe_signal - from trezor.ui import SWIPE_ALL_THE_WAY_UP, SWIPE_UP while True: direction = await swipe_signal() - - if direction == SWIPE_ALL_THE_WAY_UP: - # Going as far as possible - while self._content_obj().can_go_next(): - self._swipe(SWIPE_UP) - else: - self._swipe(direction) + self._swipe(direction) async def handle_button_click(self) -> None: """Enables clicking arbitrary of the three buttons. @@ -359,17 +227,9 @@ class RustLayout(ui.Layout): def page_count(self) -> int: """How many paginated pages current screen has.""" - # TODO: leave it debug-only or use always? self._place_layout() return self._content_obj().page_count() - def in_unknown_flow(self) -> bool: - """Whether we are in a longer flow where we cannot (easily) - beforehand say how much pages it will have. - """ - self._place_layout() - return self._content_obj().in_flow() - else: def create_tasks(self) -> tuple[loop.Task, ...]: diff --git a/core/src/trezor/ui/layouts/tt_v2/__init__.py b/core/src/trezor/ui/layouts/tt_v2/__init__.py index 0a07a2669c..8521ac3acd 100644 --- a/core/src/trezor/ui/layouts/tt_v2/__init__.py +++ b/core/src/trezor/ui/layouts/tt_v2/__init__.py @@ -115,6 +115,7 @@ class RustLayout(ui.Layout): notify_layout_change(self) def notify_backup(self): + # TODO: delete this in favor of debuglink::LayoutContent::seed_words from apps.debug import reset_current_words content = "\n".join(self.read_content()) diff --git a/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py index 8dc7dce24f..514942e529 100644 --- a/python/src/trezorlib/debuglink.py +++ b/python/src/trezorlib/debuglink.py @@ -61,84 +61,204 @@ EXPECTED_RESPONSES_CONTEXT_LINES = 3 LOG = logging.getLogger(__name__) -class LayoutContent: +def _get_strings_inside_tag(string: str, tag: str) -> List[str]: + """Getting all strings that are inside two same tags. + Example: + _get_strings_inside_tag("abc **TAG** def **TAG** ghi") + -> ["def"] + """ + parts = string.split(tag) + if len(parts) == 1: + return [] + else: + # returning all odd indexes in the list + return parts[1::2] + + +class LayoutBase: + """Common base for layouts, containing common methods.""" + + def __init__(self, lines: Sequence[str]) -> None: + self.lines = list(lines) + self.str_content = "\n".join(self.lines) + self.tokens = self.str_content.split() + + def kw_pair_int(self, key: str) -> Optional[int]: + """Getting the value of a key-value pair as an integer. None if missing.""" + val = self.kw_pair(key) + if val is None: + return None + return int(val) + + def kw_pair_compulsory(self, key: str) -> str: + """Getting value of a key that cannot be missing.""" + val = self.kw_pair(key) + assert val is not None + return val + + def kw_pair(self, key: str) -> Optional[str]: + """Getting the value of a key-value pair. None if missing.""" + # Pairs are sent in this format in the list: + # [..., "key", "::", "value", ...] + for key_index, item in enumerate(self.tokens): + if item == key: + if self.tokens[key_index + 1] == "::": + return self.tokens[key_index + 2] + + return None + + +class LayoutButtons(LayoutBase): + """Extension for the LayoutContent class to handle buttons.""" + + BTN_TAG = " **BTN** " + EMPTY_BTN = "---" + NEXT_BTN = "Next" + PREV_BTN = "Prev" + BTN_NAMES = ("left", "middle", "right") + + def __init__(self, lines: Sequence[str]) -> None: + super().__init__(lines) + + def is_applicable(self) -> bool: + """Check if the layout has buttons.""" + return self.BTN_TAG in self.str_content + + def visible(self) -> str: + """Getting content and actions for all three buttons.""" + return ", ".join(self.all_buttons()) + + def all_buttons(self) -> Tuple[str, str, str]: + """Getting content and actions for all three buttons.""" + contents = self.content() + actions = self.actions() + return tuple(f"{contents[i]} [{actions[i]}]" for i in range(3)) + + def content(self) -> Tuple[str, str, str]: + """Getting visual details for all three buttons. They should always be there.""" + if self.BTN_TAG not in self.str_content: + return ("None", "None", "None") + btns = _get_strings_inside_tag(self.str_content, self.BTN_TAG) + assert len(btns) == 3 + return btns[0].strip(), btns[1].strip(), btns[2].strip() + + def actions(self) -> Tuple[str, str, str]: + """Getting actions for all three buttons. They should always be there.""" + if "_action" not in self.str_content: + return ("None", "None", "None") + action_ids = ("left_action", "middle_action", "right_action") + assert len(action_ids) == 3 + return tuple(self.kw_pair_compulsory(action) for action in action_ids) + + def can_go_next(self) -> bool: + """Checking if there is a next page.""" + return self.get_next_button() is not None + + def can_go_back(self) -> bool: + """Checking if there is a previous page.""" + return self.get_prev_button() is not None + + def get_next_button(self) -> Optional[str]: + """Position of the next button, if any.""" + return self._get_btn_by_action(self.NEXT_BTN) + + def get_prev_button(self) -> Optional[str]: + """Position of the previous button, if any.""" + return self._get_btn_by_action(self.PREV_BTN) + + def _get_btn_by_action(self, btn_action: str) -> Optional[str]: + """Position of button described by some action. None if not found.""" + for index, action in enumerate(self.actions()): + if action == btn_action: + return self.BTN_NAMES[index] + + return None + + +class LayoutContent(LayoutBase): """Stores content of a layout as returned from Trezor. Contains helper functions to extract specific parts of the layout. """ + # How will some information be identified in the content + TITLE_TAG = " **TITLE** " + CONTENT_TAG = " **CONTENT** " + def __init__(self, lines: Sequence[str]) -> None: - self.lines = list(lines) - self.text = " ".join(self.lines) + super().__init__(lines) + self.buttons = LayoutButtons(lines) - def get_title(self) -> str: - """Get title of the layout. - - Title is located between "title" and "content" identifiers. - Example: "< Frame title : RECOVERY SHARE #1 content : < SwipePage" - -> "RECOVERY SHARE #1" + def visible_screen(self) -> str: + """String representation of a current screen content. + Example: + SIGN TRANSACTION + -------------------- + You are about to + sign 3 actions. + ******************** + Icon:cancel [Cancel], --- [None], CONFIRM [Confirm] """ - match = re.search(r"title : (.*?) content :", self.text) - if not match: - return "" - return match.group(1).strip() + title_separator = f"\n{20*'-'}\n" + btn_separator = f"\n{20*'*'}\n" - def get_content(self, tag_name: str = "Paragraphs", raw: bool = False) -> str: - """Get text of the main screen content of the layout.""" - content = "".join(self._get_content_lines(tag_name, raw)) - if not raw and content.endswith(" "): - # Stripping possible space at the end - content = content[:-1] - return content + visible = "" + if self.title(): + visible += self.title() + visible += title_separator + visible += self.raw_content() + if self.buttons.is_applicable(): + visible += btn_separator + visible += self.buttons.visible() - def get_button_texts(self) -> List[str]: - """Get text of all buttons in the layout. + return visible - Example button: "< Button text : LADYBUG >" - -> ["LADYBUG"] - """ - return re.findall(r"< Button text : +(.*?) >", self.text) + def title(self) -> str: + """Getting text that is displayed as a title.""" + # there could be multiple of those - title and subtitle for example + title_strings = _get_strings_inside_tag(self.str_content, self.TITLE_TAG) + return "\n".join(title_strings).strip() - def get_seed_words(self) -> List[str]: + def text_content(self) -> str: + """Getting text that is displayed in the main part of the screen.""" + raw = self.raw_content() + return raw.replace("\n", " ") + + def raw_content(self) -> str: + """Getting raw text that is displayed in the main part of the screen, + with corresponding line breaks.""" + content_strings = _get_strings_inside_tag(self.str_content, self.CONTENT_TAG) + # there are some unwanted spaces + strings = [ + s.replace(" \n ", "\n").replace("\n ", "\n").lstrip() + for s in content_strings + ] + return "\n".join(strings).strip() + + def seed_words(self) -> List[str]: """Get all the seed words on the screen in order. Example content: "1. ladybug 2. acid 3. academic 4. afraid" -> ["ladybug", "acid", "academic", "afraid"] """ - return re.findall(r"\d+\. (\w+)\b", self.get_content()) + # Dot after index is optional (present on TT, not on TR) + return re.findall(r"\d+\.? (\w+)\b", self.raw_content()) - def get_page_count(self) -> int: + def page_count(self) -> int: """Get number of pages for the layout.""" - return self._get_number("page_count") + return ( + self.kw_pair_int("scrollbar_page_count") + or self.kw_pair_int("page_count") + or 1 + ) - def get_active_page(self) -> int: + def active_page(self) -> int: """Get current page index of the layout.""" - return self._get_number("active_page") - - def _get_number(self, key: str) -> int: - """Get number connected with a specific key.""" - match = re.search(rf"{key} : +(\d+)", self.text) - if not match: - return 0 - return int(match.group(1)) - - def _get_content_lines( - self, tag_name: str = "Paragraphs", raw: bool = False - ) -> List[str]: - """Get lines of the main screen content of the layout.""" - - # First line should have content after the tag, last line does not store content - tag = f"< {tag_name}" - if tag in self.lines[0]: - first_line = self.lines[0].split(tag)[1] - all_lines = [first_line] + self.lines[1:-1] - else: - all_lines = self.lines[1:-1] - - if raw: - return all_lines - else: - return [_clean_line(line) for line in all_lines] + return ( + self.kw_pair_int("scrollbar_active_page") + or self.kw_pair_int("active_page") + or 0 + ) def _clean_line(line: str) -> str: @@ -170,10 +290,10 @@ def multipage_content(layouts: List[LayoutContent]) -> str: """Get overall content from multiple-page layout.""" final_text = "" for layout in layouts: - final_text += layout.get_content() + final_text += layout.text_content() # When the raw content of the page ends with ellipsis, # we need to add a space to separate it with the next page - if layout.get_content(raw=True).endswith("... "): + if layout.raw_content().endswith("... "): final_text += " " # Stripping possible space at the end of last page @@ -328,23 +448,21 @@ class DebugLink: layout = self.wait_layout() else: layout = self.read_layout() - self.save_debug_screen(layout.lines) + self.save_debug_screen(layout.visible_screen()) - def save_debug_screen(self, lines: List[str]) -> None: + def save_debug_screen(self, screen_content: str) -> None: if self.screen_text_file is not None: if not self.screen_text_file.exists(): self.screen_text_file.write_bytes(b"") - content = "\n".join(lines) - # Not writing the same screen twice - if content == self.last_screen_content: + if screen_content == self.last_screen_content: return - self.last_screen_content = content + self.last_screen_content = screen_content with open(self.screen_text_file, "a") as f: - f.write(content) + f.write(screen_content) f.write("\n" + 80 * "/" + "\n") # Type overloads make sure that when we supply `wait=True` into `click()`, @@ -373,9 +491,6 @@ class DebugLink: def press_info(self) -> None: self.input(button=messages.DebugButton.INFO) - def swipe_all_the_way_up(self) -> None: - self.input(swipe=messages.DebugSwipeDirection.ALL_THE_WAY_UP, wait=True) - def swipe_up(self, wait: bool = False) -> None: self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait) @@ -521,12 +636,8 @@ class DebugUI: else: # Paginating (going as further as possible) and pressing Yes if br.pages is not None: - if br.pages == 0: - # When we do not know how many, but want to paginate - self.debuglink.swipe_all_the_way_up() - else: - for _ in range(br.pages - 1): - self.debuglink.swipe_up(wait=True) + for _ in range(br.pages - 1): + self.debuglink.swipe_up(wait=True) self.debuglink.press_yes() elif self.input_flow is self.INPUT_FLOW_DONE: raise AssertionError("input flow ended prematurely") diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 679ded9e8b..0ceda0fab2 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -484,7 +484,6 @@ class DebugSwipeDirection(IntEnum): DOWN = 1 LEFT = 2 RIGHT = 3 - ALL_THE_WAY_UP = 4 class DebugButton(IntEnum): diff --git a/tests/click_tests/recovery.py b/tests/click_tests/recovery.py index 6e0b8cb199..1a1d2e4ae6 100644 --- a/tests/click_tests/recovery.py +++ b/tests/click_tests/recovery.py @@ -23,9 +23,9 @@ def enter_word( def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None: layout = debug.wait_layout() if legacy_ui: - layout.text.startswith("Recovery mode") + layout.str_content.startswith("Recovery mode") else: - assert layout.get_title() == "RECOVERY MODE" + assert layout.title() == "RECOVERY MODE" debug.click(buttons.OK, wait=True) @@ -35,13 +35,13 @@ def select_number_of_words( layout = debug.read_layout() # select number of words - assert "select the number of words" in layout.get_content() + assert "select the number of words" in layout.text_content() layout = debug.click(buttons.OK, wait=True) if legacy_ui: - assert layout.text == "WordSelector" + assert layout.str_content == "WordSelector" else: # Two title options - assert layout.get_title() in ("SEED CHECK", "RECOVERY MODE") + assert layout.title() in ("SEED CHECK", "RECOVERY MODE") # click the number word_option_offset = 6 @@ -51,7 +51,7 @@ def select_number_of_words( ) # raises if num of words is invalid coords = buttons.grid34(index % 3, index // 3) layout = debug.click(coords, wait=True) - assert "Enter any share" in layout.get_content() + assert "Enter any share" in layout.text_content() def enter_share( @@ -60,9 +60,9 @@ def enter_share( layout = debug.click(buttons.OK, wait=True) if legacy_ui: - assert layout.text == "Slip39Keyboard" + assert layout.str_content == "Slip39Keyboard" else: - assert layout.text == "< MnemonicKeyboard >" + assert layout.str_content == "< MnemonicKeyboard >" for word in share.split(" "): layout = enter_word(debug, word, is_slip39=True) @@ -75,15 +75,15 @@ def enter_shares(debug: "DebugLink", shares: list[str]) -> None: expected_text = "Enter any share" remaining = len(shares) for share in shares: - assert expected_text in layout.get_content() + assert expected_text in layout.text_content() layout = enter_share(debug, share) remaining -= 1 expected_text = f"{remaining} more share" - assert "You have successfully recovered your wallet" in layout.get_content() + assert "You have successfully recovered your wallet" in layout.text_content() def finalize(debug: "DebugLink") -> None: layout = debug.click(buttons.OK, wait=True) # TODO: should we also run Click/Persistence tests for model R? - assert layout.text.startswith("< Homescreen ") + assert layout.str_content.startswith("< Homescreen ") diff --git a/tests/click_tests/reset.py b/tests/click_tests/reset.py index 29be8ed918..03796b9df4 100644 --- a/tests/click_tests/reset.py +++ b/tests/click_tests/reset.py @@ -12,26 +12,26 @@ if TYPE_CHECKING: def confirm_wait(debug: "DebugLink", title: str) -> None: layout = debug.wait_layout() - assert title.upper() in layout.get_title() + assert title.upper() in layout.title() debug.click(buttons.OK, wait=True) def confirm_read(debug: "DebugLink", title: str) -> None: layout = debug.read_layout() if title == "Caution": - assert "OK, I UNDERSTAND" in layout.text + assert "OK, I UNDERSTAND" in layout.str_content elif title == "Success": assert any( - text in layout.get_content() for text in ("success", "finished", "done") + text in layout.text_content() for text in ("success", "finished", "done") ) else: - assert title.upper() in layout.get_title() + assert title.upper() in layout.title() debug.click(buttons.OK, wait=True) def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None: layout = debug.read_layout() - assert "NumberInputDialog" in layout.text + assert "NumberInputDialog" in layout.str_content for _ in range(diff): debug.click(button, wait=False) debug.click(buttons.OK, wait=True) @@ -41,15 +41,15 @@ def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]: words: list[str] = [] layout = debug.read_layout() if is_advanced: - assert layout.get_title().startswith("GROUP") + assert layout.title().startswith("GROUP") else: - assert layout.get_title().startswith("RECOVERY SHARE #") + assert layout.title().startswith("RECOVERY SHARE #") # Swiping through all the page and loading the words - for _ in range(layout.get_page_count() - 1): - words.extend(layout.get_seed_words()) + for _ in range(layout.page_count() - 1): + words.extend(layout.seed_words()) layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True) - words.extend(layout.get_seed_words()) + words.extend(layout.seed_words()) debug.press_yes() @@ -58,13 +58,13 @@ def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]: def confirm_words(debug: "DebugLink", words: list[str]) -> None: layout = debug.wait_layout() - assert "Select word" in layout.text + assert "Select word" in layout.str_content for _ in range(3): # "Select word 3 of 20" # ^ - word_pos = int(layout.get_content().split()[2]) + word_pos = int(layout.text_content().split()[2]) # Unifying both the buttons and words to lowercase - btn_texts = [text.lower() for text in layout.get_button_texts()] + btn_texts = [text.lower() for text in layout.buttons()] wanted_word = words[word_pos - 1].lower() button_pos = btn_texts.index(wanted_word) layout = debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True) diff --git a/tests/click_tests/test_autolock.py b/tests/click_tests/test_autolock.py index 012d89ddff..1be25fde55 100644 --- a/tests/click_tests/test_autolock.py +++ b/tests/click_tests/test_autolock.py @@ -46,18 +46,18 @@ def set_autolock_delay(device_handler: "BackgroundDeviceHandler", delay_ms: int) device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms) layout = debug.wait_layout() - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" debug.input("1234") layout = debug.wait_layout() assert ( f"auto-lock your device after {delay_ms // 1000} seconds" - in layout.get_content() + in layout.text_content() ) debug.click(buttons.OK) layout = debug.wait_layout() - assert layout.text.startswith("< Homescreen") + assert layout.str_content.startswith("< Homescreen") assert device_handler.result() == "Settings applied" @@ -83,13 +83,15 @@ def test_autolock_interrupts_signing(device_handler: "BackgroundDeviceHandler"): device_handler.run(btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE) layout = debug.wait_layout() - assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.get_content().replace(" ", "") + assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.text_content().replace( + " ", "" + ) debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True) - assert "Total amount: 0.0039 BTC" in layout.get_content() + assert "Total amount: 0.0039 BTC" in layout.text_content() # wait for autolock to kick in time.sleep(10.1) @@ -108,7 +110,7 @@ def test_autolock_passphrase_keyboard(device_handler: "BackgroundDeviceHandler") # enter passphrase - slowly layout = debug.wait_layout() - assert layout.text == "PassphraseKeyboard" + assert layout.str_content == "PassphraseKeyboard" CENTER_BUTTON = buttons.grid35(1, 2) for _ in range(11): @@ -127,26 +129,26 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle # unlock layout = debug.wait_layout() - assert "Do you really want to check the recovery seed?" in layout.get_content() + assert "Do you really want to check the recovery seed?" in layout.text_content() layout = debug.click(buttons.OK, wait=True) - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" layout = debug.input(PIN4, wait=True) - assert "select the number of words " in layout.get_content() + assert "select the number of words " in layout.text_content() # wait for autolock to trigger time.sleep(10.1) layout = debug.wait_layout() - assert layout.text.startswith("< Lockscreen") + assert layout.str_content.startswith("< Lockscreen") with pytest.raises(exceptions.Cancelled): device_handler.result() # unlock layout = debug.click(buttons.OK, wait=True) - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" layout = debug.input(PIN4, wait=True) # we are back at homescreen - assert "select the number of words" in layout.get_content() + assert "select the number of words" in layout.text_content() @pytest.mark.setup_client(pin=PIN4) @@ -158,9 +160,9 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"): # unlock layout = debug.wait_layout() - assert "Do you really want to check the recovery seed?" in layout.get_content() + assert "Do you really want to check the recovery seed?" in layout.text_content() layout = debug.click(buttons.OK, wait=True) - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" layout = debug.input(PIN4, wait=True) # select 20 words @@ -168,10 +170,10 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"): layout = debug.click(buttons.OK, wait=True) # make sure keyboard locks - assert layout.text == "< MnemonicKeyboard >" + assert layout.str_content == "< MnemonicKeyboard >" time.sleep(10.1) layout = debug.wait_layout() - assert layout.text.startswith("< Lockscreen") + assert layout.str_content.startswith("< Lockscreen") with pytest.raises(exceptions.Cancelled): device_handler.result() @@ -185,9 +187,9 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"): # unlock layout = debug.wait_layout() - assert "Do you really want to check the recovery seed?" in layout.get_content() + assert "Do you really want to check the recovery seed?" in layout.text_content() layout = debug.click(buttons.OK, wait=True) - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" layout = debug.input(PIN4, wait=True) # select 20 words @@ -195,11 +197,11 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"): layout = debug.click(buttons.OK, wait=True) # type the word OCEAN slowly - assert layout.text == "< MnemonicKeyboard >" + assert layout.str_content == "< MnemonicKeyboard >" for coords in buttons.type_word("ocea", is_slip39=True): time.sleep(9) debug.click(coords) layout = debug.click(buttons.CONFIRM_WORD, wait=True) # should not have locked, even though we took 9 seconds to type each letter - assert layout.text == "< MnemonicKeyboard >" + assert layout.str_content == "< MnemonicKeyboard >" device_handler.kill_task() diff --git a/tests/click_tests/test_lock.py b/tests/click_tests/test_lock.py index bb29f78391..dda1f17a35 100644 --- a/tests/click_tests/test_lock.py +++ b/tests/click_tests/test_lock.py @@ -41,7 +41,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"): # unlock with message device_handler.run(common.get_test_address) layout = debug.wait_layout() - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" debug.input("1234", wait=True) assert device_handler.result() @@ -57,7 +57,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"): # unlock by touching layout = debug.click(buttons.INFO, wait=True) - assert layout.text == "< PinKeyboard >" + assert layout.str_content == "< PinKeyboard >" debug.input("1234", wait=True) assert device_handler.features().unlocked is True diff --git a/tests/common.py b/tests/common.py index 223aaaed3a..625ae5323c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -25,7 +25,6 @@ from trezorlib import btc, tools from trezorlib.messages import ButtonRequestType if TYPE_CHECKING: - from trezorlib.debuglink import LayoutContent from trezorlib.debuglink import DebugLink, TrezorClientDebugLink as Client from trezorlib.messages import ButtonRequest from _pytest.mark.structures import MarkDecorator @@ -332,8 +331,7 @@ def read_and_confirm_mnemonic_tr( assert br.pages is not None for _ in range(br.pages): layout = debug.wait_layout() - - words = ModelRLayout(layout).get_mnemonic_words() + words = layout.seed_words() mnemonic.extend(words) debug.press_right() @@ -351,47 +349,6 @@ def read_and_confirm_mnemonic_tr( return " ".join(mnemonic) -class ModelRLayout: - """Layout shortcuts for Model R.""" - - def __init__(self, layout: "LayoutContent") -> None: - self.layout = layout - - def get_mnemonic_words(self) -> list[str]: - """Extract mnemonic words from the layout lines. - - Example input: [..., '4 must', '5 during', '6 monitor', ...] - Example output: ['must', 'during', 'monitor'] - """ - words: list[str] = [] - for line in self.layout.lines: - if " " in line: - number, word = line.split(" ", 1) - if all(c.isdigit() for c in number): - words.append(word.strip()) - - return words - - def get_word_index(self) -> int: - """Extract currently asked mnemonic index. - - Example input: "Select word 3/12" - Example output: 2 - """ - prompt = self.layout.lines[0] - human_index = prompt.split(" ")[-1].split("/")[0] - return int(human_index) - 1 - - def get_current_word(self) -> str: - """Extract currently selected word. - - Example input: "SELECT [Select(monitor)]" - Example output: "monitor" - """ - buttons = self.layout.lines[-1] - return buttons.split("[Select(")[1].split(")]")[0] - - def click_info_button(debug: "DebugLink"): """Click Shamir backup info button and return back.""" debug.press_info() @@ -414,9 +371,9 @@ def get_test_address(client: "Client") -> str: def get_text_from_paginated_screen(client: "Client", screen_count: int) -> str: """Aggregating screen text from more pages into one string.""" - text: str = client.debug.wait_layout().text + text: str = client.debug.wait_layout().str_content for _ in range(screen_count - 1): client.debug.swipe_up() - text += client.debug.wait_layout().text + text += client.debug.wait_layout().str_content return text diff --git a/tests/device_tests/test_sdcard.py b/tests/device_tests/test_sdcard.py index 84debe1713..2e0c2457f6 100644 --- a/tests/device_tests/test_sdcard.py +++ b/tests/device_tests/test_sdcard.py @@ -53,19 +53,19 @@ def test_sd_protect_unlock(client: Client): def input_flow_enable_sd_protect(): yield # Enter PIN to unlock device - assert "< PinKeyboard >" == layout().text + assert "< PinKeyboard >" == layout().str_content client.debug.input("1234") yield # do you really want to enable SD protection - assert "SD card protection" in layout().get_content() + assert "SD card protection" in layout().text_content() client.debug.press_yes() yield # enter current PIN - assert "< PinKeyboard >" == layout().text + assert "< PinKeyboard >" == layout().str_content client.debug.input("1234") yield # you have successfully enabled SD protection - assert "You have successfully enabled SD protection." in layout().get_content() + assert "You have successfully enabled SD protection." in layout().text_content() client.debug.press_yes() with client: @@ -75,23 +75,23 @@ def test_sd_protect_unlock(client: Client): def input_flow_change_pin(): yield # do you really want to change PIN? - assert "CHANGE PIN" == layout().get_title() + assert "CHANGE PIN" == layout().title() client.debug.press_yes() yield # enter current PIN - assert "< PinKeyboard >" == layout().text + assert "< PinKeyboard >" == layout().str_content client.debug.input("1234") yield # enter new PIN - assert "< PinKeyboard >" == layout().text + assert "< PinKeyboard >" == layout().str_content client.debug.input("1234") yield # enter new PIN again - assert "< PinKeyboard >" == layout().text + assert "< PinKeyboard >" == layout().str_content client.debug.input("1234") yield # Pin change successful - assert "You have successfully changed your PIN." in layout().get_content() + assert "You have successfully changed your PIN." in layout().text_content() client.debug.press_yes() with client: @@ -103,15 +103,15 @@ def test_sd_protect_unlock(client: Client): def input_flow_change_pin_format(): yield # do you really want to change PIN? - assert "CHANGE PIN" == layout().get_title() + assert "CHANGE PIN" == layout().title() client.debug.press_yes() yield # enter current PIN - assert "< PinKeyboard >" == layout().text + assert "< PinKeyboard >" == layout().str_content client.debug.input("1234") yield # SD card problem - assert "Wrong SD card" in layout().get_content() + assert "Wrong SD card" in layout().text_content() client.debug.press_no() # close with client, pytest.raises(TrezorFailure) as e: diff --git a/tests/input_flows.py b/tests/input_flows.py index 59fe0ac099..29545c5783 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -225,12 +225,12 @@ class InputFlowShowMultisigXPUBs(InputFlowBase): def input_flow_tt(self) -> GeneratorType: yield # show address layout = self.debug.wait_layout() # TODO: do not need to *wait* now? - assert layout.get_title() == "MULTISIG 2 OF 3" - assert layout.get_content().replace(" ", "") == self.address + assert layout.title() == "MULTISIG 2 OF 3" + assert layout.text_content().replace(" ", "") == self.address self.debug.press_no() yield # show QR code - assert "Painter" in self.debug.wait_layout().text + assert "Painter" in self.debug.wait_layout().str_content # Three xpub pages with the same testing logic for xpub_num in range(3): @@ -241,12 +241,12 @@ class InputFlowShowMultisigXPUBs(InputFlowBase): self.debug.press_no() yield # show XPUB#{xpub_num} layout1 = self.debug.wait_layout() - assert layout1.get_title() == expected_title + assert layout1.title() == expected_title self.debug.swipe_up() layout2 = self.debug.wait_layout() - assert layout2.get_title() == expected_title - content = (layout1.get_content() + layout2.get_content()).replace(" ", "") + assert layout2.title() == expected_title + content = (layout1.text_content() + layout2.text_content()).replace(" ", "") assert content == self.xpubs[xpub_num] self.debug.press_yes() @@ -263,14 +263,14 @@ class InputFlowPaymentRequestDetails(InputFlowBase): self.debug.press_info() yield # confirm first output - assert self.outputs[0].address[:16] in self.layout().text + assert self.outputs[0].address[:16] in self.layout().text_content() self.debug.press_yes() yield # confirm first output self.debug.wait_layout() self.debug.press_yes() yield # confirm second output - assert self.outputs[1].address[:16] in self.layout().text + assert self.outputs[1].address[:16] in self.layout().text_content() self.debug.press_yes() yield # confirm second output self.debug.wait_layout() @@ -325,7 +325,7 @@ def lock_time_input_flow_tt( debug.press_yes() yield # confirm locktime - layout_text = debug.wait_layout().text + layout_text = debug.wait_layout().text_content() layout_assert_func(layout_text) debug.press_yes() @@ -345,7 +345,7 @@ def lock_time_input_flow_tr( debug.press_yes() yield # confirm locktime - layout_text = debug.wait_layout().text + layout_text = debug.wait_layout().text_content() layout_assert_func(layout_text) debug.press_yes() @@ -989,15 +989,15 @@ class InputFlowSlip39AdvancedResetRecovery(InputFlowBase): def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> GeneratorType: yield - assert "check the recovery seed" in debug.wait_layout().get_content() + assert "check the recovery seed" in debug.wait_layout().text_content() debug.click(buttons.OK) yield - assert "Select number of words" in debug.wait_layout().get_content() + assert "Select number of words" in debug.wait_layout().text_content() debug.click(buttons.OK) yield - assert "SelectWordCount" in debug.wait_layout().text + assert "SelectWordCount" in debug.wait_layout().str_content # click the correct number word_option_offset = 6 word_options = (12, 18, 20, 24, 33) @@ -1005,12 +1005,12 @@ def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> Genera debug.click(buttons.grid34(index % 3, index // 3)) yield - assert "Enter recovery seed" in debug.wait_layout().get_content() + assert "Enter recovery seed" in debug.wait_layout().text_content() debug.click(buttons.OK) yield for word in mnemonic: - assert debug.wait_layout().text == "< MnemonicKeyboard >" + assert debug.wait_layout().str_content == "< MnemonicKeyboard >" debug.input(word) @@ -1028,16 +1028,16 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase): def input_flow_tr(self) -> GeneratorType: yield - assert "check the recovery seed" in self.layout().text + assert "check the recovery seed" in self.layout().text_content() self.debug.press_right() yield - assert "select the number of words" in self.layout().text + assert "select the number of words" in self.layout().text_content() self.debug.press_yes() yield yield - assert "NUMBER OF WORDS" in self.layout().text + assert "NUMBER OF WORDS" in self.layout().title() word_options = (12, 18, 20, 24, 33) index = word_options.index(len(self.mnemonic)) for _ in range(index): @@ -1045,14 +1045,14 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase): self.debug.input(str(len(self.mnemonic))) yield - assert "enter your recovery seed" in self.layout().text + assert "enter your recovery seed" in self.layout().text_content() self.debug.press_yes() yield self.debug.press_right() yield for word in self.mnemonic: - assert "WORD" in self.layout().text + assert "WORD" in self.layout().title() self.debug.input(word) self.debug.wait_layout() @@ -1072,56 +1072,56 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase): br = yield assert br.code == messages.ButtonRequestType.Warning - assert "invalid recovery seed" in self.layout().get_content() + assert "invalid recovery seed" in self.layout().text_content() self.debug.click(buttons.OK) yield # retry screen - assert "Select number of words" in self.layout().get_content() + assert "Select number of words" in self.layout().text_content() self.debug.click(buttons.CANCEL) yield - assert "ABORT SEED CHECK" == self.layout().get_title() + assert "ABORT SEED CHECK" == self.layout().title() self.debug.click(buttons.OK) def input_flow_tr(self) -> GeneratorType: yield - assert "check the recovery seed" in self.layout().text + assert "check the recovery seed" in self.layout().text_content() self.debug.press_right() yield - assert "select the number of words" in self.layout().text + assert "select the number of words" in self.layout().text_content() self.debug.press_yes() yield yield - assert "NUMBER OF WORDS" in self.layout().text + assert "NUMBER OF WORDS" in self.layout().title() # select 12 words self.debug.press_middle() yield - assert "enter your recovery seed" in self.layout().text + assert "enter your recovery seed" in self.layout().text_content() self.debug.press_yes() yield - assert "WORD ENTERING" in self.layout().text + assert "WORD ENTERING" in self.layout().title() self.debug.press_yes() yield for _ in range(12): - assert "WORD" in self.layout().text + assert "WORD" in self.layout().title() self.debug.input("stick") br = yield assert br.code == messages.ButtonRequestType.Warning - assert "invalid recovery seed" in self.layout().text + assert "invalid recovery seed" in self.layout().text_content() self.debug.press_right() yield # retry screen - assert "select the number of words" in self.layout().text + assert "select the number of words" in self.layout().text_content() self.debug.press_left() yield - assert "abort" in self.layout().text + assert "abort" in self.layout().text_content() self.debug.press_right() @@ -1130,41 +1130,41 @@ def bip39_recovery_possible_pin( ) -> GeneratorType: yield assert ( - "Do you really want to recover a wallet?" in debug.wait_layout().get_content() + "Do you really want to recover a wallet?" in debug.wait_layout().text_content() ) debug.press_yes() # PIN when requested if pin is not None: yield - assert debug.wait_layout().text == "< PinKeyboard >" + assert debug.wait_layout().str_content == "< PinKeyboard >" debug.input(pin) yield - assert debug.wait_layout().text == "< PinKeyboard >" + assert debug.wait_layout().str_content == "< PinKeyboard >" debug.input(pin) yield - assert "Select number of words" in debug.wait_layout().get_content() + assert "Select number of words" in debug.wait_layout().text_content() debug.press_yes() yield - assert "SelectWordCount" in debug.wait_layout().text + assert "SelectWordCount" in debug.wait_layout().str_content debug.input(str(len(mnemonic))) yield - assert "Enter recovery seed" in debug.wait_layout().get_content() + assert "Enter recovery seed" in debug.wait_layout().text_content() debug.press_yes() yield for word in mnemonic: - assert debug.wait_layout().text == "< MnemonicKeyboard >" + assert debug.wait_layout().str_content == "< MnemonicKeyboard >" debug.input(word) yield assert ( "You have successfully recovered your wallet." - in debug.wait_layout().get_content() + in debug.wait_layout().text_content() ) debug.press_yes() @@ -1179,47 +1179,49 @@ class InputFlowBip39RecoveryPIN(InputFlowBase): def input_flow_tr(self) -> GeneratorType: yield - assert "By continuing you agree" in self.layout().text + assert "By continuing you agree" in self.layout().text_content() self.debug.press_right() - assert "trezor.io/tos" in self.layout().text + assert "trezor.io/tos" in self.layout().text_content() self.debug.press_yes() yield - assert "ENTER" in self.layout().text + assert "ENTER" in self.layout().text_content() self.debug.input("654") yield - assert "re-enter PIN to confirm" in self.layout().text + assert "re-enter PIN to confirm" in self.layout().text_content() self.debug.press_right() yield - assert "ENTER" in self.layout().text + assert "ENTER" in self.layout().text_content() self.debug.input("654") yield - assert "select the number of words" in self.layout().text + assert "select the number of words" in self.layout().text_content() self.debug.press_yes() yield yield - assert "NUMBER OF WORDS" in self.layout().text + assert "NUMBER OF WORDS" in self.layout().title() self.debug.input(str(len(self.mnemonic))) yield - assert "enter your recovery seed" in self.layout().text + assert "enter your recovery seed" in self.layout().text_content() self.debug.press_yes() yield - assert "WORD ENTERING" in self.layout().text + assert "WORD ENTERING" in self.layout().title() self.debug.press_right() yield for word in self.mnemonic: - assert "WORD" in self.layout().text + assert "WORD" in self.layout().title() self.debug.input(word) yield - assert "You have finished recovering your wallet." in self.layout().text + assert ( + "You have finished recovering your wallet." in self.layout().text_content() + ) self.debug.press_yes() diff --git a/tests/persistence_tests/test_shamir_persistence.py b/tests/persistence_tests/test_shamir_persistence.py index 04688711ba..fb673bb302 100644 --- a/tests/persistence_tests/test_shamir_persistence.py +++ b/tests/persistence_tests/test_shamir_persistence.py @@ -50,10 +50,10 @@ def test_abort(emulator: Emulator): device_handler.run(device.recover, pin_protection=False, show_tutorial=False) layout = debug.wait_layout() - assert layout.get_title() == "RECOVERY MODE" + assert layout.title() == "RECOVERY MODE" layout = debug.click(buttons.OK, wait=True) - assert "select the number of words" in layout.text + assert "select the number of words" in layout.str_content device_handler.restart(emulator) debug = device_handler.debuglink() @@ -63,13 +63,13 @@ def test_abort(emulator: Emulator): # no waiting for layout because layout doesn't change layout = debug.read_layout() - assert "select the number of words" in layout.text + assert "select the number of words" in layout.str_content layout = debug.click(buttons.CANCEL, wait=True) - assert layout.get_title() == "ABORT RECOVERY" + assert layout.title() == "ABORT RECOVERY" layout = debug.click(buttons.OK, wait=True) - assert layout.text.startswith("< Homescreen") + assert layout.str_content.startswith("< Homescreen") features = device_handler.features() assert features.recovery_mode is False @@ -136,10 +136,10 @@ def test_recovery_on_old_wallet(emulator: Emulator): # start entering first share layout = debug.read_layout() - assert "Enter any share" in layout.text + assert "Enter any share" in layout.str_content debug.press_yes() layout = debug.wait_layout() - assert layout.text == "< MnemonicKeyboard >" + assert layout.str_content == "< MnemonicKeyboard >" # enter first word debug.input(words[0]) @@ -151,12 +151,12 @@ def test_recovery_on_old_wallet(emulator: Emulator): # try entering remaining 19 words for word in words[1:]: - assert layout.text == "< MnemonicKeyboard >" + assert layout.str_content == "< MnemonicKeyboard >" debug.input(word) layout = debug.wait_layout() # check that we entered the first share successfully - assert "2 more shares" in layout.text + assert "2 more shares" in layout.str_content # try entering the remaining shares for share in MNEMONIC_SLIP39_BASIC_20_3of6[1:3]: @@ -178,13 +178,13 @@ def test_recovery_multiple_resets(emulator: Emulator): expected_text = "Enter any share" remaining = len(shares) for share in shares: - assert expected_text in layout.text + assert expected_text in layout.str_content layout = recovery.enter_share(debug, share) remaining -= 1 expected_text = "You have entered" debug = _restart(device_handler, emulator) - assert "You have successfully recovered your wallet" in layout.get_content() + assert "You have successfully recovered your wallet" in layout.text_content() device_handler = BackgroundDeviceHandler(emulator.client) debug = device_handler.debuglink() @@ -212,7 +212,7 @@ def test_recovery_multiple_resets(emulator: Emulator): enter_shares_with_restarts(debug) debug = device_handler.debuglink() layout = debug.read_layout() - assert layout.text.startswith("< Homescreen") + assert layout.str_content.startswith("< Homescreen") features = device_handler.features() assert features.initialized is True diff --git a/tests/upgrade_tests/test_firmware_upgrades.py b/tests/upgrade_tests/test_firmware_upgrades.py index d7fe175ff4..83f5f52a59 100644 --- a/tests/upgrade_tests/test_firmware_upgrades.py +++ b/tests/upgrade_tests/test_firmware_upgrades.py @@ -317,7 +317,7 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]): layout = recovery.enter_share( debug, MNEMONIC_SLIP39_BASIC_20_3of6[0], legacy_ui=legacy_ui ) - assert "2 more shares" in layout.text + assert "2 more shares" in layout.str_content device_id = emu.client.features.device_id storage = emu.get_storage() @@ -331,11 +331,11 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]): # second share layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[2]) - assert "1 more share" in layout.text + assert "1 more share" in layout.str_content # last one layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[1]) - assert "You have successfully" in layout.text + assert "You have successfully" in layout.str_content # Check the result state = debug.state()