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:
parent
d928a390d8
commit
002976e16d
@ -28,7 +28,6 @@ message DebugLinkDecision {
|
|||||||
DOWN = 1;
|
DOWN = 1;
|
||||||
LEFT = 2;
|
LEFT = 2;
|
||||||
RIGHT = 3;
|
RIGHT = 3;
|
||||||
ALL_THE_WAY_UP = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -6,4 +6,3 @@ UP = 0
|
|||||||
DOWN = 1
|
DOWN = 1
|
||||||
LEFT = 2
|
LEFT = 2
|
||||||
RIGHT = 3
|
RIGHT = 3
|
||||||
ALL_THE_WAY_UP = 4
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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, ...]:
|
||||||
|
@ -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())
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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 ")
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user