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