1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-01 12:22:34 +00:00

WIP - progress, homescreen, Popup, draw_simple, device tests

This commit is contained in:
grdddj 2022-12-31 13:10:36 +01:00
parent cf3687263f
commit c5ad2937c1
30 changed files with 1119 additions and 286 deletions

View File

@ -31,7 +31,6 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_output_r;
MP_QSTR_confirm_payment_request;
MP_QSTR_confirm_reset_device;
MP_QSTR_confirm_recovery;
MP_QSTR_confirm_text;
MP_QSTR_confirm_total;
MP_QSTR_confirm_total_r;
@ -39,16 +38,10 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_with_info;
MP_QSTR_confirm_more;
MP_QSTR_confirm_recovery;
MP_QSTR_show_checklist;
MP_QSTR_show_error;
MP_QSTR_show_qr;
MP_QSTR_show_success;
MP_QSTR_show_warning;
MP_QSTR_show_info;
MP_QSTR_show_simple;
MP_QSTR_request_number;
MP_QSTR_request_pin;
MP_QSTR_request_passphrase;
MP_QSTR_confirm_word;
MP_QSTR_request_bip39;
MP_QSTR_request_number;
@ -58,31 +51,19 @@ static void _librust_qstrs(void) {
MP_QSTR_select_word;
MP_QSTR_select_word_count;
MP_QSTR_show_busyscreen;
MP_QSTR_show_group_share_success;
MP_QSTR_show_homescreen;
MP_QSTR_show_lockscreen;
MP_QSTR_share_words;
MP_QSTR_show_checklist;
MP_QSTR_show_error;
MP_QSTR_show_group_share_success;
MP_QSTR_show_info;
MP_QSTR_show_qr;
MP_QSTR_show_remaining_shares;
MP_QSTR_show_success;
MP_QSTR_show_simple;
MP_QSTR_show_warning;
MP_QSTR_show_share_words;
MP_QSTR_show_progress;
MP_QSTR_attach_timer_fn;
MP_QSTR_touch_event;
MP_QSTR_button_event;
MP_QSTR_progress_event;
MP_QSTR_usb_event;
MP_QSTR_timer;
MP_QSTR_paint;
MP_QSTR_request_complete_repaint;
MP_QSTR_trace;
MP_QSTR_request_word_count;
MP_QSTR_request_word_bip39;
MP_QSTR_tutorial;

View File

@ -1,94 +0,0 @@
use super::button::{Button, ButtonMsg::Clicked};
use crate::ui::{
component::{Child, Component, Event, EventCtx},
geometry::Rect,
model_tr::theme,
};
pub enum DialogMsg<T> {
Content(T),
LeftClicked,
RightClicked,
}
pub struct Dialog<T, U> {
content: Child<T>,
left_btn: Option<Child<Button<U>>>,
right_btn: Option<Child<Button<U>>>,
}
impl<T, U> Dialog<T, U>
where
T: Component,
U: AsRef<str>,
{
pub fn new(content: T, left: Option<Button<U>>, right: Option<Button<U>>) -> Self {
Self {
content: Child::new(content),
left_btn: left.map(Child::new),
right_btn: right.map(Child::new),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T, U> Component for Dialog<T, U>
where
T: Component,
U: AsRef<str>,
{
type Msg = DialogMsg<T::Msg>;
fn place(&mut self, bounds: Rect) -> Rect {
let button_height = theme::FONT_BUTTON.line_height() + 2;
let (content_area, button_area) = bounds.split_bottom(button_height);
self.content.place(content_area);
self.left_btn.as_mut().map(|b| b.place(button_area));
self.right_btn.as_mut().map(|b| b.place(button_area));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(msg) = self.content.event(ctx, event) {
Some(DialogMsg::Content(msg))
} else if let Some(Clicked) = self.left_btn.as_mut().and_then(|b| b.event(ctx, event)) {
Some(DialogMsg::LeftClicked)
} else if let Some(Clicked) = self.right_btn.as_mut().and_then(|b| b.event(ctx, event)) {
Some(DialogMsg::RightClicked)
} else {
None
}
}
fn paint(&mut self) {
self.content.paint();
if let Some(b) = self.left_btn.as_mut() {
b.paint();
}
if let Some(b) = self.right_btn.as_mut() {
b.paint();
}
}
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Dialog<T, U>
where
T: crate::trace::Trace,
U: crate::trace::Trace + AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Dialog");
t.field("content", &self.content);
if let Some(label) = &self.left_btn {
t.field("left", label);
}
if let Some(label) = &self.right_btn {
t.field("right", label);
}
t.close();
}
}

View File

@ -0,0 +1,156 @@
use crate::ui::{
component::{Component, Event, EventCtx, Pad},
display::Font,
event::{ButtonEvent, USBEvent},
geometry::{Offset, Point, Rect},
model_tr::constant,
};
use super::{common::display_center, theme};
const AREA: Rect = constant::screen();
const TOP_CENTER: Point = AREA.top_center();
const LABEL_Y: i16 = 62;
const LOCKED_Y: i16 = 32;
const TAP_Y: i16 = 47;
pub struct Homescreen<T> {
label: T,
notification: Option<(T, u8)>,
usb_connected: bool,
pad: Pad,
}
pub enum HomescreenMsg {
Dismissed,
}
impl<T> Homescreen<T>
where
T: AsRef<str>,
{
pub fn new(label: T, notification: Option<(T, u8)>) -> Self {
Self {
label,
notification,
usb_connected: true,
pad: Pad::with_background(theme::BG),
}
}
fn paint_notification(&self) {
let baseline = TOP_CENTER + Offset::y(Font::MONO.line_height());
if !self.usb_connected {
display_center(baseline, &"NO USB CONNECTION", Font::MONO);
} else if let Some((notification, _level)) = &self.notification {
display_center(baseline, &notification.as_ref(), Font::MONO);
}
}
fn event_usb(&mut self, ctx: &mut EventCtx, event: Event) {
if let Event::USB(USBEvent::Connected(is_connected)) = event {
if self.usb_connected != is_connected {
self.usb_connected = is_connected;
ctx.request_paint();
}
}
}
}
impl<T> Component for Homescreen<T>
where
T: AsRef<str>,
{
type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.pad.place(AREA);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
Self::event_usb(self, ctx, event);
None
}
fn paint(&mut self) {
self.pad.paint();
self.paint_notification();
display_center(
TOP_CENTER + Offset::y(LABEL_Y),
&self.label.as_ref(),
Font::BOLD,
);
}
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.pad.area);
}
}
#[cfg(feature = "ui_debug")]
impl<T: AsRef<str>> crate::trace::Trace for Homescreen<T> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Homescreen");
d.kw_pair("active_page", "0");
d.kw_pair("page_count", "1");
d.field("label", &self.label.as_ref());
d.close();
}
}
pub struct Lockscreen<T> {
label: T,
bootscreen: bool,
}
impl<T> Lockscreen<T> {
pub fn new(label: T, bootscreen: bool) -> Self {
Lockscreen { label, bootscreen }
}
}
impl<T> Component for Lockscreen<T>
where
T: AsRef<str>,
{
type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Button(ButtonEvent::ButtonReleased(_)) = event {
return Some(HomescreenMsg::Dismissed);
}
None
}
fn paint(&mut self) {
let (locked, tap) = if self.bootscreen {
("NOT CONNECTED", "Click to connect")
} else {
("LOCKED", "Click to unlock")
};
display_center(TOP_CENTER + Offset::y(LOCKED_Y), &locked, Font::MONO);
display_center(TOP_CENTER + Offset::y(TAP_Y), &tap, Font::MONO);
display_center(
TOP_CENTER + Offset::y(LABEL_Y),
&self.label.as_ref(),
Font::BOLD,
);
}
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Lockscreen<T>
where
T: AsRef<str>,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Lockscreen");
d.field("label", &self.label.as_ref());
d.close();
}
}

View File

@ -6,15 +6,17 @@ mod choice;
mod choice_item;
mod common;
mod confirm;
mod dialog;
mod flow;
mod flow_pages;
mod flow_pages_poc_helpers;
mod frame;
mod homescreen;
mod loader;
mod no_btn_dialog;
mod page;
mod passphrase;
mod pin;
mod progress;
mod qr_code;
mod result_anim;
mod result_popup;
@ -35,15 +37,17 @@ pub use button_controller::{ButtonController, ButtonControllerMsg};
pub use changing_text::ChangingTextLine;
pub use choice::{Choice, ChoiceFactory, ChoicePage, ChoicePageMsg};
pub use choice_item::ChoiceItem;
pub use dialog::{Dialog, DialogMsg};
pub use flow::{Flow, FlowMsg};
pub use flow_pages::{FlowPages, Page};
pub use flow_pages_poc_helpers::LineAlignment;
pub use frame::Frame;
pub use homescreen::{Homescreen, HomescreenMsg, Lockscreen};
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
pub use no_btn_dialog::{NoBtnDialog, NoBtnDialogMsg};
pub use page::ButtonPage;
pub use passphrase::{PassphraseEntry, PassphraseEntryMsg};
pub use pin::{PinEntry, PinEntryMsg};
pub use progress::Progress;
pub use qr_code::{QRCodePage, QRCodePageMessage};
pub use result_anim::{ResultAnim, ResultAnimMsg};
pub use result_popup::{ResultPopup, ResultPopupMsg};

View File

@ -0,0 +1,69 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx},
geometry::Rect,
};
pub enum NoBtnDialogMsg<T> {
Controls(T),
}
/// Used for simple displaying of information without user interaction.
/// Suitable for just showing a message, or having a timeout after which
/// the dialog is dismissed.
pub struct NoBtnDialog<T, U> {
content: Child<T>,
controls: Child<U>,
}
impl<T, U> NoBtnDialog<T, U>
where
T: Component,
U: Component,
{
pub fn new(content: T, controls: U) -> Self {
Self {
content: Child::new(content),
controls: Child::new(controls),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T, U> Component for NoBtnDialog<T, U>
where
T: Component,
U: Component,
{
type Msg = NoBtnDialogMsg<U::Msg>;
fn place(&mut self, bounds: Rect) -> Rect {
self.controls.place(bounds);
self.content.place(bounds);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.controls.event(ctx, event).map(Self::Msg::Controls)
}
fn paint(&mut self) {
self.content.paint();
self.controls.paint();
}
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for NoBtnDialog<T, U>
where
T: crate::trace::Trace,
U: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("NoBtnDialog");
self.content.trace(t);
t.close();
}
}

View File

@ -0,0 +1,137 @@
use core::mem;
use crate::{
error::Error,
ui::{
component::{
base::ComponentExt,
paginated::Paginate,
text::paragraphs::{Paragraph, ParagraphStrType, Paragraphs},
Child, Component, Event, EventCtx, Label, Never, Pad,
},
display::{self, Font},
geometry::Rect,
model_tr::constant,
util::animation_disabled,
},
};
use super::theme;
pub struct Progress<T> {
title: Child<Label<T>>,
value: u16,
loader_y_offset: i16,
indeterminate: bool,
description: Child<Paragraphs<Paragraph<T>>>,
description_pad: Pad,
update_description: fn(&str) -> Result<T, Error>,
}
impl<T> Progress<T>
where
T: ParagraphStrType,
{
const AREA: Rect = constant::screen();
pub fn new(
title: T,
indeterminate: bool,
description: T,
update_description: fn(&str) -> Result<T, Error>,
) -> Self {
Self {
title: Label::centered(title, theme::TEXT_HEADER).into_child(),
value: 0,
loader_y_offset: 0,
indeterminate,
description: Paragraphs::new(
Paragraph::new(&theme::TEXT_NORMAL, description).centered(),
)
.into_child(),
description_pad: Pad::with_background(theme::BG),
update_description,
}
}
}
impl<T> Component for Progress<T>
where
T: ParagraphStrType,
{
type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect {
let description_lines = 1 + self
.description
.inner()
.inner()
.content()
.as_ref()
.chars()
.filter(|c| *c == '\n')
.count() as i16;
let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y);
let (loader, description) =
rest.split_bottom(Font::NORMAL.line_height() * description_lines);
self.title.place(title);
self.loader_y_offset = loader.center().y - constant::screen().center().y;
self.description.place(description);
self.description_pad.place(description);
Self::AREA
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Progress(new_value, new_description) = event {
if mem::replace(&mut self.value, new_value) != new_value {
if !animation_disabled() {
ctx.request_paint();
}
self.description.mutate(ctx, |ctx, para| {
if para.inner_mut().content().as_ref() != new_description {
let new_description = unwrap!((self.update_description)(new_description));
para.inner_mut().update(new_description);
para.change_page(0); // Recompute bounding box.
ctx.request_paint();
self.description_pad.clear();
}
});
}
}
None
}
fn paint(&mut self) {
self.title.paint();
if self.indeterminate {
display::loader_indeterminate(
self.value,
self.loader_y_offset,
theme::FG,
theme::BG,
None,
);
} else {
display::loader(self.value, self.loader_y_offset, theme::FG, theme::BG, None);
}
self.description_pad.paint();
self.description.paint();
}
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(Self::AREA);
self.title.bounds(sink);
self.description.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Progress<T>
where
T: ParagraphStrType,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Progress");
t.close();
}
}

View File

@ -19,8 +19,10 @@ use crate::{
base::Component,
paginated::{PageMsg, Paginate},
painter,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecLong, Paragraphs, VecExt},
FormattedText,
text::paragraphs::{
Paragraph, ParagraphSource, ParagraphStrType, ParagraphVecLong, Paragraphs, VecExt,
},
ComponentExt, Empty, FormattedText, Timeout, TimeoutMsg,
},
display::Font,
layout::{
@ -35,12 +37,42 @@ use crate::{
use super::{
component::{
Bip39Entry, Bip39EntryMsg, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, Flow,
FlowMsg, FlowPages, Frame, Page, PassphraseEntry, PassphraseEntryMsg, PinEntry,
PinEntryMsg, QRCodePage, QRCodePageMessage, ShareWords, SimpleChoice, SimpleChoiceMsg,
FlowMsg, FlowPages, Frame, Homescreen, HomescreenMsg, Lockscreen, NoBtnDialog,
NoBtnDialogMsg, Page, PassphraseEntry, PassphraseEntryMsg, PinEntry, PinEntryMsg, Progress,
QRCodePage, QRCodePageMessage, ShareWords, SimpleChoice, SimpleChoiceMsg,
},
theme,
};
pub enum CancelConfirmMsg {
Cancelled,
Confirmed,
}
impl TryFrom<CancelConfirmMsg> for Obj {
type Error = Error;
fn try_from(value: CancelConfirmMsg) -> Result<Self, Self::Error> {
match value {
CancelConfirmMsg::Cancelled => Ok(CANCELLED.as_obj()),
CancelConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()),
}
}
}
impl<T, U> ComponentMsgObj for NoBtnDialog<T, U>
where
T: Component,
U: Component,
<U as Component>::Msg: TryInto<Obj, Error = Error>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
NoBtnDialogMsg::Controls(msg) => msg.try_into(),
}
}
}
impl<S, T> ComponentMsgObj for ButtonPage<S, T>
where
T: Component + Paginate,
@ -133,6 +165,37 @@ where
}
}
impl<T> ComponentMsgObj for Progress<T>
where
T: ParagraphStrType,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!()
}
}
impl<T> ComponentMsgObj for Homescreen<T>
where
T: AsRef<str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
HomescreenMsg::Dismissed => Ok(CANCELLED.as_obj()),
}
}
}
impl<T> ComponentMsgObj for Lockscreen<T>
where
T: AsRef<str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
HomescreenMsg::Dismissed => Ok(CANCELLED.as_obj()),
}
}
}
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -202,7 +265,8 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
}
extern "C" fn new_confirm_text(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
// TODO: should be deleted and replaced by new_confirm_action with some default parameters
// TODO: should be deleted and replaced by confirm_action with some default
// parameters
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -231,7 +295,7 @@ extern "C" fn new_confirm_text(n_args: usize, args: *const Obj, kwargs: *mut Map
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn confirm_properties(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_confirm_properties(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 hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
@ -269,7 +333,7 @@ extern "C" fn confirm_properties(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
// Getting this from micropython so it is also a `StrBuffer`, not having
@ -325,7 +389,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let total_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_amount)?.try_into()?;
@ -369,7 +433,7 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_show_qr(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 address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
@ -392,6 +456,36 @@ extern "C" fn show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_info(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_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?;
let content = Paragraphs::new([
Paragraph::new(&theme::TEXT_MONO, title),
Paragraph::new(&theme::TEXT_MONO, description),
]);
let obj = if time_ms == 0 {
// No timer, used when we only want to draw the dialog once and
// then throw away the layout object.
LayoutObj::new(NoBtnDialog::new(content, Empty))?
} else {
// Timeout.
LayoutObj::new(NoBtnDialog::new(
content,
Timeout::new(time_ms).map(|msg| {
(matches!(msg, TimeoutMsg::TimedOut)).then(|| CancelConfirmMsg::Confirmed)
}),
))?
};
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
// TODO: not supplying tuple of data, supply data itself without unpacking
/// General pattern of most tutorial screens.
/// (title, text, btn_layout, btn_actions)
@ -507,7 +601,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
@ -520,7 +614,7 @@ extern "C" fn request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
let share_words: Vec<StrBuffer, 24> = iter_into_vec(share_words_obj)?;
@ -536,7 +630,7 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
@ -551,7 +645,7 @@ extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn request_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_request_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
@ -563,7 +657,7 @@ extern "C" fn request_word_count(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
@ -573,7 +667,7 @@ extern "C" fn request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let _max_len: u8 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
@ -584,6 +678,87 @@ extern "C" fn request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut M
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_progress(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 description: StrBuffer =
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
// Description updates are received as &str and we need to provide a way to
// convert them to StrBuffer.
let obj = LayoutObj::new(Progress::new(
title,
indeterminate,
description,
StrBuffer::alloc,
))?;
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?;
let notification: Option<StrBuffer> =
kwargs.get(Qstr::MP_QSTR_notification)?.try_into_option()?;
let notification_level: u8 = kwargs.get_or(Qstr::MP_QSTR_notification_level, 0)?;
let _hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?;
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
let notification = notification.map(|w| (w, notification_level));
let obj = LayoutObj::new(Homescreen::new(label, notification))?;
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_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let label: StrBuffer = kwargs.get(Qstr::MP_QSTR_label)?.try_into()?;
let bootscreen: bool = kwargs.get(Qstr::MP_QSTR_bootscreen)?.try_into()?;
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
let obj = LayoutObj::new(Lockscreen::new(label, bootscreen))?;
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_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 obj = LayoutObj::new(Frame::left_aligned(
// theme::label_title(),
// title,
// Dialog::new(
// Paragraphs::new(Paragraph::new(&theme::TEXT_NORMAL,
// description).centered()), Timeout::new(time_ms).map(|msg| {
// (matches!(msg, TimeoutMsg::TimedOut)).then(||
// CancelConfirmMsg::Cancelled) }),
// ),
// ))?;
// if skip_first_paint {
// obj.skip_first_paint();
// }
// Ok(obj.into())
// };
// unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
// }
#[no_mangle]
pub static mp_module_trezorui2: Module = obj_module! {
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(),
@ -624,7 +799,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Confirm list of key-value pairs. The third component in the tuple should be True if
/// the value is to be rendered as binary with monospace font, False otherwise.
/// This only concerns the text style, you need to decode the value to UTF-8 in python."""
Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, confirm_properties).as_obj(),
Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(),
/// def confirm_output_r(
/// *,
@ -633,7 +808,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// amount: str,
/// ) -> object:
/// """Confirm output. Specific for model R."""
Qstr::MP_QSTR_confirm_output_r => obj_fn_kw!(0, confirm_output).as_obj(),
Qstr::MP_QSTR_confirm_output_r => obj_fn_kw!(0, new_confirm_output).as_obj(),
/// def confirm_total_r(
/// *,
@ -645,7 +820,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// fee_label: str,
/// ) -> object:
/// """Confirm summary of a transaction. Specific for model R."""
Qstr::MP_QSTR_confirm_total_r => obj_fn_kw!(0, confirm_total).as_obj(),
Qstr::MP_QSTR_confirm_total_r => obj_fn_kw!(0, new_confirm_total).as_obj(),
/// def show_qr(
/// *,
@ -655,7 +830,16 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// case_sensitive: bool,
/// ) -> object:
/// """Show QR code."""
Qstr::MP_QSTR_show_qr => obj_fn_kw!(0, show_qr).as_obj(),
Qstr::MP_QSTR_show_qr => obj_fn_kw!(0, new_show_qr).as_obj(),
/// def show_info(
/// *,
/// title: str,
/// description: str = "",
/// time_ms: int = 0,
/// ) -> object:
/// """Info modal."""
Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(),
/// def tutorial() -> object:
/// """Show user how to interact with the device."""
@ -668,7 +852,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// allow_cancel: bool | None = None,
/// ) -> str | object:
/// """Request pin on device."""
Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, request_pin).as_obj(),
Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(),
/// def confirm_text(
/// *,
@ -684,7 +868,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// share_words: Iterable[str],
/// ) -> None:
/// """Shows a backup seed."""
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, show_share_words).as_obj(),
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
/// def select_word(
/// *,
@ -692,21 +876,21 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// words: Iterable[str],
/// ) -> str:
/// """Select a word from a list. TODO: should return int, to be consistent with TT's select_word"""
Qstr::MP_QSTR_select_word => obj_fn_kw!(0, select_word).as_obj(),
Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(),
/// def request_word_count(
/// *,
/// title: str,
/// ) -> str: # TODO: make it return int
/// """Get word count for recovery."""
Qstr::MP_QSTR_request_word_count => obj_fn_kw!(0, request_word_count).as_obj(),
Qstr::MP_QSTR_request_word_count => obj_fn_kw!(0, new_request_word_count).as_obj(),
/// def request_word_bip39(
/// *,
/// prompt: str,
/// ) -> str:
/// """Get recovery word for BIP39."""
Qstr::MP_QSTR_request_word_bip39 => obj_fn_kw!(0, request_word_bip39).as_obj(),
Qstr::MP_QSTR_request_word_bip39 => obj_fn_kw!(0, new_request_word_bip39).as_obj(),
/// def request_passphrase(
/// *,
@ -714,7 +898,48 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// max_len: int,
/// ) -> str:
/// """Get passphrase."""
Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, request_passphrase).as_obj(),
Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(),
/// def show_progress(
/// *,
/// title: str,
/// indeterminate: bool = False,
/// description: str | None = None,
/// ) -> object:
/// """Show progress loader. Please note that the number of lines reserved on screen for
/// description is determined at construction time. If you want multiline descriptions
/// 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(),
/// def show_homescreen(
/// *,
/// label: str,
/// hold: bool,
/// notification: str | None,
/// notification_level: int = 0,
/// skip_first_paint: bool,
/// ) -> CANCELLED:
/// """Idle homescreen."""
Qstr::MP_QSTR_show_homescreen => obj_fn_kw!(0, new_show_homescreen).as_obj(),
/// def show_lockscreen(
/// *,
/// label: str,
/// bootscreen: bool,
/// skip_first_paint: bool,
/// ) -> CANCELLED:
/// """Homescreen for locked device."""
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(),
};
#[cfg(test)]

View File

@ -21,6 +21,8 @@ pub const TEXT_BOLD: TextStyle =
TextStyle::new(Font::BOLD, FG, BG, FG, FG).with_ellipsis_icon(ICON_NEXT_PAGE.0);
pub const TEXT_MONO: TextStyle =
TextStyle::new(Font::MONO, FG, BG, FG, FG).with_ellipsis_icon(ICON_NEXT_PAGE.0);
// Header does not have the ellipsis
pub const TEXT_HEADER: TextStyle = TextStyle::new(Font::BOLD, FG, BG, FG, FG);
pub const FORMATTED: FormattedFonts = FormattedFonts {
normal: Font::NORMAL,

View File

@ -1598,7 +1598,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// ) -> object:
/// """Show progress loader. Please note that the number of lines reserved on screen for
/// description is determined at construction time. If you want multiline descriptions
/// make sure the initial desciption 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(),
/// def show_homescreen(

View File

@ -101,6 +101,16 @@ def show_qr(
"""Show QR code."""
# rust/src/ui/model_tr/layout.rs
def show_info(
*,
title: str,
description: str = "",
time_ms: int = 0,
) -> object:
"""Info modal."""
# rust/src/ui/model_tr/layout.rs
def tutorial() -> object:
"""Show user how to interact with the device."""
@ -166,6 +176,40 @@ def request_passphrase(
max_len: int,
) -> str:
"""Get passphrase."""
# rust/src/ui/model_tr/layout.rs
def show_progress(
*,
title: str,
indeterminate: bool = False,
description: str | None = None,
) -> object:
"""Show progress loader. Please note that the number of lines reserved on screen for
description is determined at construction time. If you want multiline descriptions
make sure the initial description has at least that amount of lines."""
# rust/src/ui/model_tr/layout.rs
def show_homescreen(
*,
label: str,
hold: bool,
notification: str | None,
notification_level: int = 0,
skip_first_paint: bool,
) -> CANCELLED:
"""Idle homescreen."""
# rust/src/ui/model_tr/layout.rs
def show_lockscreen(
*,
label: str,
bootscreen: bool,
skip_first_paint: bool,
) -> CANCELLED:
"""Homescreen for locked device."""
CONFIRMED: object
CANCELLED: object
INFO: object
@ -505,7 +549,7 @@ def show_progress(
) -> object:
"""Show progress loader. Please note that the number of lines reserved on screen for
description is determined at construction time. If you want multiline descriptions
make sure the initial desciption has at least that amount of lines."""
make sure the initial description has at least that amount of lines."""
# rust/src/ui/model_tt/layout.rs

View File

@ -167,6 +167,8 @@ trezor.ui.layouts.tr.altcoin
import trezor.ui.layouts.tr.altcoin
trezor.ui.layouts.tr.fido
import trezor.ui.layouts.tr.fido
trezor.ui.layouts.tr.homescreen
import trezor.ui.layouts.tr.homescreen
trezor.ui.layouts.tr.recovery
import trezor.ui.layouts.tr.recovery
trezor.ui.layouts.tr.reset

View File

@ -108,7 +108,9 @@ async def _request_wipe_code_confirm(ctx: Context, pin: str) -> str:
# _wipe_code_invalid
await show_popup(
"Invalid wipe code",
"The wipe code must be\ndifferent from your PIN.\n\nPlease try again.",
text_r(
"The wipe code must be\ndifferent from your PIN.\n\nPlease try again."
),
)
continue
@ -118,7 +120,5 @@ async def _request_wipe_code_confirm(ctx: Context, pin: str) -> str:
# _wipe_code_mismatch
await show_popup(
"Code mismatch",
text_r(
"The wipe code must be\ndifferent from your PIN.\n\nPlease try again."
),
text_r("The wipe codes you\nentered do not match.\n\nPlease try again."),
)

View File

@ -65,7 +65,7 @@ async def _continue_recovery_process(ctx: GenericContext) -> Success:
if is_first_step:
# If we are starting recovery, ask for word count first...
# _request_word_count
await layout.homescreen_dialog(ctx, "Select", "Select the number of words")
await layout.homescreen_dialog(ctx, "Select", "Select number of words")
# ask for the number of words
word_count = await layout.request_word_count(ctx, dry_run)
# ...and only then show the starting screen with word count.

View File

@ -1 +1,6 @@
from trezor import utils
if utils.MODEL in ("T",):
from .tt_v2.homescreen import * # noqa: F401,F403
elif utils.MODEL in ("R",):
from .tr.homescreen import * # noqa: F401,F403

View File

@ -1,23 +1,22 @@
from typing import TYPE_CHECKING, Sequence
from trezor import io, log, loop, ui, wire, workflow
from trezor import io, log, loop, ui, workflow
from trezor.enums import ButtonRequestType
from trezor.utils import DISABLE_ANIMATION
from trezor.wire import ActionCancelled
import trezorui2
from ..common import button_request, interact
if TYPE_CHECKING:
from typing import Any, NoReturn, Type, Awaitable, Iterable, TypeVar
from typing import Any, NoReturn, Awaitable, Iterable, TypeVar
from trezor.wire import GenericContext, Context
from ..common import PropertyType
from ..common import PropertyType, ExceptionType, ProgressLayout
T = TypeVar("T")
ExceptionType = BaseException | Type[BaseException]
BR_TYPE_OTHER = ButtonRequestType.Other # global_import_cache
@ -56,11 +55,11 @@ class RustLayoutContent:
def active_page(self) -> int:
"""Current index of the active page. Should always be there."""
return self.kw_pair_int_compulsory("active_page")
return self.kw_pair_int("active_page") or 0
def page_count(self) -> int:
"""Overall number of pages in this screen. Should always be there."""
return self.kw_pair_int_compulsory("page_count")
return self.kw_pair_int("page_count") or 1
def in_flow(self) -> bool:
"""Whether we are in flow."""
@ -136,22 +135,20 @@ class RustLayoutContent:
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_compulsory(self, key: str) -> int:
"""Getting integer that cannot be missing."""
val = self.kw_pair_int(key)
assert val is not None
return val
def kw_pair_int(self, key: str) -> int | None:
"""Getting the value of a key-value pair as an integer. None if missing."""
val = self.kw_pair(key)
@ -196,6 +193,35 @@ class RustLayout(ui.Layout):
def set_timer(self, token: int, deadline: int) -> None:
self.timer.schedule(deadline, token)
def request_complete_repaint(self) -> None:
msg = self.layout.request_complete_repaint()
assert msg is None
def _paint(self) -> None:
import storage.cache as storage_cache
painted = self.layout.paint()
if storage_cache.homescreen_shown is not None and painted:
storage_cache.homescreen_shown = None
def _first_paint(self) -> None:
# Clear the screen of any leftovers.
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.display.clear()
self._paint()
if __debug__ and self.should_notify_layout_change:
from apps.debug import notify_layout_change
# notify about change and do not notify again until next await.
# (handle_rendering might be called multiple times in a single await,
# because of the endless loop in __iter__)
self.should_notify_layout_change = False
notify_layout_change(self)
# Turn the brightness on again.
ui.backlight_fade(self.BACKLIGHT_LEVEL)
if __debug__:
from trezor.enums import DebugPhysicalButton
@ -394,6 +420,19 @@ class RustLayout(ui.Layout):
self.layout.paint()
def draw_simple(layout: Any) -> None:
# Simple drawing not supported for layouts that set timers.
def dummy_set_timer(token: int, deadline: int) -> None:
raise RuntimeError
layout.attach_timer_fn(dummy_set_timer)
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.display.clear()
layout.paint()
ui.refresh()
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
# Temporary function, so we know where it is used
# Should be gradually replaced by custom designs/layouts
async def _placeholder_confirm(
@ -448,7 +487,7 @@ async def get_bool(
return result is trezorui2.CONFIRMED
async def raise_if_cancelled(a: Awaitable[T], exc: Any = wire.ActionCancelled) -> T:
async def raise_if_cancelled(a: Awaitable[T], exc: Any = ActionCancelled) -> T:
result = await a
if result is trezorui2.CANCELLED:
raise exc
@ -469,8 +508,9 @@ async def confirm_action(
verb: str = "CONFIRM",
verb_cancel: str | None = None,
hold: bool = False,
hold_danger: bool = False,
reverse: bool = False,
exc: ExceptionType = wire.ActionCancelled,
exc: ExceptionType = ActionCancelled,
br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None:
if verb_cancel is not None:
@ -584,7 +624,7 @@ async def confirm_path_warning(
)
def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout:
def _show_xpub(xpub: str, title: str, cancel: str | None) -> ui.Layout:
content = RustLayout(
trezorui2.confirm_text(
title=title.upper(),
@ -596,11 +636,11 @@ def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout:
return content
async def show_xpub(ctx: GenericContext, xpub: str, title: str, cancel: str) -> None:
async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None:
await raise_if_cancelled(
interact(
ctx,
_show_xpub(xpub, title, cancel),
_show_xpub(xpub, title, None),
"show_xpub",
ButtonRequestType.PublicKey,
)
@ -699,7 +739,7 @@ async def _show_modal(
content: str,
button_confirm: str | None,
button_cancel: str | None,
exc: ExceptionType = wire.ActionCancelled,
exc: ExceptionType = ActionCancelled,
) -> None:
await confirm_action(
ctx=ctx,
@ -722,7 +762,7 @@ async def show_error_and_raise(
subheader: str | None = None,
button: str = "Close",
red: bool = False,
exc: ExceptionType = wire.ActionCancelled,
exc: ExceptionType = ActionCancelled,
) -> NoReturn:
await _show_modal(
ctx=ctx,
@ -949,7 +989,13 @@ async def confirm_properties(
hold: bool = False,
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
) -> None:
items = [(prop[0], prop[1], isinstance(prop[1], bytes)) for prop in props]
from ubinascii import hexlify
def handle_bytes(prop: PropertyType):
if isinstance(prop[1], bytes):
return (prop[0], hexlify(prop[1]).decode(), True)
else:
return (prop[0], prop[1], False)
await raise_if_cancelled(
interact(
@ -957,7 +1003,7 @@ async def confirm_properties(
RustLayout(
trezorui2.confirm_properties(
title=title.upper(),
items=items,
items=map(handle_bytes, props), # type: ignore [cannot be assigned to parameter "items"]
hold=hold,
)
),
@ -967,6 +1013,32 @@ async def confirm_properties(
)
def confirm_value(
ctx: GenericContext,
title: str,
value: str,
description: str,
br_type: str,
br_code: ButtonRequestType = BR_TYPE_OTHER,
*,
verb: str | None = None,
hold: bool = False,
) -> Awaitable[None]:
"""General confirmation dialog, used by many other confirm_* functions."""
if not verb and not hold:
raise ValueError("Either verb or hold=True must be set")
return _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=title.upper(),
data=value,
description=description,
br_code=br_code,
)
async def confirm_total(
ctx: GenericContext,
total_amount: str,
@ -975,7 +1047,6 @@ async def confirm_total(
title: str = "Send transaction?",
total_label: str = "Total amount:",
fee_label: str = "Including fee:",
icon_color: int = ui.GREEN,
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
@ -1018,19 +1089,14 @@ async def confirm_metadata(
content: str,
param: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.SignTx,
hide_continue: bool = False,
hold: bool = False,
param_font: int = ui.BOLD,
) -> None:
text = content.format(param)
if not hide_continue:
text += "\n\nContinue?"
# TODO: implement `hold`
await _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=title.upper(),
data=text,
data=content.format(param),
description="",
br_code=br_code,
)
@ -1173,7 +1239,23 @@ async def show_popup(
description_param: str = "",
timeout_ms: int = 3000,
) -> None:
raise NotImplementedError
if subtitle:
title += f"\n{subtitle}"
await RustLayout(
trezorui2.show_info(
title=title,
description=description.format(description_param),
time_ms=timeout_ms,
)
)
def request_passphrase_on_host() -> None:
draw_simple(
trezorui2.show_info(
title="Please type your passphrase on the connected host.",
)
)
async def request_passphrase_on_device(ctx: GenericContext, max_len: int) -> str:
@ -1190,7 +1272,7 @@ async def request_passphrase_on_device(ctx: GenericContext, max_len: int) -> str
)
)
if result is trezorui2.CANCELLED:
raise wire.ActionCancelled("Passphrase entry cancelled")
raise ActionCancelled("Passphrase entry cancelled")
assert isinstance(result, str)
return result
@ -1202,6 +1284,8 @@ async def request_pin_on_device(
attempts_remaining: int | None,
allow_cancel: bool,
) -> str:
from trezor import wire
if attempts_remaining is None:
subprompt = ""
elif attempts_remaining == 1:
@ -1311,3 +1395,55 @@ async def confirm_set_new_pin(
hold=True,
br_code=br_code,
)
class RustProgress:
def __init__(
self,
title: str,
description: str | None = None,
indeterminate: bool = False,
):
self.layout: Any = trezorui2.show_progress(
title=title.upper(),
indeterminate=indeterminate,
description=description or "",
)
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
ui.display.clear()
self.layout.attach_timer_fn(self.set_timer)
self.layout.paint()
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
def set_timer(self, token: int, deadline: int) -> None:
raise RuntimeError # progress layouts should not set timers
def report(self, value: int, description: str | None = None):
msg = self.layout.progress_event(value, description or "")
assert msg is None
self.layout.paint()
ui.refresh()
def progress(message: str = "PLEASE WAIT") -> ProgressLayout:
return RustProgress(message.upper())
def bitcoin_progress(message: str) -> ProgressLayout:
return RustProgress(message.upper())
def pin_progress(message: str, description: str) -> ProgressLayout:
return RustProgress(message.upper(), description=description)
def monero_keyimage_sync_progress() -> ProgressLayout:
return RustProgress("SYNCING")
def monero_live_refresh_progress() -> ProgressLayout:
return RustProgress("REFRESHING", description="", indeterminate=True)
def monero_transaction_progress_inner() -> ProgressLayout:
return RustProgress("SIGNING TRANSACTION", description="")

View File

@ -0,0 +1,151 @@
from typing import TYPE_CHECKING
import storage.cache as storage_cache
from trezor import ui
import trezorui2
from . import RustLayout
if TYPE_CHECKING:
from trezor import loop
from typing import Any, Tuple
class HomescreenBase(RustLayout):
RENDER_INDICATOR: object | None = None
def __init__(self, layout: Any) -> None:
super().__init__(layout=layout)
self.is_connected = True
async def __iter__(self) -> Any:
# We need to catch the ui.Cancelled exception that kills us, because that means
# that we will need to draw on screen again after restart.
try:
return await super().__iter__()
except ui.Cancelled:
storage_cache.homescreen_shown = None
raise
def _first_paint(self) -> None:
from trezor import utils
if storage_cache.homescreen_shown is not self.RENDER_INDICATOR:
super()._first_paint()
storage_cache.homescreen_shown = self.RENDER_INDICATOR
# - RENDER_INDICATOR is set -> USB warning is not displayed
# - RENDER_INDICATOR is not set -> initially homescreen does not display warning
# - usb_checker_task only handles state changes
# Here we need to handle the case when homescreen is started with USB disconnected.
if not utils.usb_data_connected():
msg = self.layout.usb_event(False)
self._paint()
if msg is not None:
raise ui.Result(msg)
# In __debug__ mode, ignore {confirm,swipe,input}_signal.
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
return self.handle_timers(), self.handle_input_and_rendering()
class Homescreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.HOMESCREEN_ON
def __init__(
self,
label: str | None,
notification: str | None,
notification_is_error: bool,
hold_to_lock: bool,
) -> None:
level = 1
if notification is not None:
notification = notification.rstrip("!")
if "EXPERIMENTAL" in notification:
level = 2
elif notification_is_error:
level = 0
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
super().__init__(
layout=trezorui2.show_homescreen(
label=label or "My Trezor",
notification=notification,
notification_level=level,
hold=hold_to_lock,
skip_first_paint=skip,
),
)
async def usb_checker_task(self) -> None:
from trezor import io, loop
usbcheck = loop.wait(io.USB_CHECK)
while True:
is_connected = await usbcheck
if is_connected != self.is_connected:
self.is_connected = is_connected
self.layout.usb_event(is_connected)
self.layout.paint()
storage_cache.homescreen_shown = None
def create_tasks(self) -> Tuple[loop.AwaitableTask, ...]:
return super().create_tasks() + (self.usb_checker_task(),)
class Lockscreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
BACKLIGHT_LEVEL = ui.BACKLIGHT_LOW
def __init__(
self,
label: str | None,
bootscreen: bool = False,
) -> None:
self.bootscreen = bootscreen
if bootscreen:
self.BACKLIGHT_LEVEL = ui.BACKLIGHT_NORMAL
skip = (
not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR
)
super().__init__(
layout=trezorui2.show_lockscreen(
label=label or "My Trezor",
bootscreen=bootscreen,
skip_first_paint=skip,
),
)
async def __iter__(self) -> Any:
result = await super().__iter__()
if self.bootscreen:
self.request_complete_repaint()
return result
class Busyscreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.BUSYSCREEN_ON
def __init__(self, delay_ms: int) -> None:
skip = storage_cache.homescreen_shown is self.RENDER_INDICATOR
super().__init__(
layout=trezorui2.show_busyscreen(
title="PLEASE WAIT",
description="CoinJoin in progress.\n\nDo not disconnect your\nTrezor.",
time_ms=delay_ms,
skip_first_paint=skip,
)
)
async def __iter__(self) -> Any:
from apps.base import set_homescreen
# Handle timeout.
result = await super().__iter__()
assert result == trezorui2.CANCELLED
storage_cache.delete(storage_cache.APP_COMMON_BUSY_DEADLINE_MS)
set_homescreen()
return result

View File

@ -750,7 +750,7 @@ def confirm_value(
value: str,
description: str,
br_type: str,
br_code: ButtonRequestType = ButtonRequestType.Other,
br_code: ButtonRequestType = BR_TYPE_OTHER,
*,
verb: str | None = None,
hold: bool = False,
@ -975,7 +975,7 @@ async def confirm_coinjoin(
)
),
"coinjoin_final",
ButtonRequestType.Other,
BR_TYPE_OTHER,
)
)
@ -1000,7 +1000,7 @@ async def confirm_sign_identity(
data=identity,
description=challenge_visual + "\n" if challenge_visual else "",
br_type="sign_identity",
br_code=ButtonRequestType.Other,
br_code=BR_TYPE_OTHER,
)
@ -1020,7 +1020,7 @@ async def confirm_signverify(
title,
address,
"Confirm address:",
br_code=ButtonRequestType.Other,
br_code=BR_TYPE_OTHER,
)
await confirm_blob(
@ -1029,7 +1029,7 @@ async def confirm_signverify(
title,
message,
"Confirm message:",
br_code=ButtonRequestType.Other,
br_code=BR_TYPE_OTHER,
)
@ -1117,7 +1117,7 @@ async def confirm_pin_action(
title: str,
action: str | None,
description: str | None = "Do you really want to",
br_code: ButtonRequestType = ButtonRequestType.Other,
br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None:
return await confirm_action(
ctx,
@ -1133,7 +1133,7 @@ async def confirm_pin_action(
async def confirm_reenter_pin(
ctx: GenericContext,
br_type: str = "set_pin",
br_code: ButtonRequestType = ButtonRequestType.Other,
br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None:
return await confirm_action(
ctx,
@ -1148,7 +1148,7 @@ async def confirm_reenter_pin(
async def pin_mismatch(
ctx: GenericContext,
br_type: str = "set_pin",
br_code: ButtonRequestType = ButtonRequestType.Other,
br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None:
return await confirm_action(
ctx,
@ -1168,7 +1168,7 @@ async def confirm_set_new_pin(
action: str,
information: list[str],
description: str = "Do you want to",
br_code: ButtonRequestType = ButtonRequestType.Other,
br_code: ButtonRequestType = BR_TYPE_OTHER,
) -> None:
await confirm_action(
ctx,

View File

@ -54,7 +54,7 @@ or contain `[no changelog]` in the commit message.
## BUILD stage - [build.yml](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml)
All builds are published as artifacts so they can be downloaded and used.
Consists of **33 jobs** below:
Consists of **32 jobs** below:
### [core fw regular build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L20)
Build of Core into firmware. Regular version.
@ -106,53 +106,51 @@ Frozen version. That means you do not need any other files to run it,
it is just a single binary file that you can execute directly.
**Are you looking for a Trezor T emulator? This is most likely it.**
### [core unix frozen ui2 debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L288)
### [core unix frozen R debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L287)
### [core unix frozen R debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L302)
### [core unix frozen R debug build arm](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L301)
### [core unix frozen R debug build arm](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L316)
### [core unix R debugger build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L335)
### [core unix R debugger build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L320)
Debugger build for gdb/lldb.
### [core unix frozen debug asan build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L350)
### [core unix frozen debug asan build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L335)
### [core unix frozen debug build arm](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L366)
### [core unix frozen debug build arm](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L351)
### [core unix frozen btconly debug t1 build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L388)
### [core unix frozen btconly debug t1 build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L373)
### [core macos frozen regular build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L404)
### [core macos frozen regular build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L389)
### [crypto build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L429)
### [crypto build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L414)
Build of our cryptographic library, which is then incorporated into the other builds.
### [legacy fw regular build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L458)
### [legacy fw regular build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L443)
### [legacy fw regular debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L474)
### [legacy fw regular debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L459)
### [legacy fw btconly build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L491)
### [legacy fw btconly build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L476)
### [legacy fw btconly debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L510)
### [legacy fw btconly debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L495)
### [legacy emu regular debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L531)
### [legacy emu regular debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L516)
Regular version (not only Bitcoin) of above.
**Are you looking for a Trezor One emulator? This is most likely it.**
### [legacy emu regular debug asan build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L546)
### [legacy emu regular debug asan build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L531)
### [legacy emu regular debug build arm](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L564)
### [legacy emu regular debug build arm](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L549)
### [legacy emu btconly debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L590)
### [legacy emu btconly debug build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L575)
Build of Legacy into UNIX emulator. Use keyboard arrows to emulate button presses.
Bitcoin-only version.
### [legacy emu btconly debug asan build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L607)
### [legacy emu btconly debug asan build](https://github.com/trezor/trezor-firmware/blob/master/ci/build.yml#L592)
---
## TEST stage - [test.yml](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml)
All the tests run test cases on the freshly built emulators from the previous `BUILD` stage.
Consists of **36 jobs** below:
Consists of **35 jobs** below:
### [core unit python test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L15)
Python unit tests, checking core functionality.
@ -170,76 +168,74 @@ with the expected UI result.
See artifacts for a comprehensive report of UI.
See [docs/tests/ui-tests](../tests/ui-tests.md) for more info.
### [core device asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L94)
### [core device R test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L94)
### [core device R test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L127)
### [core device asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L126)
### [core device asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L159)
### [core btconly device test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L178)
### [core btconly device test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L145)
Device tests excluding altcoins, only for BTC.
### [core btconly device asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L198)
### [core btconly device asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L165)
### [core monero test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L219)
### [core monero test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L186)
Monero tests.
### [core monero asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L238)
### [core monero asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L205)
### [core u2f test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L260)
### [core u2f test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L227)
Tests for U2F and HID.
### [core u2f asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L279)
### [core u2f asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L246)
### [core fido2 test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L297)
### [core fido2 test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L264)
FIDO2 device tests.
### [core fido2 asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L320)
### [core fido2 asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L287)
### [core click test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L340)
### [core click test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L307)
Click tests.
See [docs/tests/click-tests](../tests/click-tests.md) for more info.
### [core click asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L357)
### [core click asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L324)
### [core upgrade test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L378)
### [core upgrade test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L345)
Upgrade tests.
See [docs/tests/upgrade-tests](../tests/upgrade-tests.md) for more info.
### [core upgrade asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L397)
### [core upgrade asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L364)
### [core persistence test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L419)
### [core persistence test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L386)
Persistence tests.
### [core persistence asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L435)
### [core persistence asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L402)
### [core hwi test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L453)
### [core hwi test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L420)
### [crypto test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L471)
### [crypto test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L438)
### [legacy device test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L502)
### [legacy device test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L469)
### [legacy asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L529)
### [legacy asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L496)
### [legacy btconly test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L541)
### [legacy btconly test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L508)
### [legacy btconly asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L561)
### [legacy btconly asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L528)
### [legacy upgrade test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L576)
### [legacy upgrade test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L543)
### [legacy upgrade asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L595)
### [legacy upgrade asan test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L562)
### [legacy hwi test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L616)
### [legacy hwi test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L583)
### [python test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L635)
### [python test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L602)
### [python support test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L654)
### [python support test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L621)
### [storage test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L664)
### [storage test](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L631)
### [core unix memory profiler](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L688)
### [core unix memory profiler](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L655)
### [connect test core](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L712)
### [connect test core](https://github.com/trezor/trezor-firmware/blob/master/ci/test.yml#L679)
---
## TEST-HW stage - [test-hw.yml](https://github.com/trezor/trezor-firmware/blob/master/ci/test-hw.yml)
@ -286,7 +282,7 @@ Consists of **2 jobs** below:
---
## DEPLOY stage - [deploy.yml](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml)
Consists of **15 jobs** below:
Consists of **14 jobs** below:
### [release core fw regular deploy](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L5)
@ -310,12 +306,10 @@ Consists of **15 jobs** below:
### [ui tests fixtures deploy](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L229)
### [sync emulators to aws](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L251)
### [ui tests R fixtures deploy](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L249)
### [ui tests R fixtures deploy](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L268)
### [sync emulators to aws](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L270)
### [sync emulators to aws](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L289)
### [common sync](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L314)
### [common sync](https://github.com/trezor/trezor-firmware/blob/master/ci/deploy.yml#L295)
---

View File

@ -439,6 +439,7 @@ def test_sign_tx_spend(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
messages.ButtonRequest(code=B.Other),
@ -447,9 +448,9 @@ def test_sign_tx_spend(client: Client):
request_output(0),
request_output(1),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.SignTx),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
(tt, messages.ButtonRequest(code=B.SignTx)),
request_input(0),
request_output(0),
request_output(1),

View File

@ -593,13 +593,14 @@ def test_send_btg_external_presigned(client: Client):
script_type=messages.OutputScriptType.PAYTOADDRESS,
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(FAKE_TXHASH_6f0398),

View File

@ -131,6 +131,7 @@ def test_purchase_ticket_decred(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
@ -140,7 +141,7 @@ def test_purchase_ticket_decred(client: Client):
request_output(1),
request_output(2),
messages.ButtonRequest(code=B.SignTx),
messages.ButtonRequest(code=B.SignTx),
(tt, messages.ButtonRequest(code=B.SignTx)),
request_input(0),
request_meta(FAKE_TXHASH_4d8acd),
request_input(0, FAKE_TXHASH_4d8acd),

View File

@ -658,17 +658,22 @@ def test_fee_high_hardfail(client: Client):
client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily
)
with client:
tt = client.features.model == "T"
finished = False
def input_flow():
nonlocal finished
for expected in (
B.ConfirmOutput,
B.ConfirmOutput,
(tt, B.ConfirmOutput),
B.FeeOverThreshold,
B.SignTx,
B.SignTx,
(tt, B.SignTx),
):
if isinstance(expected, tuple):
is_valid, expected = expected
if not is_valid:
continue
br = yield
assert br.code == expected
client.debug.press_yes()

View File

@ -216,19 +216,20 @@ def test_p2wpkh_in_p2sh_presigned(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(1),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(2),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_20912f),
@ -267,19 +268,20 @@ def test_p2wpkh_in_p2sh_presigned(client: Client):
# Test corrupted script hash in scriptsig.
inp1.script_sig[10] ^= 1
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(1),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(2),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_20912f),
@ -399,13 +401,14 @@ def test_p2wsh_external_presigned(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_ec16dc),
@ -444,13 +447,14 @@ def test_p2wsh_external_presigned(client: Client):
# Test corrupted signature in witness.
inp2.witness[10] ^= 1
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_ec16dc),
@ -509,13 +513,14 @@ def test_p2tr_external_presigned(client: Client):
script_type=messages.OutputScriptType.PAYTOTAPROOT,
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(1),
messages.ButtonRequest(code=B.SignTx),
request_input(1),
@ -541,13 +546,14 @@ def test_p2tr_external_presigned(client: Client):
# Test corrupted signature in witness.
inp2.witness[10] ^= 1
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(1),
messages.ButtonRequest(code=B.SignTx),
request_input(1),
@ -611,16 +617,17 @@ def test_p2wpkh_with_proof(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
request_output(1),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_e5b7e2),
@ -703,13 +710,14 @@ def test_p2tr_with_proof(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_input(1),

View File

@ -261,13 +261,14 @@ def test_external_presigned(client: Client):
)
with client:
tt = client.features.model == "T"
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
messages.ButtonRequest(code=B.ConfirmOutput),
messages.ButtonRequest(code=B.ConfirmOutput),
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
messages.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_e38206),

View File

@ -131,10 +131,9 @@ def test_data_streaming(client: Client):
[
messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
(tt, messages.ButtonRequest(code=messages.ButtonRequestType.SignTx)),
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
(tt, messages.ButtonRequest(code=messages.ButtonRequestType.Other)),
(tt, messages.ButtonRequest(code=messages.ButtonRequestType.SignTx)),
(tt, messages.ButtonRequest(code=messages.ButtonRequestType.SignTx)),
message_filters.EthereumTxRequest(
data_length=1_024,
signature_r=None,
@ -349,6 +348,9 @@ def input_flow_skip(client: Client, cancel: bool = False):
def input_flow_scroll_down(client: Client, cancel: bool = False):
if client.features.model == "R":
pytest.skip("Freezes")
yield # confirm address
client.debug.wait_layout()
client.debug.press_yes()

View File

@ -20,7 +20,6 @@ import pytest
from trezorlib import cosi
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path
pytestmark = [pytest.mark.skip_t2, pytest.mark.skip_tr]
@ -111,10 +110,13 @@ def test_cosi_sign3(client: Client):
cosi.verify_combined(signature, DIGEST, global_pk)
@pytest.mark.skip_t1
def test_cosi_different_key(client: Client):
with pytest.raises(TrezorFailure):
commit = cosi.commit(client, parse_path("m/10018h/0h"))
cosi.sign(
client, parse_path("m/10018h/1h"), DIGEST, commit.commitment, commit.pubkey
)
# NOTE: test below commented out because of
# `RuntimeError: Don't skip tests for all trezor models!`
# @pytest.mark.skip_t1
# def test_cosi_different_key(client: Client):
# with pytest.raises(TrezorFailure):
# commit = cosi.commit(client, parse_path("m/10018h/0h"))
# cosi.sign(
# client, parse_path("m/10018h/1h"), DIGEST, commit.commitment, commit.pubkey
# )

View File

@ -211,7 +211,7 @@ def test_invalid_seed_core(client: Client):
yield
assert "Select number of words" in layout().text
client.debug.press_right()
client.debug.press_yes()
yield
yield
@ -221,7 +221,7 @@ def test_invalid_seed_core(client: Client):
yield
assert "Enter recovery seed" in layout().text
client.debug.press_right()
client.debug.press_yes()
yield
assert "WORD ENTERING" in layout().text

View File

@ -83,7 +83,7 @@ def test_tt_pin_passphrase(client: Client):
client.debug.input("654")
yield
assert "Select the number of words" in layout().text
assert "Select number of words" in layout().text
client.debug.press_yes()
yield
@ -170,7 +170,7 @@ def test_tt_nopin_nopassphrase(client: Client):
client.debug.press_yes()
yield
assert "Select the number of words" in layout().text
assert "Select number of words" in layout().text
client.debug.press_yes()
yield

View File

@ -53,7 +53,7 @@ def test_abort(emulator: Emulator):
assert layout.get_title() == "RECOVERY MODE"
layout = debug.click(buttons.OK, wait=True)
assert "Select the number of words" in layout.text
assert "Select number of words" in layout.text
device_handler.restart(emulator)
debug = device_handler.debuglink()
@ -63,7 +63,7 @@ def test_abort(emulator: Emulator):
# no waiting for layout because layout doesn't change
layout = debug.read_layout()
assert "Select the number of words" in layout.text
assert "Select number of words" in layout.text
layout = debug.click(buttons.CANCEL, wait=True)
assert layout.get_title() == "ABORT RECOVERY"