mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-03 21:32:33 +00:00
TR-core/rust: implement Coinjoin progress screen
This commit is contained in:
parent
cde8d29e51
commit
e1252e9ee9
@ -7,9 +7,11 @@ pub mod toif;
|
|||||||
|
|
||||||
use heapless::String;
|
use heapless::String;
|
||||||
|
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
constant,
|
constant,
|
||||||
geometry::{Offset, Point, Rect},
|
geometry::{Alignment, Offset, Point, Rect},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "dma2d")]
|
#[cfg(feature = "dma2d")]
|
||||||
use crate::trezorhal::{
|
use crate::trezorhal::{
|
||||||
@ -853,50 +855,86 @@ pub fn paint_point(point: &Point, color: Color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Draws longer multiline texts inside an area.
|
/// Draws longer multiline texts inside an area.
|
||||||
/// Does not add any characters on the line boundaries.
|
/// Splits lines on word boundaries/whitespace.
|
||||||
|
/// When a word is too long to fit one line, splitting
|
||||||
|
/// it on multiple lines with "-" at the line-ends.
|
||||||
///
|
///
|
||||||
/// If it fits, returns the rest of the area.
|
/// If it fits, returns the rest of the area.
|
||||||
/// If it does not fit, returns `None`.
|
/// If it does not fit, returns `None`.
|
||||||
pub fn text_multiline(
|
pub fn text_multiline_split_words(
|
||||||
area: Rect,
|
area: Rect,
|
||||||
text: &str,
|
text: &str,
|
||||||
font: Font,
|
font: Font,
|
||||||
fg_color: Color,
|
fg_color: Color,
|
||||||
bg_color: Color,
|
bg_color: Color,
|
||||||
|
alignment: Alignment,
|
||||||
) -> Option<Rect> {
|
) -> Option<Rect> {
|
||||||
let line_height = font.line_height();
|
let line_height = font.line_height();
|
||||||
let characters_overall = text.chars().count();
|
|
||||||
let mut taken_from_top = 0;
|
let mut taken_from_top = 0;
|
||||||
let mut characters_drawn = 0;
|
let mut chars_processed = 0;
|
||||||
|
|
||||||
|
let mut text_iter = text.split_whitespace();
|
||||||
|
let mut word_from_prev_line = None;
|
||||||
|
|
||||||
'lines: loop {
|
'lines: loop {
|
||||||
let baseline = area.top_left() + Offset::y(line_height + taken_from_top);
|
let baseline_left = area.top_left() + Offset::y(line_height + taken_from_top);
|
||||||
if !area.contains(baseline) {
|
if !area.contains(baseline_left) {
|
||||||
// The whole area was consumed.
|
// The whole area was consumed.
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut line_text: String<50> = String::new();
|
let mut line_text: String<100> = String::new();
|
||||||
'characters: loop {
|
|
||||||
if let Some(character) = text.chars().nth(characters_drawn) {
|
'words: while let Some(word) = word_from_prev_line.take().or_else(|| text_iter.next()) {
|
||||||
characters_drawn += 1;
|
let prev_line_text_len = line_text.len();
|
||||||
if character == '\n' {
|
if !line_text.is_empty() {
|
||||||
// The line is forced to end.
|
// Putting spaces in between words.
|
||||||
break 'characters;
|
unwrap!(line_text.push(' '));
|
||||||
}
|
}
|
||||||
unwrap!(line_text.push(character));
|
if write!(&mut line_text, "{}", word).is_err() {
|
||||||
|
// We have a word/line longer than 100 chars.
|
||||||
|
// Add just 50 characters, that is enough for this line.
|
||||||
|
unwrap!(write!(&mut line_text, "{}", &word[..50]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if font.text_width(&line_text) <= area.width() {
|
||||||
|
chars_processed += word.chars().count() + 1;
|
||||||
} else {
|
} else {
|
||||||
// No more characters to draw.
|
// The word does not fit on the line anymore.
|
||||||
break 'characters;
|
// Word can be longer than the whole line - in that case splitting it to more
|
||||||
}
|
// lines
|
||||||
if font.text_width(&line_text) > area.width() {
|
if prev_line_text_len == 0 {
|
||||||
// Cannot fit on the line anymore.
|
for (idx, _) in word.char_indices() {
|
||||||
line_text.pop();
|
if font.text_width(&word[..idx]) > area.width() {
|
||||||
characters_drawn -= 1;
|
let split_idx = idx - 1;
|
||||||
break 'characters;
|
let chars_fitting_this_line = split_idx - 1; // accounting for the hyphen we will add
|
||||||
|
line_text = String::from(&word[..chars_fitting_this_line]);
|
||||||
|
unwrap!(line_text.push('-'));
|
||||||
|
chars_processed += chars_fitting_this_line;
|
||||||
|
word_from_prev_line = Some(&word[chars_fitting_this_line..]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line_text.truncate(prev_line_text_len);
|
||||||
|
word_from_prev_line = Some(word);
|
||||||
|
}
|
||||||
|
break 'words;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match alignment {
|
||||||
|
Alignment::Start => text_left(baseline_left, &line_text, font, fg_color, bg_color),
|
||||||
|
Alignment::Center => {
|
||||||
|
let baseline_center = baseline_left + Offset::x(area.width() / 2);
|
||||||
|
text_center(baseline_center, &line_text, font, fg_color, bg_color)
|
||||||
|
}
|
||||||
|
Alignment::End => {
|
||||||
|
let baseline_right = baseline_left + Offset::x(area.width());
|
||||||
|
text_right(baseline_right, &line_text, font, fg_color, bg_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text_left(baseline, &line_text, font, fg_color, bg_color);
|
|
||||||
taken_from_top += line_height;
|
taken_from_top += line_height;
|
||||||
if characters_drawn == characters_overall {
|
if chars_processed >= text.chars().count() {
|
||||||
// No more lines to draw.
|
// No more lines to draw.
|
||||||
break 'lines;
|
break 'lines;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
use crate::{
|
||||||
|
micropython::buffer::StrBuffer,
|
||||||
|
ui::{
|
||||||
|
component::{base::Never, Component, Event, EventCtx},
|
||||||
|
display::{text_multiline_split_words, Font},
|
||||||
|
geometry::{Alignment, Rect},
|
||||||
|
model_tr::theme,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const HEADER: &str = "COINJOIN IN PROGRESS";
|
||||||
|
const FOOTER: &str = "Don't disconnect your Trezor";
|
||||||
|
|
||||||
|
pub struct CoinJoinProgress {
|
||||||
|
text: StrBuffer,
|
||||||
|
area: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoinJoinProgress {
|
||||||
|
pub fn new(text: StrBuffer, _indeterminate: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
text,
|
||||||
|
area: Rect::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for CoinJoinProgress {
|
||||||
|
type Msg = Never;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.area = bounds;
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
// Trying to paint all three parts into the area, stopping if any of them
|
||||||
|
// doesn't fit.
|
||||||
|
let mut possible_rest = text_multiline_split_words(
|
||||||
|
self.area,
|
||||||
|
HEADER,
|
||||||
|
Font::NORMAL,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
Alignment::Center,
|
||||||
|
);
|
||||||
|
if let Some(rest) = possible_rest {
|
||||||
|
possible_rest = text_multiline_split_words(
|
||||||
|
rest,
|
||||||
|
self.text.as_ref(),
|
||||||
|
Font::MONO,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
Alignment::Center,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(rest) = possible_rest {
|
||||||
|
text_multiline_split_words(
|
||||||
|
rest,
|
||||||
|
FOOTER,
|
||||||
|
Font::BOLD,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
Alignment::Center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl crate::trace::Trace for CoinJoinProgress {
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("CoinJoinProgress");
|
||||||
|
t.string(self.text.as_ref());
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ mod address_details;
|
|||||||
mod button;
|
mod button;
|
||||||
mod button_controller;
|
mod button_controller;
|
||||||
mod changing_text;
|
mod changing_text;
|
||||||
|
mod coinjoin_progress;
|
||||||
mod common;
|
mod common;
|
||||||
mod flow;
|
mod flow;
|
||||||
mod flow_pages;
|
mod flow_pages;
|
||||||
@ -34,6 +35,7 @@ pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
|||||||
|
|
||||||
pub use button_controller::{ButtonController, ButtonControllerMsg};
|
pub use button_controller::{ButtonController, ButtonControllerMsg};
|
||||||
pub use changing_text::ChangingTextLine;
|
pub use changing_text::ChangingTextLine;
|
||||||
|
pub use coinjoin_progress::CoinJoinProgress;
|
||||||
pub use flow::{Flow, FlowMsg};
|
pub use flow::{Flow, FlowMsg};
|
||||||
pub use flow_pages::{FlowPages, Page};
|
pub use flow_pages::{FlowPages, Page};
|
||||||
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
|
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
|
||||||
|
@ -2,8 +2,8 @@ use crate::{
|
|||||||
micropython::buffer::StrBuffer,
|
micropython::buffer::StrBuffer,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, Never, Paginate},
|
component::{Component, Event, EventCtx, Never, Paginate},
|
||||||
display::{text_multiline, Font},
|
display::{text_multiline_split_words, Font},
|
||||||
geometry::{Offset, Rect},
|
geometry::{Alignment, Offset, Rect},
|
||||||
model_tr::theme,
|
model_tr::theme,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -77,40 +77,42 @@ impl<const N: usize> ShareWords<N> {
|
|||||||
50,
|
50,
|
||||||
"Write all ",
|
"Write all ",
|
||||||
inttostr!(self.share_words.len() as u8),
|
inttostr!(self.share_words.len() as u8),
|
||||||
"\nwords in order on\nrecovery seed card."
|
" words in order on recovery seed card."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the first page with user information.
|
/// Display the first page with user information.
|
||||||
fn paint_entry_page(&mut self) {
|
fn paint_entry_page(&mut self) {
|
||||||
text_multiline(
|
text_multiline_split_words(
|
||||||
self.area.split_top(15).1,
|
self.area.split_top(15).1,
|
||||||
&self.get_first_text(),
|
&self.get_first_text(),
|
||||||
Font::BOLD,
|
Font::BOLD,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
|
Alignment::Start,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_second_text(&self) -> String<50> {
|
fn get_second_text(&self) -> String<50> {
|
||||||
build_string!(50, "Do NOT make\ndigital copies!")
|
build_string!(50, "Do NOT make digital copies!")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the second page with user information.
|
/// Display the second page with user information.
|
||||||
fn paint_second_page(&mut self) {
|
fn paint_second_page(&mut self) {
|
||||||
text_multiline(
|
text_multiline_split_words(
|
||||||
self.area.split_top(15).1,
|
self.area.split_top(15).1,
|
||||||
&self.get_second_text(),
|
&self.get_second_text(),
|
||||||
Font::MONO,
|
Font::MONO,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
|
Alignment::Start,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_final_text(&self) -> String<50> {
|
fn get_final_text(&self) -> String<50> {
|
||||||
build_string!(
|
build_string!(
|
||||||
50,
|
50,
|
||||||
"I wrote down all\n",
|
"I wrote down all ",
|
||||||
inttostr!(self.share_words.len() as u8),
|
inttostr!(self.share_words.len() as u8),
|
||||||
" words in order."
|
" words in order."
|
||||||
)
|
)
|
||||||
@ -118,12 +120,13 @@ impl<const N: usize> ShareWords<N> {
|
|||||||
|
|
||||||
/// Display the final page with user confirmation.
|
/// Display the final page with user confirmation.
|
||||||
fn paint_final_page(&mut self) {
|
fn paint_final_page(&mut self) {
|
||||||
text_multiline(
|
text_multiline_split_words(
|
||||||
self.area.split_top(12).1,
|
self.area.split_top(12).1,
|
||||||
&self.get_final_text(),
|
&self.get_final_text(),
|
||||||
Font::MONO,
|
Font::MONO,
|
||||||
theme::FG,
|
theme::FG,
|
||||||
theme::BG,
|
theme::BG,
|
||||||
|
Alignment::Start,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ use crate::{
|
|||||||
use super::{
|
use super::{
|
||||||
component::{
|
component::{
|
||||||
AddressDetails, AddressDetailsMsg, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage,
|
AddressDetails, AddressDetailsMsg, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage,
|
||||||
CancelInfoConfirmMsg, Flow, FlowMsg, FlowPages, Frame, Homescreen, HomescreenMsg,
|
CancelInfoConfirmMsg, CoinJoinProgress, Flow, FlowMsg, FlowPages, Frame, Homescreen,
|
||||||
Lockscreen, NoBtnDialog, NoBtnDialogMsg, NumberInput, NumberInputMsg, Page,
|
HomescreenMsg, Lockscreen, NoBtnDialog, NoBtnDialogMsg, NumberInput, NumberInputMsg, Page,
|
||||||
PassphraseEntry, PassphraseEntryMsg, PinEntry, PinEntryMsg, Progress, ShareWords, ShowMore,
|
PassphraseEntry, PassphraseEntryMsg, PinEntry, PinEntryMsg, Progress, ShareWords, ShowMore,
|
||||||
SimpleChoice, SimpleChoiceMsg, WelcomeScreen, WordlistEntry, WordlistEntryMsg,
|
SimpleChoice, SimpleChoiceMsg, WelcomeScreen, WordlistEntry, WordlistEntryMsg,
|
||||||
WordlistType,
|
WordlistType,
|
||||||
@ -132,6 +132,19 @@ impl ComponentMsgObj for PinEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clippy complains about conflicting implementations
|
||||||
|
#[cfg(not(feature = "clippy"))]
|
||||||
|
impl<T> ComponentMsgObj for (Timeout, T)
|
||||||
|
where
|
||||||
|
T: Component<Msg = TimeoutMsg>,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
TimeoutMsg::TimedOut => Ok(CANCELLED.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ComponentMsgObj for AddressDetails {
|
impl ComponentMsgObj for AddressDetails {
|
||||||
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 {
|
||||||
@ -140,6 +153,12 @@ impl ComponentMsgObj for AddressDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ComponentMsgObj for CoinJoinProgress {
|
||||||
|
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ComponentMsgObj for NumberInput {
|
impl ComponentMsgObj for NumberInput {
|
||||||
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 {
|
||||||
@ -900,9 +919,9 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
|
|||||||
let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?;
|
let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?;
|
||||||
|
|
||||||
let paragraphs = Paragraphs::new([
|
let paragraphs = Paragraphs::new([
|
||||||
Paragraph::new(&theme::TEXT_BOLD, "Maximum rounds:".into()),
|
Paragraph::new(&theme::TEXT_BOLD, "Max rounds".into()),
|
||||||
Paragraph::new(&theme::TEXT_MONO, max_rounds),
|
Paragraph::new(&theme::TEXT_MONO, max_rounds),
|
||||||
Paragraph::new(&theme::TEXT_BOLD, "Maximum mining fee:".into()).no_break(),
|
Paragraph::new(&theme::TEXT_BOLD, "Max mining fee".into()).no_break(),
|
||||||
Paragraph::new(&theme::TEXT_MONO, max_feerate),
|
Paragraph::new(&theme::TEXT_MONO, max_feerate),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -1138,6 +1157,29 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_show_progress_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
|
let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?;
|
||||||
|
let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?;
|
||||||
|
let skip_first_paint: bool = kwargs.get_or(Qstr::MP_QSTR_skip_first_paint, false)?;
|
||||||
|
|
||||||
|
// The second type parameter is actually not used in `new()` but we need to
|
||||||
|
// provide it.
|
||||||
|
let progress = CoinJoinProgress::new(title, indeterminate);
|
||||||
|
let obj = if time_ms > 0 && indeterminate {
|
||||||
|
let timeout = Timeout::new(time_ms);
|
||||||
|
LayoutObj::new((timeout, progress.map(|_msg| None)))?
|
||||||
|
} else {
|
||||||
|
LayoutObj::new(progress)?
|
||||||
|
};
|
||||||
|
if skip_first_paint {
|
||||||
|
obj.skip_first_paint();
|
||||||
|
}
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?;
|
let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?;
|
||||||
@ -1171,33 +1213,6 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn new_show_busyscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
|
||||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
|
||||||
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
|
||||||
let time_ms: u32 = kwargs.get(Qstr::MP_QSTR_time_ms)?.try_into()?;
|
|
||||||
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
|
|
||||||
|
|
||||||
let content = Paragraphs::new([
|
|
||||||
Paragraph::new(&theme::TEXT_BOLD, title),
|
|
||||||
Paragraph::new(&theme::TEXT_MONO, description),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let obj = LayoutObj::new(NoBtnDialog::new(
|
|
||||||
content,
|
|
||||||
Timeout::new(time_ms).map(|msg| {
|
|
||||||
(matches!(msg, TimeoutMsg::TimedOut)).then(|| CancelConfirmMsg::Confirmed)
|
|
||||||
}),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
if skip_first_paint {
|
|
||||||
obj.skip_first_paint();
|
|
||||||
}
|
|
||||||
Ok(obj.into())
|
|
||||||
};
|
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn draw_welcome_screen() -> Obj {
|
extern "C" fn draw_welcome_screen() -> Obj {
|
||||||
// No need of util::try_or_raise, this does not allocate
|
// No need of util::try_or_raise, this does not allocate
|
||||||
let mut screen = WelcomeScreen::new();
|
let mut screen = WelcomeScreen::new();
|
||||||
@ -1510,6 +1525,17 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// make sure the initial description has at least that amount of lines."""
|
/// make sure the initial description has at least that amount of lines."""
|
||||||
Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(),
|
Qstr::MP_QSTR_show_progress => obj_fn_kw!(0, new_show_progress).as_obj(),
|
||||||
|
|
||||||
|
/// def show_progress_coinjoin(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// indeterminate: bool = False,
|
||||||
|
/// time_ms: int = 0,
|
||||||
|
/// skip_first_paint: bool = False,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when
|
||||||
|
/// time_ms timeout is passed."""
|
||||||
|
Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(),
|
||||||
|
|
||||||
/// def show_homescreen(
|
/// def show_homescreen(
|
||||||
/// *,
|
/// *,
|
||||||
/// label: str,
|
/// label: str,
|
||||||
@ -1530,16 +1556,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """Homescreen for locked device."""
|
/// """Homescreen for locked device."""
|
||||||
Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(),
|
Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(),
|
||||||
|
|
||||||
/// def show_busyscreen(
|
|
||||||
/// *,
|
|
||||||
/// title: str,
|
|
||||||
/// description: str,
|
|
||||||
/// time_ms: int,
|
|
||||||
/// skip_first_paint: bool,
|
|
||||||
/// ) -> CANCELLED:
|
|
||||||
/// """Homescreen used for indicating coinjoin in progress."""
|
|
||||||
Qstr::MP_QSTR_show_busyscreen => obj_fn_kw!(0, new_show_busyscreen).as_obj(),
|
|
||||||
|
|
||||||
/// def draw_welcome_screen() -> None:
|
/// def draw_welcome_screen() -> None:
|
||||||
/// """Show logo icon with the model name at the bottom and return."""
|
/// """Show logo icon with the model name at the bottom and return."""
|
||||||
Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(),
|
Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(),
|
||||||
|
@ -114,10 +114,9 @@ class Busyscreen(HomescreenBase):
|
|||||||
def __init__(self, delay_ms: int) -> None:
|
def __init__(self, delay_ms: int) -> None:
|
||||||
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
|
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
|
||||||
super().__init__(
|
super().__init__(
|
||||||
# TODO: remove show_busyscreen in favor of show_progress_coinjoin
|
layout=trezorui2.show_progress_coinjoin(
|
||||||
layout=trezorui2.show_busyscreen(
|
title="Waiting for others",
|
||||||
title="PLEASE WAIT",
|
indeterminate=True,
|
||||||
description="Coinjoin in progress.\n\nDo not disconnect your Trezor.",
|
|
||||||
time_ms=delay_ms,
|
time_ms=delay_ms,
|
||||||
skip_first_paint=skip,
|
skip_first_paint=skip,
|
||||||
)
|
)
|
||||||
|
@ -99,8 +99,10 @@ def read_words(debug: "DebugLink", backup_type: messages.BackupType) -> list[str
|
|||||||
else:
|
else:
|
||||||
assert layout.text_content().startswith("RECOVERY SEED")
|
assert layout.text_content().startswith("RECOVERY SEED")
|
||||||
|
|
||||||
# Swiping through all the page and loading the words
|
# Swiping through all the pages and loading the words
|
||||||
for _ in range(layout.page_count() - 1):
|
for i in range(layout.page_count() - 1):
|
||||||
|
# In model R, first two pages are just informational
|
||||||
|
if not (debug.model == "R" and i < 2):
|
||||||
words.extend(layout.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)
|
||||||
assert layout is not None
|
assert layout is not None
|
||||||
|
@ -341,8 +341,10 @@ def read_and_confirm_mnemonic_tr(
|
|||||||
mnemonic: list[str] = []
|
mnemonic: list[str] = []
|
||||||
br = yield
|
br = yield
|
||||||
assert br.pages is not None
|
assert br.pages is not None
|
||||||
for _ in range(br.pages - 1):
|
for i in range(br.pages - 1):
|
||||||
layout = debug.wait_layout()
|
layout = debug.wait_layout()
|
||||||
|
# First two pages have just instructions
|
||||||
|
if i > 1:
|
||||||
words = layout.seed_words()
|
words = layout.seed_words()
|
||||||
mnemonic.extend(words)
|
mnemonic.extend(words)
|
||||||
debug.press_right()
|
debug.press_right()
|
||||||
|
Loading…
Reference in New Issue
Block a user