1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-21 05:58:09 +00:00

WIP - move debug-trace processing logic to Debuglink

This commit is contained in:
grdddj 2023-01-19 17:04:12 +01:00
parent d928a390d8
commit 002976e16d
32 changed files with 445 additions and 526 deletions

View File

@ -28,7 +28,6 @@ message DebugLinkDecision {
DOWN = 1; DOWN = 1;
LEFT = 2; LEFT = 2;
RIGHT = 3; RIGHT = 3;
ALL_THE_WAY_UP = 4;
} }
/** /**

View File

@ -14,7 +14,7 @@ pub trait Tracer {
fn title(&mut self, title: &str); fn title(&mut self, title: &str);
fn button(&mut self, button: &str); fn button(&mut self, button: &str);
fn content_flag(&mut self); 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); fn close(&mut self);
} }
@ -143,10 +143,10 @@ mod tests {
} }
/// Key-value pair for easy parsing /// 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(key);
self.string("::"); self.string("::");
self.string(value); value.trace(self);
self.string(","); // mostly for human readability self.string(","); // mostly for human readability
} }

View File

@ -271,10 +271,10 @@ impl LayoutObj {
self.string(crate::trace::CONTENT_TAG); 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(key);
self.string("::"); self.string("::");
self.string(value); value.trace(self);
self.string(","); // mostly for human readability self.string(","); // mostly for human readability
} }

View File

@ -15,7 +15,7 @@ use super::{
/// To be returned directly from Flow. /// To be returned directly from Flow.
pub enum FlowMsg { pub enum FlowMsg {
Confirmed, Confirmed,
ConfirmedIndex(u8), ConfirmedIndex(usize),
Cancelled, Cancelled,
} }
@ -31,13 +31,13 @@ pub struct Flow<F, const M: usize> {
title_area: Rect, title_area: Rect,
pad: Pad, pad: Pad,
buttons: Child<ButtonController>, buttons: Child<ButtonController>,
page_counter: u8, page_counter: usize,
return_confirmed_index: bool, return_confirmed_index: bool,
} }
impl<F, const M: usize> Flow<F, M> impl<F, const M: usize> Flow<F, M>
where where
F: Fn(u8) -> Page<M>, F: Fn(usize) -> Page<M>,
{ {
pub fn new(pages: FlowPages<F, M>) -> Self { pub fn new(pages: FlowPages<F, M>) -> Self {
let current_page = pages.get(0); let current_page = pages.get(0);
@ -123,16 +123,16 @@ where
/// Negative index means counting from the end. /// Negative index means counting from the end.
fn go_to_page_absolute(&mut self, index: i16, ctx: &mut EventCtx) { fn go_to_page_absolute(&mut self, index: i16, ctx: &mut EventCtx) {
if index < 0 { 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 { } else {
self.page_counter = index as u8; self.page_counter = index as usize;
} }
self.update(ctx, true); self.update(ctx, true);
} }
/// Jumping to another page relative to the current one. /// Jumping to another page relative to the current one.
fn go_to_page_relative(&mut self, jump: i16, ctx: &mut EventCtx) { 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); self.update(ctx, true);
} }
@ -176,7 +176,7 @@ where
impl<F, const M: usize> Component for Flow<F, M> impl<F, const M: usize> Component for Flow<F, M>
where where
F: Fn(u8) -> Page<M>, F: Fn(usize) -> Page<M>,
{ {
type Msg = FlowMsg; type Msg = FlowMsg;
@ -190,14 +190,15 @@ where
}; };
self.content_area = content_area; 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 // Finding out the total amount of pages in this flow
let complete_page_count = self.pages.scrollbar_page_count(content_area); let complete_page_count = self.pages.scrollbar_page_count(content_area);
self.scrollbar self.scrollbar
.inner_mut() .inner_mut()
.set_page_count(complete_page_count); .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) = let (title_area, scrollbar_area) =
title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE); title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE);
@ -288,7 +289,7 @@ use heapless::String;
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<F, const M: usize> crate::trace::Trace for Flow<F, M> impl<F, const M: usize> crate::trace::Trace for Flow<F, M>
where where
F: Fn(u8) -> Page<M>, F: Fn(usize) -> Page<M>,
{ {
/// Accounting for the possibility that button is connected with the /// Accounting for the possibility that button is connected with the
/// currently paginated flow_page (only Prev or Next in that case). /// 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) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Flow"); t.open("Flow");
t.kw_pair("flow_page", inttostr!(self.page_counter)); t.kw_pair("flow_page", &self.page_counter);
t.kw_pair("flow_page_count", inttostr!(self.pages.count())); t.kw_pair("flow_page_count", &self.pages.count());
self.report_btn_actions(t); self.report_btn_actions(t);
if self.title.is_some() { if self.title.is_some() {
t.field("title", &self.title); t.field("title", &self.title);
} }
t.field("content_area", &self.content_area); t.field("scrollbar", &self.scrollbar);
t.field("buttons", &self.buttons); t.field("buttons", &self.buttons);
t.field("flow_page", &self.current_page); t.field("flow_page", &self.current_page);
t.close() t.close()

View File

@ -32,14 +32,14 @@ pub struct FlowPages<F, const M: usize> {
/// Function/closure that will return appropriate page on demand. /// Function/closure that will return appropriate page on demand.
get_page: F, get_page: F,
/// Number of pages in the flow. /// Number of pages in the flow.
page_count: u8, page_count: usize,
} }
impl<F, const M: usize> FlowPages<F, M> impl<F, const M: usize> FlowPages<F, M>
where where
F: Fn(u8) -> Page<M>, F: Fn(usize) -> Page<M>,
{ {
pub fn new(get_page: F, page_count: u8) -> Self { pub fn new(get_page: F, page_count: usize) -> Self {
Self { Self {
get_page, get_page,
page_count, page_count,
@ -47,12 +47,12 @@ where
} }
/// Returns a page on demand on a specified index. /// Returns a page on demand on a specified index.
pub fn get(&self, page_index: u8) -> Page<M> { pub fn get(&self, page_index: usize) -> Page<M> {
(self.get_page)(page_index) (self.get_page)(page_index)
} }
/// Total amount of pages. /// Total amount of pages.
pub fn count(&self) -> u8 { pub fn count(&self) -> usize {
self.page_count self.page_count
} }
@ -63,8 +63,8 @@ where
} }
/// Active scrollbar position connected with the beginning of a specific /// Active scrollbar position connected with the beginning of a specific
/// page index /// page index.
pub fn scrollbar_page_index(&self, bounds: Rect, page_index: u8) -> usize { pub fn scrollbar_page_index(&self, bounds: Rect, page_index: usize) -> usize {
let mut page_count = 0; let mut page_count = 0;
for i in 0..page_index { for i in 0..page_index {
let mut current_page = self.get(i); let mut current_page = self.get(i);
@ -364,8 +364,8 @@ pub mod trace {
impl<const M: usize> crate::trace::Trace for Page<M> { impl<const M: usize> crate::trace::Trace for Page<M> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Page"); t.open("Page");
t.kw_pair("active_page", inttostr!(self.current_page as u8)); t.kw_pair("active_page", &self.current_page);
t.kw_pair("page_count", inttostr!(self.page_count as u8)); t.kw_pair("page_count", &self.page_count);
t.field("content", &trace::TraceText(self)); t.field("content", &trace::TraceText(self));
t.close(); t.close();
} }

View File

@ -8,7 +8,7 @@ use crate::ui::{
use super::super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos}; use super::super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos};
pub enum ChoicePageMsg { pub enum ChoicePageMsg {
Choice(u8), Choice(usize),
LeftMost, LeftMost,
RightMost, RightMost,
} }
@ -46,8 +46,8 @@ pub trait ChoiceFactory {
type Item: Choice + Trace; type Item: Choice + Trace;
#[cfg(not(feature = "ui_debug"))] #[cfg(not(feature = "ui_debug"))]
type Item: Choice; type Item: Choice;
fn count(&self) -> u8; fn count(&self) -> usize;
fn get(&self, index: u8) -> Self::Item; fn get(&self, index: usize) -> Self::Item;
} }
/// General component displaying a set of items on the screen /// General component displaying a set of items on the screen
@ -70,7 +70,7 @@ where
choices: F, choices: F,
pad: Pad, pad: Pad,
buttons: Child<ButtonController>, buttons: Child<ButtonController>,
page_counter: u8, page_counter: usize,
/// How many pixels from top should we render the items. /// How many pixels from top should we render the items.
y_baseline: i16, y_baseline: i16,
/// How many pixels are between the items. /// How many pixels are between the items.
@ -110,7 +110,7 @@ where
/// Set the page counter at the very beginning. /// Set the page counter at the very beginning.
/// Need to update the initial button layout. /// 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; self.page_counter = page_counter;
let initial_btn_layout = self.choices.get(page_counter).btn_layout(); let initial_btn_layout = self.choices.get(page_counter).btn_layout();
self.buttons = Child::new(ButtonController::new(initial_btn_layout)); self.buttons = Child::new(ButtonController::new(initial_btn_layout));
@ -156,7 +156,7 @@ where
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
new_choices: F, new_choices: F,
new_page_counter: Option<u8>, new_page_counter: Option<usize>,
is_carousel: bool, is_carousel: bool,
) { ) {
self.choices = new_choices; self.choices = new_choices;
@ -168,7 +168,7 @@ where
} }
/// Navigating to the chosen page index. /// 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.page_counter = page_counter;
self.update(ctx); self.update(ctx);
} }
@ -188,7 +188,7 @@ where
// Getting the remaining left and right areas. // Getting the remaining left and right areas.
let (left_area, _center_area, right_area) = 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. // Possibly drawing on the left side.
if self.has_previous_choice() || self.is_carousel { if self.has_previous_choice() || self.is_carousel {
@ -214,8 +214,8 @@ where
} }
/// Index of the last page. /// Index of the last page.
fn last_page_index(&self) -> u8 { fn last_page_index(&self) -> usize {
self.choices.count() as u8 - 1 self.choices.count() - 1
} }
/// Whether there is a previous choice (on the left). /// Whether there is a previous choice (on the left).
@ -243,6 +243,7 @@ where
/// Display all the choices fitting on the left side. /// Display all the choices fitting on the left side.
/// Going as far as possible. /// Going as far as possible.
fn show_left_choices(&self, area: Rect) { 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 page_index = self.page_counter as i16 - 1;
let mut x_offset = 0; let mut x_offset = 0;
loop { loop {
@ -261,7 +262,7 @@ where
if let Some(width) = self if let Some(width) = self
.choices .choices
.get(page_index as u8) .get(page_index as usize)
.paint_left(current_area, self.show_incomplete) .paint_left(current_area, self.show_incomplete)
{ {
// Updating loop variables. // Updating loop variables.
@ -293,7 +294,7 @@ where
if let Some(width) = self if let Some(width) = self
.choices .choices
.get(page_index as u8) .get(page_index as usize)
.paint_right(current_area, self.show_incomplete) .paint_right(current_area, self.show_incomplete)
{ {
// Updating loop variables. // Updating loop variables.
@ -326,7 +327,7 @@ where
} }
/// Get current page counter. /// Get current page counter.
pub fn page_index(&self) -> u8 { pub fn page_index(&self) -> usize {
self.page_counter self.page_counter
} }
@ -430,9 +431,9 @@ where
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ChoicePage"); t.open("ChoicePage");
t.kw_pair("active_page", inttostr!(self.page_counter)); t.kw_pair("active_page", &self.page_counter);
t.kw_pair("page_count", inttostr!(self.choices.count() as u8)); t.kw_pair("page_count", &self.choices.count());
t.kw_pair("is_carousel", booltostr!(self.is_carousel)); t.kw_pair("is_carousel", &booltostr!(self.is_carousel));
if self.has_previous_choice() { if self.has_previous_choice() {
t.field("prev_choice", &self.choices.get(self.page_counter - 1)); t.field("prev_choice", &self.choices.get(self.page_counter - 1));

View File

@ -24,11 +24,11 @@ impl ChoiceFactoryNumberInput {
impl ChoiceFactory for ChoiceFactoryNumberInput { impl ChoiceFactory for ChoiceFactoryNumberInput {
type Item = ChoiceItem; type Item = ChoiceItem;
fn count(&self) -> u8 { fn count(&self) -> usize {
(self.max - self.min + 1) as u8 (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 num = self.min + choice_index as u32;
let text: String<10> = String::from(num); let text: String<10> = String::from(num);
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons()); let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
@ -59,7 +59,7 @@ impl NumberInput {
let initial_page = init_value - min; let initial_page = init_value - min;
Self { Self {
min, 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),
} }
} }
} }

View File

@ -62,7 +62,7 @@ const MENU: [&str; MENU_LENGTH] = [
]; ];
/// Get a character at a specified index for a specified category. /// 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; let index = index as usize;
match current_category { match current_category {
ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS[index], 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. /// 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 { match page_index as usize {
LOWERCASE_INDEX => ChoiceCategory::LowercaseLetter, LOWERCASE_INDEX => ChoiceCategory::LowercaseLetter,
UPPERCASE_INDEX => ChoiceCategory::UppercaseLetter, 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. /// How many choices are available for a specified category.
/// (does not count the extra MENU choice for characters) /// (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 { match current_category {
ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS.len() as u8, ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS.len(),
ChoiceCategory::UppercaseLetter => UPPERCASE_LETTERS.len() as u8, ChoiceCategory::UppercaseLetter => UPPERCASE_LETTERS.len(),
ChoiceCategory::Digit => DIGITS.len() as u8, ChoiceCategory::Digit => DIGITS.len(),
ChoiceCategory::SpecialSymbol => SPECIAL_SYMBOLS.len() as u8, ChoiceCategory::SpecialSymbol => SPECIAL_SYMBOLS.len(),
ChoiceCategory::Menu => MENU.len() as u8, ChoiceCategory::Menu => MENU.len(),
} }
} }
/// Whether this index is the MENU index - the last one in the list. /// 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 { if let ChoiceCategory::Menu = current_category {
unreachable!() unreachable!()
} }
@ -120,7 +120,7 @@ impl ChoiceFactoryPassphrase {
} }
/// MENU choices with accept and cancel hold-to-confirm side buttons. /// 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; let choice_index = choice_index as usize;
// More options for CANCEL/DELETE button // 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 /// Character choices with a BACK to MENU choice at the end (visible from
/// start) to return back /// 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) { if is_menu_choice(&self.current_category, choice_index) {
ChoiceItem::new("BACK", ButtonLayout::arrow_armed_arrow("RETURN".into())) ChoiceItem::new("BACK", ButtonLayout::arrow_armed_arrow("RETURN".into()))
.with_icon(Icon::new(theme::ICON_ARROW_BACK_UP)) .with_icon(Icon::new(theme::ICON_ARROW_BACK_UP))
@ -178,7 +178,7 @@ impl ChoiceFactoryPassphrase {
impl ChoiceFactory for ChoiceFactoryPassphrase { impl ChoiceFactory for ChoiceFactoryPassphrase {
type Item = ChoiceItem; type Item = ChoiceItem;
fn count(&self) -> u8 { fn count(&self) -> usize {
let length = get_category_length(&self.current_category); let length = get_category_length(&self.current_category);
// All non-MENU categories have an extra item for returning back to MENU // All non-MENU categories have an extra item for returning back to MENU
match self.current_category { match self.current_category {
@ -186,7 +186,7 @@ impl ChoiceFactory for ChoiceFactoryPassphrase {
_ => length + 1, _ => length + 1,
} }
} }
fn get(&self, choice_index: u8) -> ChoiceItem { fn get(&self, choice_index: usize) -> ChoiceItem {
match self.current_category { match self.current_category {
ChoiceCategory::Menu => self.get_menu_item(choice_index), ChoiceCategory::Menu => self.get_menu_item(choice_index),
_ => self.get_character_item(choice_index), _ => self.get_character_item(choice_index),
@ -201,7 +201,7 @@ pub struct PassphraseEntry {
show_plain_passphrase: bool, show_plain_passphrase: bool,
textbox: TextBox<MAX_PASSPHRASE_LENGTH>, textbox: TextBox<MAX_PASSPHRASE_LENGTH>,
current_category: ChoiceCategory, 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 { impl PassphraseEntry {
@ -209,7 +209,7 @@ impl PassphraseEntry {
Self { Self {
choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true)) choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true))
.with_carousel(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())), passphrase_dots: Child::new(ChangingTextLine::center_mono(String::new())),
show_plain_passphrase: false, show_plain_passphrase: false,
textbox: TextBox::empty(), textbox: TextBox::empty(),
@ -298,7 +298,7 @@ impl Component for PassphraseEntry {
self.update_passphrase_dots(ctx); self.update_passphrase_dots(ctx);
if self.is_empty() { if self.is_empty() {
// Allowing for DELETE/CANCEL change // Allowing for DELETE/CANCEL change
self.menu_position = CANCEL_DELETE_INDEX as u8; self.menu_position = CANCEL_DELETE_INDEX;
self.show_menu_page(ctx); self.show_menu_page(ctx);
} }
ctx.request_paint(); ctx.request_paint();
@ -410,7 +410,7 @@ impl crate::trace::Trace for PassphraseEntry {
t.open("PassphraseEntry"); t.open("PassphraseEntry");
// NOTE: `show_plain_passphrase` was not able to be transferred, // NOTE: `show_plain_passphrase` was not able to be transferred,
// as it is true only for a very small amount of time // 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); self.report_btn_actions(t);
t.field("choice_page", &self.choice_page); t.field("choice_page", &self.choice_page);
t.close(); t.close();

View File

@ -42,31 +42,31 @@ impl ChoiceFactoryPIN {
impl ChoiceFactory for ChoiceFactoryPIN { impl ChoiceFactory for ChoiceFactoryPIN {
type Item = ChoiceItem; type Item = ChoiceItem;
fn get(&self, choice_index: u8) -> ChoiceItem { fn get(&self, choice_index: usize) -> ChoiceItem {
let choice_str = CHOICES[choice_index as usize]; let choice_str = CHOICES[choice_index];
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons()); let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
// Action buttons have different middle button text // 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()); let confirm_btn = ButtonDetails::armed_text("CONFIRM".into());
choice_item.set_middle_btn(Some(confirm_btn)); choice_item.set_middle_btn(Some(confirm_btn));
} }
// Adding icons for appropriate items // 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)); 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)); 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 = choice_item.with_icon(Icon::new(theme::ICON_TICK));
} }
choice_item choice_item
} }
fn count(&self) -> u8 { fn count(&self) -> usize {
CHOICE_LENGTH as u8 CHOICE_LENGTH
} }
} }
@ -87,7 +87,7 @@ impl PinEntry {
Self { Self {
// Starting at the digit 0 // Starting at the digit 0
choice_page: ChoicePage::new(choices) choice_page: ChoicePage::new(choices)
.with_initial_page_counter(NUMBER_START_INDEX as u8) .with_initial_page_counter(NUMBER_START_INDEX)
.with_carousel(true), .with_carousel(true),
pin_line: Child::new(ChangingTextLine::center_bold(String::from( pin_line: Child::new(ChangingTextLine::center_bold(String::from(
prompt.clone().as_ref(), prompt.clone().as_ref(),
@ -99,8 +99,8 @@ impl PinEntry {
} }
} }
fn append_new_digit(&mut self, ctx: &mut EventCtx, page_counter: u8) { fn append_new_digit(&mut self, ctx: &mut EventCtx, page_counter: usize) {
let digit = CHOICES[page_counter as usize]; let digit = CHOICES[page_counter];
self.textbox.append_slice(ctx, digit); self.textbox.append_slice(ctx, digit);
} }
@ -180,7 +180,7 @@ impl Component for PinEntry {
let msg = self.choice_page.event(ctx, event); let msg = self.choice_page.event(ctx, event);
if let Some(ChoicePageMsg::Choice(page_counter)) = msg { if let Some(ChoicePageMsg::Choice(page_counter)) = msg {
// Performing action under specific index or appending new digit // Performing action under specific index or appending new digit
match page_counter as usize { match page_counter {
DELETE_INDEX => { DELETE_INDEX => {
self.delete_last_digit(ctx); self.delete_last_digit(ctx);
self.update(ctx); self.update(ctx);
@ -201,7 +201,7 @@ impl Component for PinEntry {
page_counter as u32, page_counter as u32,
); );
self.choice_page self.choice_page
.set_page_counter(ctx, new_page_counter as u8); .set_page_counter(ctx, new_page_counter as usize);
self.update(ctx); self.update(ctx);
} }
} }
@ -229,7 +229,7 @@ impl crate::trace::Trace for PinEntry {
ButtonPos::Left => ButtonAction::PrevPage.string(), ButtonPos::Left => ButtonAction::PrevPage.string(),
ButtonPos::Right => ButtonAction::NextPage.string(), ButtonPos::Right => ButtonAction::NextPage.string(),
ButtonPos::Middle => { ButtonPos::Middle => {
let current_index = self.choice_page.page_index() as usize; let current_index = self.choice_page.page_index();
match current_index { match current_index {
DELETE_INDEX => ButtonAction::Action("Delete last digit").string(), DELETE_INDEX => ButtonAction::Action("Delete last digit").string(),
SHOW_INDEX => ButtonAction::Action("Show PIN").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, // NOTE: `show_real_pin` was not able to be transferred,
// as it is true only for a very small amount of time // as it is true only for a very small amount of time
t.title(self.prompt.as_ref()); 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); self.report_btn_actions(t);
t.field("choice_page", &self.choice_page); t.field("choice_page", &self.choice_page);
t.close(); t.close();

View File

@ -11,7 +11,7 @@ use heapless::{String, Vec};
pub enum SimpleChoiceMsg { pub enum SimpleChoiceMsg {
Result(String<50>), Result(String<50>),
Index(u8), Index(usize),
} }
struct ChoiceFactorySimple<const N: usize> { struct ChoiceFactorySimple<const N: usize> {
@ -28,12 +28,12 @@ impl<const N: usize> ChoiceFactorySimple<N> {
impl<const N: usize> ChoiceFactory for ChoiceFactorySimple<N> { impl<const N: usize> ChoiceFactory for ChoiceFactorySimple<N> {
type Item = ChoiceItem; type Item = ChoiceItem;
fn count(&self) -> u8 { fn count(&self) -> usize {
N as u8 N
} }
fn get(&self, choice_index: u8) -> ChoiceItem { fn get(&self, choice_index: usize) -> ChoiceItem {
let text = &self.choices[choice_index as usize]; let text = &self.choices[choice_index];
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons()); let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
// Disabling prev/next buttons for the first/last choice when not in carousel. // Disabling prev/next buttons for the first/last choice when not in carousel.
@ -42,7 +42,7 @@ impl<const N: usize> ChoiceFactory for ChoiceFactorySimple<N> {
if choice_index == 0 { if choice_index == 0 {
choice_item.set_left_btn(None); choice_item.set_left_btn(None);
} }
if choice_index as usize == N - 1 { if choice_index == N - 1 {
choice_item.set_right_btn(None); choice_item.set_right_btn(None);
} }
} }
@ -102,7 +102,7 @@ impl<const N: usize> Component for SimpleChoice<N> {
if self.return_index { if self.return_index {
Some(SimpleChoiceMsg::Index(page_counter)) Some(SimpleChoiceMsg::Index(page_counter))
} else { } 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)) Some(SimpleChoiceMsg::Result(result))
} }
} }
@ -133,7 +133,7 @@ impl<const N: usize> crate::trace::Trace for SimpleChoice<N> {
false => ButtonAction::empty(), false => ButtonAction::empty(),
}, },
ButtonPos::Middle => { 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()) ButtonAction::select_item(self.choices[current_index].as_ref())
} }
} }

View File

@ -25,10 +25,10 @@ const MAX_LETTERS_LENGTH: usize = 26;
const OFFER_WORDS_THRESHOLD: usize = 10; const OFFER_WORDS_THRESHOLD: usize = 10;
/// Where will be the DELETE option - at the first position /// 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. /// Which index will be used at the beginning.
/// (Accounts for DELETE to be at index 0) /// (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 = "_"; const PROMPT: &str = "_";
@ -57,15 +57,15 @@ impl ChoiceFactoryWordlist {
impl ChoiceFactory for ChoiceFactoryWordlist { impl ChoiceFactory for ChoiceFactoryWordlist {
type Item = ChoiceItem; type Item = ChoiceItem;
fn count(&self) -> u8 { fn count(&self) -> usize {
// Accounting for the DELETE option // Accounting for the DELETE option
match self { match self {
Self::Letters(letter_choices) => letter_choices.len() as u8 + 1, Self::Letters(letter_choices) => letter_choices.len() + 1,
Self::Words(word_choices) => word_choices.len() as u8 + 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 // Letters have a carousel, words do not
// Putting DELETE as the first option in both cases // Putting DELETE as the first option in both cases
// (is a requirement for WORDS, doing it for LETTERS as well to unite it) // (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) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Bip39Entry"); t.open("Bip39Entry");
t.kw_pair("textbox", self.textbox.content()); t.kw_pair("textbox", &self.textbox.content());
self.report_btn_actions(t); self.report_btn_actions(t);

View File

@ -249,8 +249,8 @@ where
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ButtonPage"); t.open("ButtonPage");
t.kw_pair("active_page", inttostr!(self.active_page as u8)); t.kw_pair("active_page", &self.active_page);
t.kw_pair("page_count", inttostr!(self.page_count as u8)); t.kw_pair("page_count", &self.page_count);
self.report_btn_actions(t); self.report_btn_actions(t);
// TODO: it seems the button text is not updated when paginating (but actions // TODO: it seems the button text is not updated when paginating (but actions
// above are) // above are)

View File

@ -247,8 +247,8 @@ impl Component for ScrollBar {
impl crate::trace::Trace for ScrollBar { impl crate::trace::Trace for ScrollBar {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ScrollBar"); t.open("ScrollBar");
t.field("page_count", &self.page_count); t.kw_pair("scrollbar_page_count", &self.page_count);
t.field("active_page", &self.active_page); t.kw_pair("scrollbar_active_page", &self.active_page);
t.close(); t.close();
} }
} }

View File

@ -93,13 +93,13 @@ where
impl<F, const M: usize> ComponentMsgObj for Flow<F, M> impl<F, const M: usize> ComponentMsgObj for Flow<F, M>
where where
F: Fn(u8) -> Page<M>, F: Fn(usize) -> Page<M>,
{ {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg { match msg {
FlowMsg::Confirmed => Ok(CONFIRMED.as_obj()), FlowMsg::Confirmed => Ok(CONFIRMED.as_obj()),
FlowMsg::Cancelled => Ok(CANCELLED.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<const N: usize> ComponentMsgObj for SimpleChoice<N> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg { match msg {
SimpleChoiceMsg::Result(choice) => choice.as_str().try_into(), 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 { extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| { let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: u8 = 7; const PAGE_COUNT: usize = 7;
let get_page = |page_index| { let get_page = |page_index| {
// Lazy-loaded list of screens to show, with custom content, // 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) .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. // Returning the page index in case of confirmation.
let obj = LayoutObj::new( let obj = LayoutObj::new(
Flow::new(pages) Flow::new(pages)

View File

@ -73,7 +73,6 @@ if __debug__:
SWIPE_DOWN, SWIPE_DOWN,
SWIPE_LEFT, SWIPE_LEFT,
SWIPE_RIGHT, SWIPE_RIGHT,
SWIPE_ALL_THE_WAY_UP,
) )
button = msg.button # local_cache_attribute button = msg.button # local_cache_attribute
@ -99,8 +98,6 @@ if __debug__:
await swipe_chan.put(SWIPE_LEFT) await swipe_chan.put(SWIPE_LEFT)
elif swipe == DebugSwipeDirection.RIGHT: elif swipe == DebugSwipeDirection.RIGHT:
await swipe_chan.put(SWIPE_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: if msg.input is not None:
await input_chan.put(Result(msg.input)) await input_chan.put(Result(msg.input))

View File

@ -6,4 +6,3 @@ UP = 0
DOWN = 1 DOWN = 1
LEFT = 2 LEFT = 2
RIGHT = 3 RIGHT = 3
ALL_THE_WAY_UP = 4

View File

@ -449,7 +449,6 @@ if TYPE_CHECKING:
DOWN = 1 DOWN = 1
LEFT = 2 LEFT = 2
RIGHT = 3 RIGHT = 3
ALL_THE_WAY_UP = 4
class DebugButton(IntEnum): class DebugButton(IntEnum):
NO = 0 NO = 0

View File

@ -22,7 +22,6 @@ if __debug__:
SWIPE_DOWN = const(0x02) SWIPE_DOWN = const(0x02)
SWIPE_LEFT = const(0x04) SWIPE_LEFT = const(0x04)
SWIPE_RIGHT = const(0x08) SWIPE_RIGHT = const(0x08)
SWIPE_ALL_THE_WAY_UP = const(0x10)
# channel used to cancel layouts, see `Cancelled` exception # channel used to cancel layouts, see `Cancelled` exception

View File

@ -37,14 +37,7 @@ async def interact(
br_type: str, br_type: str,
br_code: ButtonRequestType = ButtonRequestType.Other, br_code: ButtonRequestType = ButtonRequestType.Other,
) -> Any: ) -> Any:
if hasattr(layout, "in_unknown_flow") and layout.in_unknown_flow(): # type: ignore [Cannot access member "in_unknown_flow" for type "LayoutType"] if hasattr(layout, "page_count") and layout.page_count() > 1: # type: ignore [Cannot access member "page_count" 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"]
# We know for certain how many pages the layout will have # 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"] 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) return await ctx.wait(layout)

View File

@ -30,125 +30,23 @@ if __debug__:
Used only in debug mode. Used only in debug mode.
""" """
# How will some information be identified in the content # TODO: used only because of `page_count`
TITLE_TAG = " **TITLE** " # We could do it some other way
CONTENT_TAG = " **CONTENT** " # TT does this:
BTN_TAG = " **BTN** " # def page_count(self) -> int:
EMPTY_BTN = "---" # return self.layout.page_count()
NEXT_BTN = "Next"
PREV_BTN = "Prev"
def __init__(self, raw_content: list[str]) -> None: def __init__(self, raw_content: list[str]) -> None:
self.raw_content = raw_content self.raw_content = raw_content
self.str_content = " ".join(raw_content).replace(" ", " ") 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: def page_count(self) -> int:
"""Overall number of pages in this screen. Should always be there.""" """Overall number of pages in this screen. Should always be there."""
return self.kw_pair_int("page_count") or 1 return (
self.kw_pair_int("scrollbar_page_count")
def in_flow(self) -> bool: or self.kw_pair_int("page_count")
"""Whether we are in flow.""" or 1
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 "\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: def kw_pair_int(self, key: str) -> int | None:
"""Getting the value of a key-value pair as an integer. None if missing.""" """Getting the value of a key-value pair as an integer. None if missing."""
@ -157,12 +55,6 @@ if __debug__:
return None return None
return int(val) 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: def kw_pair(self, key: str) -> str | None:
"""Getting the value of a key-value pair. None if missing.""" """Getting the value of a key-value pair. None if missing."""
# Pairs are sent in this format in the list: # Pairs are sent in this format in the list:
@ -174,16 +66,6 @@ if __debug__:
return None 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): class RustLayout(ui.Layout):
# pylint: disable=super-init-not-called # pylint: disable=super-init-not-called
@ -227,12 +109,6 @@ class RustLayout(ui.Layout):
if __debug__: if __debug__:
from trezor.enums import DebugPhysicalButton 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] def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: # type: ignore [obscured-by-same-name]
from apps.debug import confirm_signal, input_signal from apps.debug import confirm_signal, input_signal
@ -248,7 +124,8 @@ class RustLayout(ui.Layout):
def read_content(self) -> list[str]: def read_content(self) -> list[str]:
"""Gets the visible content of the screen.""" """Gets the visible content of the screen."""
self._place_layout() 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: def _place_layout(self) -> None:
"""It is necessary to place the layout to get data about its screen content.""" """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_UP,
SWIPE_DOWN, SWIPE_DOWN,
) )
from trezor.enums import DebugPhysicalButton
content_obj = self._content_obj()
if direction == SWIPE_UP: if direction == SWIPE_UP:
btn_to_press = content_obj.get_next_button() btn_to_press = DebugPhysicalButton.RIGHT_BTN
elif direction == SWIPE_DOWN: elif direction == SWIPE_DOWN:
btn_to_press = content_obj.get_prev_button() btn_to_press = DebugPhysicalButton.LEFT_BTN
else: else:
raise Exception(f"Unsupported direction: {direction}") raise Exception(f"Unsupported direction: {direction}")
assert btn_to_press is not None self._press_button(btn_to_press)
self._press_button(self.BTN_MAP[btn_to_press])
async def handle_swipe(self) -> None: async def handle_swipe(self) -> None:
"""Enables pagination through the current page/flow page. """Enables pagination through the current page/flow page.
@ -334,16 +209,9 @@ class RustLayout(ui.Layout):
Waits for `swipe_signal` and carries it out. Waits for `swipe_signal` and carries it out.
""" """
from apps.debug import swipe_signal from apps.debug import swipe_signal
from trezor.ui import SWIPE_ALL_THE_WAY_UP, SWIPE_UP
while True: while True:
direction = await swipe_signal() 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: async def handle_button_click(self) -> None:
@ -359,17 +227,9 @@ class RustLayout(ui.Layout):
def page_count(self) -> int: def page_count(self) -> int:
"""How many paginated pages current screen has.""" """How many paginated pages current screen has."""
# TODO: leave it debug-only or use always?
self._place_layout() self._place_layout()
return self._content_obj().page_count() 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: else:
def create_tasks(self) -> tuple[loop.Task, ...]: def create_tasks(self) -> tuple[loop.Task, ...]:

View File

@ -115,6 +115,7 @@ class RustLayout(ui.Layout):
notify_layout_change(self) notify_layout_change(self)
def notify_backup(self): def notify_backup(self):
# TODO: delete this in favor of debuglink::LayoutContent::seed_words
from apps.debug import reset_current_words from apps.debug import reset_current_words
content = "\n".join(self.read_content()) content = "\n".join(self.read_content())

View File

@ -61,84 +61,204 @@ EXPECTED_RESPONSES_CONTEXT_LINES = 3
LOG = logging.getLogger(__name__) 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. """Stores content of a layout as returned from Trezor.
Contains helper functions to extract specific parts of the layout. 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: def __init__(self, lines: Sequence[str]) -> None:
self.lines = list(lines) super().__init__(lines)
self.text = " ".join(self.lines) self.buttons = LayoutButtons(lines)
def get_title(self) -> str: def visible_screen(self) -> str:
"""Get title of the layout. """String representation of a current screen content.
Example:
Title is located between "title" and "content" identifiers. SIGN TRANSACTION
Example: "< Frame title : RECOVERY SHARE #1 content : < SwipePage" --------------------
-> "RECOVERY SHARE #1" You are about to
sign 3 actions.
********************
Icon:cancel [Cancel], --- [None], CONFIRM [Confirm]
""" """
match = re.search(r"title : (.*?) content :", self.text) title_separator = f"\n{20*'-'}\n"
if not match: btn_separator = f"\n{20*'*'}\n"
return ""
return match.group(1).strip()
def get_content(self, tag_name: str = "Paragraphs", raw: bool = False) -> str: visible = ""
"""Get text of the main screen content of the layout.""" if self.title():
content = "".join(self._get_content_lines(tag_name, raw)) visible += self.title()
if not raw and content.endswith(" "): visible += title_separator
# Stripping possible space at the end visible += self.raw_content()
content = content[:-1] if self.buttons.is_applicable():
return content visible += btn_separator
visible += self.buttons.visible()
def get_button_texts(self) -> List[str]: return visible
"""Get text of all buttons in the layout.
Example button: "< Button text : LADYBUG >" def title(self) -> str:
-> ["LADYBUG"] """Getting text that is displayed as a title."""
""" # there could be multiple of those - title and subtitle for example
return re.findall(r"< Button text : +(.*?) >", self.text) 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. """Get all the seed words on the screen in order.
Example content: "1. ladybug 2. acid 3. academic 4. afraid" Example content: "1. ladybug 2. acid 3. academic 4. afraid"
-> ["ladybug", "acid", "academic", "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.""" """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.""" """Get current page index of the layout."""
return self._get_number("active_page") return (
self.kw_pair_int("scrollbar_active_page")
def _get_number(self, key: str) -> int: or self.kw_pair_int("active_page")
"""Get number connected with a specific key.""" or 0
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]
def _clean_line(line: str) -> str: def _clean_line(line: str) -> str:
@ -170,10 +290,10 @@ def multipage_content(layouts: List[LayoutContent]) -> str:
"""Get overall content from multiple-page layout.""" """Get overall content from multiple-page layout."""
final_text = "" final_text = ""
for layout in layouts: for layout in layouts:
final_text += layout.get_content() final_text += layout.text_content()
# When the raw content of the page ends with ellipsis, # When the raw content of the page ends with ellipsis,
# we need to add a space to separate it with the next page # 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 += " " final_text += " "
# Stripping possible space at the end of last page # Stripping possible space at the end of last page
@ -328,23 +448,21 @@ class DebugLink:
layout = self.wait_layout() layout = self.wait_layout()
else: else:
layout = self.read_layout() 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 self.screen_text_file is not None:
if not self.screen_text_file.exists(): if not self.screen_text_file.exists():
self.screen_text_file.write_bytes(b"") self.screen_text_file.write_bytes(b"")
content = "\n".join(lines)
# Not writing the same screen twice # Not writing the same screen twice
if content == self.last_screen_content: if screen_content == self.last_screen_content:
return return
self.last_screen_content = content self.last_screen_content = screen_content
with open(self.screen_text_file, "a") as f: with open(self.screen_text_file, "a") as f:
f.write(content) f.write(screen_content)
f.write("\n" + 80 * "/" + "\n") f.write("\n" + 80 * "/" + "\n")
# Type overloads make sure that when we supply `wait=True` into `click()`, # Type overloads make sure that when we supply `wait=True` into `click()`,
@ -373,9 +491,6 @@ class DebugLink:
def press_info(self) -> None: def press_info(self) -> None:
self.input(button=messages.DebugButton.INFO) 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: def swipe_up(self, wait: bool = False) -> None:
self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait) self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait)
@ -521,10 +636,6 @@ class DebugUI:
else: else:
# Paginating (going as further as possible) and pressing Yes # Paginating (going as further as possible) and pressing Yes
if br.pages is not None: 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): for _ in range(br.pages - 1):
self.debuglink.swipe_up(wait=True) self.debuglink.swipe_up(wait=True)
self.debuglink.press_yes() self.debuglink.press_yes()

View File

@ -484,7 +484,6 @@ class DebugSwipeDirection(IntEnum):
DOWN = 1 DOWN = 1
LEFT = 2 LEFT = 2
RIGHT = 3 RIGHT = 3
ALL_THE_WAY_UP = 4
class DebugButton(IntEnum): class DebugButton(IntEnum):

View File

@ -23,9 +23,9 @@ def enter_word(
def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None: def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None:
layout = debug.wait_layout() layout = debug.wait_layout()
if legacy_ui: if legacy_ui:
layout.text.startswith("Recovery mode") layout.str_content.startswith("Recovery mode")
else: else:
assert layout.get_title() == "RECOVERY MODE" assert layout.title() == "RECOVERY MODE"
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
@ -35,13 +35,13 @@ def select_number_of_words(
layout = debug.read_layout() layout = debug.read_layout()
# select number of words # 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) layout = debug.click(buttons.OK, wait=True)
if legacy_ui: if legacy_ui:
assert layout.text == "WordSelector" assert layout.str_content == "WordSelector"
else: else:
# Two title options # Two title options
assert layout.get_title() in ("SEED CHECK", "RECOVERY MODE") assert layout.title() in ("SEED CHECK", "RECOVERY MODE")
# click the number # click the number
word_option_offset = 6 word_option_offset = 6
@ -51,7 +51,7 @@ def select_number_of_words(
) # raises if num of words is invalid ) # raises if num of words is invalid
coords = buttons.grid34(index % 3, index // 3) coords = buttons.grid34(index % 3, index // 3)
layout = debug.click(coords, wait=True) 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( def enter_share(
@ -60,9 +60,9 @@ def enter_share(
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
if legacy_ui: if legacy_ui:
assert layout.text == "Slip39Keyboard" assert layout.str_content == "Slip39Keyboard"
else: else:
assert layout.text == "< MnemonicKeyboard >" assert layout.str_content == "< MnemonicKeyboard >"
for word in share.split(" "): for word in share.split(" "):
layout = enter_word(debug, word, is_slip39=True) 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" expected_text = "Enter any share"
remaining = len(shares) remaining = len(shares)
for share in shares: for share in shares:
assert expected_text in layout.get_content() assert expected_text in layout.text_content()
layout = enter_share(debug, share) layout = enter_share(debug, share)
remaining -= 1 remaining -= 1
expected_text = f"{remaining} more share" 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: def finalize(debug: "DebugLink") -> None:
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
# TODO: should we also run Click/Persistence tests for model R? # TODO: should we also run Click/Persistence tests for model R?
assert layout.text.startswith("< Homescreen ") assert layout.str_content.startswith("< Homescreen ")

View File

@ -12,26 +12,26 @@ if TYPE_CHECKING:
def confirm_wait(debug: "DebugLink", title: str) -> None: def confirm_wait(debug: "DebugLink", title: str) -> None:
layout = debug.wait_layout() layout = debug.wait_layout()
assert title.upper() in layout.get_title() assert title.upper() in layout.title()
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
def confirm_read(debug: "DebugLink", title: str) -> None: def confirm_read(debug: "DebugLink", title: str) -> None:
layout = debug.read_layout() layout = debug.read_layout()
if title == "Caution": if title == "Caution":
assert "OK, I UNDERSTAND" in layout.text assert "OK, I UNDERSTAND" in layout.str_content
elif title == "Success": elif title == "Success":
assert any( 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: else:
assert title.upper() in layout.get_title() assert title.upper() in layout.title()
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None: def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
layout = debug.read_layout() layout = debug.read_layout()
assert "NumberInputDialog" in layout.text assert "NumberInputDialog" in layout.str_content
for _ in range(diff): for _ in range(diff):
debug.click(button, wait=False) debug.click(button, wait=False)
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
@ -41,15 +41,15 @@ def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]:
words: list[str] = [] words: list[str] = []
layout = debug.read_layout() layout = debug.read_layout()
if is_advanced: if is_advanced:
assert layout.get_title().startswith("GROUP") assert layout.title().startswith("GROUP")
else: else:
assert layout.get_title().startswith("RECOVERY SHARE #") assert layout.title().startswith("RECOVERY SHARE #")
# Swiping through all the page and loading the words # Swiping through all the page and loading the words
for _ in range(layout.get_page_count() - 1): for _ in range(layout.page_count() - 1):
words.extend(layout.get_seed_words()) words.extend(layout.seed_words())
layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True) layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True)
words.extend(layout.get_seed_words()) words.extend(layout.seed_words())
debug.press_yes() 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: def confirm_words(debug: "DebugLink", words: list[str]) -> None:
layout = debug.wait_layout() layout = debug.wait_layout()
assert "Select word" in layout.text assert "Select word" in layout.str_content
for _ in range(3): for _ in range(3):
# "Select word 3 of 20" # "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 # 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() wanted_word = words[word_pos - 1].lower()
button_pos = btn_texts.index(wanted_word) button_pos = btn_texts.index(wanted_word)
layout = debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True) layout = debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True)

View File

@ -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) device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
debug.input("1234") debug.input("1234")
layout = debug.wait_layout() layout = debug.wait_layout()
assert ( assert (
f"auto-lock your device after {delay_ms // 1000} seconds" f"auto-lock your device after {delay_ms // 1000} seconds"
in layout.get_content() in layout.text_content()
) )
debug.click(buttons.OK) debug.click(buttons.OK)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("< Homescreen") assert layout.str_content.startswith("< Homescreen")
assert device_handler.result() == "Settings applied" 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) device_handler.run(btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE)
layout = debug.wait_layout() 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)
debug.click(buttons.OK, wait=True) debug.click(buttons.OK, wait=True)
layout = 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 # wait for autolock to kick in
time.sleep(10.1) time.sleep(10.1)
@ -108,7 +110,7 @@ def test_autolock_passphrase_keyboard(device_handler: "BackgroundDeviceHandler")
# enter passphrase - slowly # enter passphrase - slowly
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "PassphraseKeyboard" assert layout.str_content == "PassphraseKeyboard"
CENTER_BUTTON = buttons.grid35(1, 2) CENTER_BUTTON = buttons.grid35(1, 2)
for _ in range(11): for _ in range(11):
@ -127,26 +129,26 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle
# unlock # unlock
layout = debug.wait_layout() 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) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) 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 # wait for autolock to trigger
time.sleep(10.1) time.sleep(10.1)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen") assert layout.str_content.startswith("< Lockscreen")
with pytest.raises(exceptions.Cancelled): with pytest.raises(exceptions.Cancelled):
device_handler.result() device_handler.result()
# unlock # unlock
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# we are back at homescreen # 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) @pytest.mark.setup_client(pin=PIN4)
@ -158,9 +160,9 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
# unlock # unlock
layout = debug.wait_layout() 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) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# select 20 words # select 20 words
@ -168,10 +170,10 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
# make sure keyboard locks # make sure keyboard locks
assert layout.text == "< MnemonicKeyboard >" assert layout.str_content == "< MnemonicKeyboard >"
time.sleep(10.1) time.sleep(10.1)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen") assert layout.str_content.startswith("< Lockscreen")
with pytest.raises(exceptions.Cancelled): with pytest.raises(exceptions.Cancelled):
device_handler.result() device_handler.result()
@ -185,9 +187,9 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
# unlock # unlock
layout = debug.wait_layout() 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) layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True) layout = debug.input(PIN4, wait=True)
# select 20 words # select 20 words
@ -195,11 +197,11 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
# type the word OCEAN slowly # type the word OCEAN slowly
assert layout.text == "< MnemonicKeyboard >" assert layout.str_content == "< MnemonicKeyboard >"
for coords in buttons.type_word("ocea", is_slip39=True): for coords in buttons.type_word("ocea", is_slip39=True):
time.sleep(9) time.sleep(9)
debug.click(coords) debug.click(coords)
layout = debug.click(buttons.CONFIRM_WORD, wait=True) layout = debug.click(buttons.CONFIRM_WORD, wait=True)
# should not have locked, even though we took 9 seconds to type each letter # 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() device_handler.kill_task()

View File

@ -41,7 +41,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
# unlock with message # unlock with message
device_handler.run(common.get_test_address) device_handler.run(common.get_test_address)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
debug.input("1234", wait=True) debug.input("1234", wait=True)
assert device_handler.result() assert device_handler.result()
@ -57,7 +57,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
# unlock by touching # unlock by touching
layout = debug.click(buttons.INFO, wait=True) layout = debug.click(buttons.INFO, wait=True)
assert layout.text == "< PinKeyboard >" assert layout.str_content == "< PinKeyboard >"
debug.input("1234", wait=True) debug.input("1234", wait=True)
assert device_handler.features().unlocked is True assert device_handler.features().unlocked is True

View File

@ -25,7 +25,6 @@ from trezorlib import btc, tools
from trezorlib.messages import ButtonRequestType from trezorlib.messages import ButtonRequestType
if TYPE_CHECKING: if TYPE_CHECKING:
from trezorlib.debuglink import LayoutContent
from trezorlib.debuglink import DebugLink, TrezorClientDebugLink as Client from trezorlib.debuglink import DebugLink, TrezorClientDebugLink as Client
from trezorlib.messages import ButtonRequest from trezorlib.messages import ButtonRequest
from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import MarkDecorator
@ -332,8 +331,7 @@ def read_and_confirm_mnemonic_tr(
assert br.pages is not None assert br.pages is not None
for _ in range(br.pages): for _ in range(br.pages):
layout = debug.wait_layout() layout = debug.wait_layout()
words = layout.seed_words()
words = ModelRLayout(layout).get_mnemonic_words()
mnemonic.extend(words) mnemonic.extend(words)
debug.press_right() debug.press_right()
@ -351,47 +349,6 @@ def read_and_confirm_mnemonic_tr(
return " ".join(mnemonic) 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"): def click_info_button(debug: "DebugLink"):
"""Click Shamir backup info button and return back.""" """Click Shamir backup info button and return back."""
debug.press_info() 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: def get_text_from_paginated_screen(client: "Client", screen_count: int) -> str:
"""Aggregating screen text from more pages into one string.""" """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): for _ in range(screen_count - 1):
client.debug.swipe_up() client.debug.swipe_up()
text += client.debug.wait_layout().text text += client.debug.wait_layout().str_content
return text return text

View File

@ -53,19 +53,19 @@ def test_sd_protect_unlock(client: Client):
def input_flow_enable_sd_protect(): def input_flow_enable_sd_protect():
yield # Enter PIN to unlock device yield # Enter PIN to unlock device
assert "< PinKeyboard >" == layout().text assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234") client.debug.input("1234")
yield # do you really want to enable SD protection 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() client.debug.press_yes()
yield # enter current PIN yield # enter current PIN
assert "< PinKeyboard >" == layout().text assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234") client.debug.input("1234")
yield # you have successfully enabled SD protection 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() client.debug.press_yes()
with client: with client:
@ -75,23 +75,23 @@ def test_sd_protect_unlock(client: Client):
def input_flow_change_pin(): def input_flow_change_pin():
yield # do you really want to 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() client.debug.press_yes()
yield # enter current PIN yield # enter current PIN
assert "< PinKeyboard >" == layout().text assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234") client.debug.input("1234")
yield # enter new PIN yield # enter new PIN
assert "< PinKeyboard >" == layout().text assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234") client.debug.input("1234")
yield # enter new PIN again yield # enter new PIN again
assert "< PinKeyboard >" == layout().text assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234") client.debug.input("1234")
yield # Pin change successful 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() client.debug.press_yes()
with client: with client:
@ -103,15 +103,15 @@ def test_sd_protect_unlock(client: Client):
def input_flow_change_pin_format(): def input_flow_change_pin_format():
yield # do you really want to 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() client.debug.press_yes()
yield # enter current PIN yield # enter current PIN
assert "< PinKeyboard >" == layout().text assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234") client.debug.input("1234")
yield # SD card problem 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 client.debug.press_no() # close
with client, pytest.raises(TrezorFailure) as e: with client, pytest.raises(TrezorFailure) as e:

View File

@ -225,12 +225,12 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
def input_flow_tt(self) -> GeneratorType: def input_flow_tt(self) -> GeneratorType:
yield # show address yield # show address
layout = self.debug.wait_layout() # TODO: do not need to *wait* now? layout = self.debug.wait_layout() # TODO: do not need to *wait* now?
assert layout.get_title() == "MULTISIG 2 OF 3" assert layout.title() == "MULTISIG 2 OF 3"
assert layout.get_content().replace(" ", "") == self.address assert layout.text_content().replace(" ", "") == self.address
self.debug.press_no() self.debug.press_no()
yield # show QR code 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 # Three xpub pages with the same testing logic
for xpub_num in range(3): for xpub_num in range(3):
@ -241,12 +241,12 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
self.debug.press_no() self.debug.press_no()
yield # show XPUB#{xpub_num} yield # show XPUB#{xpub_num}
layout1 = self.debug.wait_layout() layout1 = self.debug.wait_layout()
assert layout1.get_title() == expected_title assert layout1.title() == expected_title
self.debug.swipe_up() self.debug.swipe_up()
layout2 = self.debug.wait_layout() layout2 = self.debug.wait_layout()
assert layout2.get_title() == expected_title assert layout2.title() == expected_title
content = (layout1.get_content() + layout2.get_content()).replace(" ", "") content = (layout1.text_content() + layout2.text_content()).replace(" ", "")
assert content == self.xpubs[xpub_num] assert content == self.xpubs[xpub_num]
self.debug.press_yes() self.debug.press_yes()
@ -263,14 +263,14 @@ class InputFlowPaymentRequestDetails(InputFlowBase):
self.debug.press_info() self.debug.press_info()
yield # confirm first output 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() self.debug.press_yes()
yield # confirm first output yield # confirm first output
self.debug.wait_layout() self.debug.wait_layout()
self.debug.press_yes() self.debug.press_yes()
yield # confirm second output 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() self.debug.press_yes()
yield # confirm second output yield # confirm second output
self.debug.wait_layout() self.debug.wait_layout()
@ -325,7 +325,7 @@ def lock_time_input_flow_tt(
debug.press_yes() debug.press_yes()
yield # confirm locktime yield # confirm locktime
layout_text = debug.wait_layout().text layout_text = debug.wait_layout().text_content()
layout_assert_func(layout_text) layout_assert_func(layout_text)
debug.press_yes() debug.press_yes()
@ -345,7 +345,7 @@ def lock_time_input_flow_tr(
debug.press_yes() debug.press_yes()
yield # confirm locktime yield # confirm locktime
layout_text = debug.wait_layout().text layout_text = debug.wait_layout().text_content()
layout_assert_func(layout_text) layout_assert_func(layout_text)
debug.press_yes() debug.press_yes()
@ -989,15 +989,15 @@ class InputFlowSlip39AdvancedResetRecovery(InputFlowBase):
def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> GeneratorType: def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> GeneratorType:
yield 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) debug.click(buttons.OK)
yield 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) debug.click(buttons.OK)
yield yield
assert "SelectWordCount" in debug.wait_layout().text assert "SelectWordCount" in debug.wait_layout().str_content
# click the correct number # click the correct number
word_option_offset = 6 word_option_offset = 6
word_options = (12, 18, 20, 24, 33) 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)) debug.click(buttons.grid34(index % 3, index // 3))
yield 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) debug.click(buttons.OK)
yield yield
for word in mnemonic: for word in mnemonic:
assert debug.wait_layout().text == "< MnemonicKeyboard >" assert debug.wait_layout().str_content == "< MnemonicKeyboard >"
debug.input(word) debug.input(word)
@ -1028,16 +1028,16 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase):
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield yield
assert "check the recovery seed" in self.layout().text assert "check the recovery seed" in self.layout().text_content()
self.debug.press_right() self.debug.press_right()
yield 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() self.debug.press_yes()
yield yield
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) word_options = (12, 18, 20, 24, 33)
index = word_options.index(len(self.mnemonic)) index = word_options.index(len(self.mnemonic))
for _ in range(index): for _ in range(index):
@ -1045,14 +1045,14 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase):
self.debug.input(str(len(self.mnemonic))) self.debug.input(str(len(self.mnemonic)))
yield yield
assert "enter your recovery seed" in self.layout().text assert "enter your recovery seed" in self.layout().text_content()
self.debug.press_yes() self.debug.press_yes()
yield yield
self.debug.press_right() self.debug.press_right()
yield yield
for word in self.mnemonic: for word in self.mnemonic:
assert "WORD" in self.layout().text assert "WORD" in self.layout().title()
self.debug.input(word) self.debug.input(word)
self.debug.wait_layout() self.debug.wait_layout()
@ -1072,56 +1072,56 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning 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) self.debug.click(buttons.OK)
yield # retry screen 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) self.debug.click(buttons.CANCEL)
yield yield
assert "ABORT SEED CHECK" == self.layout().get_title() assert "ABORT SEED CHECK" == self.layout().title()
self.debug.click(buttons.OK) self.debug.click(buttons.OK)
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield yield
assert "check the recovery seed" in self.layout().text assert "check the recovery seed" in self.layout().text_content()
self.debug.press_right() self.debug.press_right()
yield 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() self.debug.press_yes()
yield yield
yield yield
assert "NUMBER OF WORDS" in self.layout().text assert "NUMBER OF WORDS" in self.layout().title()
# select 12 words # select 12 words
self.debug.press_middle() self.debug.press_middle()
yield yield
assert "enter your recovery seed" in self.layout().text assert "enter your recovery seed" in self.layout().text_content()
self.debug.press_yes() self.debug.press_yes()
yield yield
assert "WORD ENTERING" in self.layout().text assert "WORD ENTERING" in self.layout().title()
self.debug.press_yes() self.debug.press_yes()
yield yield
for _ in range(12): for _ in range(12):
assert "WORD" in self.layout().text assert "WORD" in self.layout().title()
self.debug.input("stick") self.debug.input("stick")
br = yield br = yield
assert br.code == messages.ButtonRequestType.Warning 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() self.debug.press_right()
yield # retry screen 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() self.debug.press_left()
yield yield
assert "abort" in self.layout().text assert "abort" in self.layout().text_content()
self.debug.press_right() self.debug.press_right()
@ -1130,41 +1130,41 @@ def bip39_recovery_possible_pin(
) -> GeneratorType: ) -> GeneratorType:
yield yield
assert ( 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() debug.press_yes()
# PIN when requested # PIN when requested
if pin is not None: if pin is not None:
yield yield
assert debug.wait_layout().text == "< PinKeyboard >" assert debug.wait_layout().str_content == "< PinKeyboard >"
debug.input(pin) debug.input(pin)
yield yield
assert debug.wait_layout().text == "< PinKeyboard >" assert debug.wait_layout().str_content == "< PinKeyboard >"
debug.input(pin) debug.input(pin)
yield 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() debug.press_yes()
yield yield
assert "SelectWordCount" in debug.wait_layout().text assert "SelectWordCount" in debug.wait_layout().str_content
debug.input(str(len(mnemonic))) debug.input(str(len(mnemonic)))
yield yield
assert "Enter recovery seed" in debug.wait_layout().get_content() assert "Enter recovery seed" in debug.wait_layout().text_content()
debug.press_yes() debug.press_yes()
yield yield
for word in mnemonic: for word in mnemonic:
assert debug.wait_layout().text == "< MnemonicKeyboard >" assert debug.wait_layout().str_content == "< MnemonicKeyboard >"
debug.input(word) debug.input(word)
yield yield
assert ( assert (
"You have successfully recovered your wallet." "You have successfully recovered your wallet."
in debug.wait_layout().get_content() in debug.wait_layout().text_content()
) )
debug.press_yes() debug.press_yes()
@ -1179,47 +1179,49 @@ class InputFlowBip39RecoveryPIN(InputFlowBase):
def input_flow_tr(self) -> GeneratorType: def input_flow_tr(self) -> GeneratorType:
yield yield
assert "By continuing you agree" in self.layout().text assert "By continuing you agree" in self.layout().text_content()
self.debug.press_right() 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() self.debug.press_yes()
yield yield
assert "ENTER" in self.layout().text assert "ENTER" in self.layout().text_content()
self.debug.input("654") self.debug.input("654")
yield 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() self.debug.press_right()
yield yield
assert "ENTER" in self.layout().text assert "ENTER" in self.layout().text_content()
self.debug.input("654") self.debug.input("654")
yield 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() self.debug.press_yes()
yield yield
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))) self.debug.input(str(len(self.mnemonic)))
yield yield
assert "enter your recovery seed" in self.layout().text assert "enter your recovery seed" in self.layout().text_content()
self.debug.press_yes() self.debug.press_yes()
yield yield
assert "WORD ENTERING" in self.layout().text assert "WORD ENTERING" in self.layout().title()
self.debug.press_right() self.debug.press_right()
yield yield
for word in self.mnemonic: for word in self.mnemonic:
assert "WORD" in self.layout().text assert "WORD" in self.layout().title()
self.debug.input(word) self.debug.input(word)
yield 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() self.debug.press_yes()

View File

@ -50,10 +50,10 @@ def test_abort(emulator: Emulator):
device_handler.run(device.recover, pin_protection=False, show_tutorial=False) device_handler.run(device.recover, pin_protection=False, show_tutorial=False)
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.get_title() == "RECOVERY MODE" assert layout.title() == "RECOVERY MODE"
layout = debug.click(buttons.OK, wait=True) 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) device_handler.restart(emulator)
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -63,13 +63,13 @@ def test_abort(emulator: Emulator):
# no waiting for layout because layout doesn't change # no waiting for layout because layout doesn't change
layout = debug.read_layout() 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) 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) layout = debug.click(buttons.OK, wait=True)
assert layout.text.startswith("< Homescreen") assert layout.str_content.startswith("< Homescreen")
features = device_handler.features() features = device_handler.features()
assert features.recovery_mode is False assert features.recovery_mode is False
@ -136,10 +136,10 @@ def test_recovery_on_old_wallet(emulator: Emulator):
# start entering first share # start entering first share
layout = debug.read_layout() layout = debug.read_layout()
assert "Enter any share" in layout.text assert "Enter any share" in layout.str_content
debug.press_yes() debug.press_yes()
layout = debug.wait_layout() layout = debug.wait_layout()
assert layout.text == "< MnemonicKeyboard >" assert layout.str_content == "< MnemonicKeyboard >"
# enter first word # enter first word
debug.input(words[0]) debug.input(words[0])
@ -151,12 +151,12 @@ def test_recovery_on_old_wallet(emulator: Emulator):
# try entering remaining 19 words # try entering remaining 19 words
for word in words[1:]: for word in words[1:]:
assert layout.text == "< MnemonicKeyboard >" assert layout.str_content == "< MnemonicKeyboard >"
debug.input(word) debug.input(word)
layout = debug.wait_layout() layout = debug.wait_layout()
# check that we entered the first share successfully # 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 # try entering the remaining shares
for share in MNEMONIC_SLIP39_BASIC_20_3of6[1:3]: 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" expected_text = "Enter any share"
remaining = len(shares) remaining = len(shares)
for share in shares: for share in shares:
assert expected_text in layout.text assert expected_text in layout.str_content
layout = recovery.enter_share(debug, share) layout = recovery.enter_share(debug, share)
remaining -= 1 remaining -= 1
expected_text = "You have entered" expected_text = "You have entered"
debug = _restart(device_handler, emulator) 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) device_handler = BackgroundDeviceHandler(emulator.client)
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -212,7 +212,7 @@ def test_recovery_multiple_resets(emulator: Emulator):
enter_shares_with_restarts(debug) enter_shares_with_restarts(debug)
debug = device_handler.debuglink() debug = device_handler.debuglink()
layout = debug.read_layout() layout = debug.read_layout()
assert layout.text.startswith("< Homescreen") assert layout.str_content.startswith("< Homescreen")
features = device_handler.features() features = device_handler.features()
assert features.initialized is True assert features.initialized is True

View File

@ -317,7 +317,7 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
layout = recovery.enter_share( layout = recovery.enter_share(
debug, MNEMONIC_SLIP39_BASIC_20_3of6[0], legacy_ui=legacy_ui 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 device_id = emu.client.features.device_id
storage = emu.get_storage() storage = emu.get_storage()
@ -331,11 +331,11 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
# second share # second share
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[2]) 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 # last one
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[1]) 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 # Check the result
state = debug.state() state = debug.state()