feat(core): bootloader design for T2B1

[no changelog]
tychovrahe/trbootloader/main_before
tychovrahe 1 year ago
parent 06eb7c7d5e
commit 2d6dfd74f6

@ -23,7 +23,7 @@ PRODUCTION ?= 0
PYOPT ?= 1
BITCOIN_ONLY ?= 0
BOOTLOADER_QA ?= 0
TREZOR_MODEL ?= T
TREZOR_MODEL ?= R
TREZOR_MEMPERF ?= 0
ADDRESS_SANITIZER ?= 0
CMAKELISTS ?= 0

@ -28,11 +28,11 @@ CPPPATH_MOD = []
CPPDEFINES_MOD = []
SOURCE_MOD = []
if TREZOR_MODEL in ('1', 'R'):
if TREZOR_MODEL in ('R', ):
FONT_NORMAL='Font_PixelOperator_Regular_8'
FONT_DEMIBOLD=None
FONT_BOLD=None
FONT_MONO='Font_PixelOperatorMono_Regular_8'
FONT_DEMIBOLD='Font_PixelOperator_Regular_8'
FONT_BOLD='Font_PixelOperator_Bold_8'
FONT_MONO='Font_PixelOperator_Regular_8'
if TREZOR_MODEL in ('T', ):
FONT_NORMAL='Font_TTHoves_Regular_21'
FONT_DEMIBOLD=None

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@ -530,7 +530,7 @@ int bootloader_main(void) {
ui_screen_boot_click();
}
ui_fadeout();
ui_screen_boot_empty(false);
}
ensure_compatible_settings();

@ -1,21 +1,16 @@
use crate::ui::{
component::{
text::paragraphs::{ParagraphVecShort, Paragraphs},
Child, Component, Event, EventCtx, Pad,
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
display,
display::{Color, Font},
geometry::{Point, Rect},
model_tr::{
bootloader::theme::WHITE,
component::{ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos},
constant::WIDTH,
},
constant::screen,
display::{Color, Icon},
geometry::{Point, Rect, CENTER},
};
use super::{
super::{
component::{Button, ButtonMsg::Clicked},
constant::{HEIGHT, WIDTH},
theme::WHITE,
},
ReturnToC,
};
use super::ReturnToC;
#[derive(Copy, Clone)]
pub enum ConfirmMsg {
@ -29,93 +24,130 @@ impl ReturnToC for ConfirmMsg {
}
}
pub struct Confirm {
pub struct Confirm<'a> {
bg: Pad,
bg_color: Color,
icon: Option<Icon>,
message: Child<Paragraphs<ParagraphVecShort<&'static str>>>,
left: Child<Button<&'static str>>,
right: Child<Button<&'static str>>,
confirm_left: bool,
label: &'static str,
message: Child<Label<&'a str>>,
buttons: ButtonController<&'static str>,
buttons_info: ButtonController<&'static str>,
alert: Option<Label<&'a str>>,
info: Option<Label<&'a str>>,
info_shown: bool,
}
impl Confirm {
impl<'a> Confirm<'a> {
pub fn new(
bg_color: Color,
icon: Option<Icon>,
message: Paragraphs<ParagraphVecShort<&'static str>>,
left: Button<&'static str>,
right: Button<&'static str>,
confirm_left: bool,
label: &'static str,
message: Label<&'a str>,
alert: Option<Label<&'a str>>,
info: Option<Label<&'a str>>,
text: &'static str,
) -> Self {
let mut instance = Self {
bg: Pad::with_background(bg_color),
let controller = if info.is_some() {
ButtonController::new(ButtonLayout::cancel_armed_text("INSTALL", " i "))
} else {
ButtonController::new(ButtonLayout::cancel_none_text(text))
};
Self {
bg: Pad::with_background(bg_color).with_clear(),
bg_color,
icon,
label,
message: Child::new(message),
left: Child::new(left),
right: Child::new(right),
confirm_left,
};
instance.bg.clear();
instance
alert,
info,
buttons: controller,
buttons_info: ButtonController::new(ButtonLayout::arrow_none_none()),
info_shown: false,
}
}
}
impl Component for Confirm {
impl<'a> Component for Confirm<'a> {
type Msg = ConfirmMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg
.place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT)));
self.message
.place(Rect::new(Point::new(10, 0), Point::new(118, 50)));
self.bg.place(bounds);
let (message_area, alert_area) = if self.alert.is_some() {
(
Rect::new(Point::new(0, 12), Point::new(WIDTH, 39)),
Rect::new(Point::new(0, 39), Point::new(WIDTH, 54)),
)
} else {
(
Rect::new(Point::new(0, 12), Point::new(WIDTH, 54)),
Rect::zero(),
)
};
self.message.place(message_area);
self.alert.place(alert_area);
self.info
.place(Rect::new(Point::new(0, 12), Point::new(WIDTH, 54)));
let button_area = bounds.split_bottom(12).1;
self.left.place(button_area);
self.right.place(button_area);
self.buttons.place(button_area);
self.buttons_info.place(button_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(Clicked) = self.left.event(ctx, event) {
return if self.confirm_left {
Some(Self::Msg::Confirm)
} else {
Some(Self::Msg::Cancel)
};
};
if let Some(Clicked) = self.right.event(ctx, event) {
return if self.confirm_left {
Some(Self::Msg::Cancel)
} else {
Some(Self::Msg::Confirm)
if self.info_shown {
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) =
self.buttons.event(ctx, event)
{
self.info_shown = false;
self.message.request_complete_repaint(ctx);
self.buttons.request_complete_repaint(ctx);
self.bg.clear();
self.request_complete_repaint(ctx);
};
};
None
None
} else if self.info.is_some() {
match self.buttons.event(ctx, event) {
Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) => Some(ConfirmMsg::Cancel),
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle)) => {
Some(ConfirmMsg::Confirm)
}
Some(ButtonControllerMsg::Triggered(ButtonPos::Right)) => {
self.info_shown = true;
self.bg.clear();
self.info.request_complete_repaint(ctx);
self.buttons_info.request_complete_repaint(ctx);
self.request_complete_repaint(ctx);
None
}
_ => None,
}
} else {
match self.buttons.event(ctx, event) {
Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) => Some(ConfirmMsg::Cancel),
Some(ButtonControllerMsg::Triggered(ButtonPos::Right)) => Some(ConfirmMsg::Confirm),
_ => None,
}
}
}
fn paint(&mut self) {
self.bg.paint();
if let Some(icon) = self.icon {
icon.draw(
Point::new(screen().center().x, 45),
CENTER,
WHITE,
self.bg_color,
);
}
display::text_top_left(Point::zero(), self.label, Font::BOLD, WHITE, self.bg_color);
self.message.paint();
self.left.paint();
self.right.paint();
if self.info_shown {
self.info.paint();
self.buttons_info.paint();
} else {
self.message.paint();
self.alert.paint();
self.buttons.paint();
}
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.left.bounds(sink);
self.right.bounds(sink);
self.buttons.bounds(sink);
}
}

@ -0,0 +1,50 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
constant::screen,
display::{self, Font},
geometry::{Offset, Rect},
model_tr::bootloader::theme::{BLD_BG, BLD_FG},
};
pub struct Connect {
bg: Pad,
message: &'static str,
}
impl Connect {
pub fn new(message: &'static str) -> Self {
let mut instance = Self {
bg: Pad::with_background(BLD_BG),
message,
};
instance.bg.clear();
instance
}
}
impl Component for Connect {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(screen());
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
let font = Font::NORMAL;
self.bg.paint();
display::text_center(
screen().center() + Offset::y(font.text_height() / 2),
self.message,
Font::NORMAL,
BLD_FG,
BLD_BG,
);
}
}

@ -1,19 +1,19 @@
use crate::ui::{
component::{
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
Child, Component, Event, EventCtx, Pad,
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
constant::screen,
display::Icon,
geometry::{Alignment, Point, Rect, TOP_LEFT, TOP_RIGHT},
model_tr::bootloader::{
theme::{BLD_BG, TEXT_NORMAL},
ReturnToC,
},
geometry::{LinearPlacement, Point, Rect},
};
use super::super::{
bootloader::{
theme::{bld_button_default, BLD_BG, TEXT_NORMAL},
title::Title,
ReturnToC,
},
component::{Button, ButtonMsg::Clicked, ButtonPos},
constant::{HEIGHT, WIDTH},
use crate::ui::model_tr::{
bootloader::theme::BLD_FG,
component::{ButtonController, ButtonControllerMsg::Triggered, ButtonLayout, ButtonPos},
constant::WIDTH,
theme::ICON_WARN_TITLE,
};
#[repr(u32)]
@ -28,38 +28,27 @@ impl ReturnToC for IntroMsg {
}
}
pub struct Intro {
pub struct Intro<'a> {
bg: Pad,
title: Child<Title>,
host: Child<Button<&'static str>>,
menu: Child<Button<&'static str>>,
text: Child<Paragraphs<ParagraphVecShort<&'static str>>>,
title: Child<Label<&'a str>>,
buttons: Child<ButtonController<&'static str>>,
text: Child<Label<&'a str>>,
}
impl Intro {
pub fn new(bld_version: &'static str, vendor: &'static str, version: &'static str) -> Self {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&TEXT_NORMAL, version));
messages.add(Paragraph::new(&TEXT_NORMAL, vendor));
let p1 =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_start());
impl<'a> Intro<'a> {
pub fn new(title: &'a str, content: &'a str) -> Self {
let mut instance = Self {
bg: Pad::with_background(BLD_BG),
title: Child::new(Title::new(bld_version)),
host: Child::new(Button::with_text(
ButtonPos::Left,
"INSTALL FIRMWARE",
bld_button_default(),
)),
menu: Child::new(Button::with_text(
ButtonPos::Right,
"MENU",
bld_button_default(),
)),
text: Child::new(p1),
bg: Pad::with_background(BLD_BG).with_clear(),
title: Child::new(
Label::new(title, Alignment::Center, TEXT_NORMAL)
.vertically_aligned(Alignment::Start),
),
buttons: ButtonController::new(ButtonLayout::text_none_text("INSTALL FW", "MENU"))
.into_child(),
text: Child::new(
Label::new(content, Alignment::Start, TEXT_NORMAL)
.vertically_aligned(Alignment::Center),
),
};
instance.bg.clear();
@ -67,47 +56,48 @@ impl Intro {
}
}
impl Component for Intro {
impl<'a> Component for Intro<'a> {
type Msg = IntroMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg
.place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT)));
self.bg.place(screen());
self.title
.place(Rect::new(Point::new(10, 0), Point::new(128, 8)));
.place(Rect::new(Point::zero(), Point::new(WIDTH, 8)));
let button_area = bounds.split_bottom(12).1;
self.host.place(button_area);
self.menu.place(button_area);
self.buttons.place(bounds.split_bottom(12).1);
self.text
.place(Rect::new(Point::new(10, 20), Point::new(118, 50)));
.place(Rect::new(Point::new(0, 12), Point::new(WIDTH, 54)));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(Clicked) = self.menu.event(ctx, event) {
return Some(Self::Msg::Menu);
};
if let Some(Clicked) = self.host.event(ctx, event) {
let msg = self.buttons.event(ctx, event);
if let Some(Triggered(ButtonPos::Left)) = msg {
return Some(Self::Msg::Host);
};
if let Some(Triggered(ButtonPos::Right)) = msg {
return Some(Self::Msg::Menu);
};
None
}
fn paint(&mut self) {
self.bg.paint();
self.title.paint();
Icon::new(ICON_WARN_TITLE).draw(screen().top_left(), TOP_LEFT, BLD_FG, BLD_BG);
Icon::new(ICON_WARN_TITLE).draw(screen().top_right(), TOP_RIGHT, BLD_FG, BLD_BG);
self.text.paint();
self.host.paint();
self.menu.paint();
self.buttons.paint();
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.title.bounds(sink);
self.text.bounds(sink);
self.host.bounds(sink);
self.menu.bounds(sink);
self.buttons.bounds(sink);
}
}

@ -1,46 +1,149 @@
#[cfg(feature = "ui_debug")]
use crate::trace::{Trace, Tracer};
use crate::ui::{
component::{Child, Component, Event, EventCtx, Never, Pad},
geometry::{Point, Rect},
component::{Child, Component, ComponentExt, Event, EventCtx, Pad},
constant::screen,
display,
display::{Font, Icon},
geometry::{Offset, Rect, CENTER},
model_tr::{
bootloader::{
theme::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH},
ReturnToC,
},
component::{Choice, ChoiceFactory, ChoicePage, ChoicePageMsg},
},
};
use super::super::{
bootloader::{theme::BLD_BG, title::Title},
constant::{HEIGHT, WIDTH},
};
#[repr(u32)]
#[derive(Copy, Clone)]
pub enum MenuMsg {
Close = 1,
Reboot = 2,
FactoryReset = 3,
}
impl ReturnToC for MenuMsg {
fn return_to_c(self) -> u32 {
self as u32
}
}
const CHOICE_LENGTH: usize = 3;
pub struct MenuChoice {
text1: &'static str,
text2: &'static str,
icon: &'static [u8],
}
impl MenuChoice {
pub fn new(text1: &'static str, text2: &'static str, icon: &'static [u8]) -> Self {
Self { text1, text2, icon }
}
}
impl Choice<&'static str> for MenuChoice {
fn paint_center(&self, _area: Rect, _inverse: bool) {
Icon::new(self.icon).draw(screen().center() + Offset::y(-20), CENTER, BLD_FG, BLD_BG);
display::text_center(
screen().center() + Offset::y(0),
self.text1,
Font::NORMAL,
BLD_FG,
BLD_BG,
);
display::text_center(
screen().center() + Offset::y(10),
self.text2,
Font::NORMAL,
BLD_FG,
BLD_BG,
);
}
}
#[cfg(feature = "ui_debug")]
impl Trace for MenuChoice {
fn trace(&self, t: &mut dyn Tracer) {
t.open("MenuChoice");
t.close();
}
}
pub struct MenuChoiceFactory;
impl MenuChoiceFactory {
const CHOICES: [(&'static str, &'static str, &'static [u8]); CHOICE_LENGTH] = [
("Factory", "Reset", ICON_TRASH),
("Reboot", "Trezor", ICON_REDO),
("Exit", "Menu", ICON_EXIT),
];
pub fn new() -> Self {
Self {}
}
}
impl ChoiceFactory<&'static str> for MenuChoiceFactory {
type Item = MenuChoice;
fn count(&self) -> usize {
CHOICE_LENGTH
}
fn get(&self, choice_index: usize) -> MenuChoice {
MenuChoice::new(
MenuChoiceFactory::CHOICES[choice_index].0,
MenuChoiceFactory::CHOICES[choice_index].1,
MenuChoiceFactory::CHOICES[choice_index].2,
)
}
}
pub struct Menu {
bg: Pad,
title: Child<Title>,
pg: Child<ChoicePage<MenuChoiceFactory, &'static str>>,
}
impl Menu {
pub fn new(bld_version: &'static str) -> Self {
let mut instance = Self {
bg: Pad::with_background(BLD_BG),
title: Child::new(Title::new(bld_version)),
};
instance.bg.clear();
instance
pub fn new() -> Self {
let choices = MenuChoiceFactory::new();
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
pg: ChoicePage::new(choices)
.with_carousel(true)
.with_only_one_item(true)
.into_child(),
}
}
}
impl Component for Menu {
type Msg = Never;
type Msg = MenuMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg
.place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT)));
self.title
.place(Rect::new(Point::new(10, 0), Point::new(128, 8)));
self.bg.place(bounds);
self.pg.place(bounds);
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
match self.pg.event(ctx, event) {
Some(ChoicePageMsg::Choice(0)) => Some(MenuMsg::FactoryReset),
Some(ChoicePageMsg::Choice(1)) => Some(MenuMsg::Reboot),
Some(ChoicePageMsg::Choice(2)) => Some(MenuMsg::Close),
_ => None,
}
}
fn paint(&mut self) {
self.bg.paint();
self.title.paint();
self.pg.paint();
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.pg.bounds(sink)
}
}

@ -1,43 +1,44 @@
use crate::{
trezorhal::io::io_button_read,
strutil::hexlify,
time::Duration,
trezorhal::{io::io_button_read, time},
ui::{
component::{Component, Never},
display::{self, Font},
geometry::Point,
model_tr::constant,
},
};
use heapless::String;
mod confirm;
mod connect;
mod intro;
mod menu;
mod theme;
mod title;
mod welcome;
use crate::ui::{
component::{
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
Event, EventCtx,
},
constant::{screen, WIDTH},
display::{fade_backlight_duration, Color, Icon, TextOverlay},
component::{Event, EventCtx, Label, LineBreaking::BreakWordsNoHyphen},
constant::screen,
display::{Color, Icon},
event::ButtonEvent,
geometry::{LinearPlacement, Offset, Rect, CENTER},
util::{from_c_array, from_c_str},
};
use super::{
bootloader::{
confirm::Confirm,
intro::Intro,
menu::Menu,
theme::{bld_button_cancel, bld_button_default, BLD_BG, BLD_FG},
geometry::{Alignment, Alignment::Center, Offset, Rect, TOP_CENTER},
model_tr::{
bootloader::{
confirm::Confirm,
connect::Connect,
intro::Intro,
menu::Menu,
theme::{BLD_BG, BLD_FG, ICON_ALERT, ICON_SPINNER, LOGO_EMPTY},
welcome::Welcome,
},
component::{ResultScreen, WelcomeScreen},
theme::ICON_SUCCESS,
},
component::{Button, ButtonPos, ResultScreen},
constant,
theme::{BACKLIGHT_NORMAL, ICON_FAIL, ICON_SUCCESS, LOGO_EMPTY},
util::{from_c_array, from_c_str},
};
const SCREEN_ADJ: Rect = screen().split_top(64).0;
pub type BootloaderString = String<128>;
pub trait ReturnToC {
fn return_to_c(self) -> u32;
@ -77,9 +78,9 @@ where
F: Component,
F::Msg: ReturnToC,
{
frame.place(SCREEN_ADJ);
frame.place(screen());
frame.paint();
fade_backlight_duration(BACKLIGHT_NORMAL, 500);
display::refresh();
while button_eval().is_some() {}
@ -94,6 +95,7 @@ where
}
frame.paint();
display::refresh();
}
}
}
@ -102,7 +104,7 @@ fn show<F>(frame: &mut F)
where
F: Component,
{
frame.place(SCREEN_ADJ);
frame.place(screen());
display::sync();
frame.paint();
display::refresh();
@ -113,81 +115,80 @@ extern "C" fn screen_install_confirm(
vendor_str: *const cty::c_char,
vendor_str_len: u8,
version: *const cty::c_char,
_fingerprint: *const cty::uint8_t,
downgrade: bool,
vendor: bool,
fingerprint: *const cty::uint8_t,
should_keep_seed: bool,
is_newvendor: bool,
version_cmp: cty::c_int,
) -> u32 {
let text = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) });
let version = unwrap!(unsafe { from_c_str(version) });
let icon: Option<Icon> = None;
let mut fingerprint_buffer: [u8; 64] = [0; 64];
let fingerprint_str = unsafe {
let fingerprint_slice = core::slice::from_raw_parts(fingerprint as *const u8, 32);
hexlify(fingerprint_slice, &mut fingerprint_buffer);
core::str::from_utf8_unchecked(fingerprint_buffer.as_ref())
};
let msg = if downgrade {
"Downgrade firmware by"
} else if vendor {
"Change vendor to"
let mut version_str: BootloaderString = String::new();
unwrap!(version_str.push_str("Firmware version "));
unwrap!(version_str.push_str(version));
unwrap!(version_str.push_str("\nby "));
unwrap!(version_str.push_str(text));
let title_str = if is_newvendor {
"CHANGE FW VENDOR"
} else if version_cmp > 0 {
"UPDATE FIRMWARE"
} else if version_cmp == 0 {
"REINSTALL FW"
} else {
"Update firmware by"
"DOWNGRADE FW"
};
let mut message = ParagraphVecShort::new();
message.add(Paragraph::new(&theme::TEXT_NORMAL, msg).centered());
message.add(Paragraph::new(&theme::TEXT_NORMAL, text).centered());
message.add(Paragraph::new(&theme::TEXT_NORMAL, version).centered());
if vendor || downgrade {
message.add(Paragraph::new(&theme::TEXT_BOLD, "Seed will be erased!").centered());
}
let message = Label::new(version_str.as_str(), Alignment::Start, theme::TEXT_NORMAL)
.vertically_aligned(Center);
let fingerprint = Label::new(
fingerprint_str,
Alignment::Start,
theme::TEXT_NORMAL.with_line_breaking(BreakWordsNoHyphen),
)
.vertically_aligned(Center);
// TODO: this relies on StrBuffer support for bootloader, decide what to do
let left = Button::with_text(ButtonPos::Left, "CANCEL", bld_button_cancel());
let right = Button::with_text(ButtonPos::Right, "INSTALL", bld_button_default());
let alert = (!should_keep_seed).then_some(Label::new(
"Seed will be erased!",
Alignment::Start,
theme::TEXT_NORMAL,
));
let mut frame = Confirm::new(
BLD_BG,
icon,
Paragraphs::new(message).with_placement(LinearPlacement::vertical().align_at_center()),
left,
right,
false,
title_str,
message,
alert,
Some(fingerprint),
"INSTALL",
);
run(&mut frame)
}
#[no_mangle]
extern "C" fn screen_wipe_confirm() -> u32 {
let icon: Option<Icon> = None; //Some(ERASE_BIG);
let mut messages = ParagraphVecShort::new();
messages.add(
Paragraph::new(
&theme::TEXT_NORMAL,
"Do you really want to wipe the device?",
)
.centered(),
);
messages.add(Paragraph::new(&theme::TEXT_BOLD, "Seed will be erased!").centered());
let message =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
// TODO: this relies on StrBuffer support for bootloader, decide what to do
let left = Button::with_text(ButtonPos::Left, "WIPE", bld_button_default());
let right = Button::with_text(ButtonPos::Right, "CANCEL", bld_button_cancel());
let alert = Label::new(
"Seed and firmware will be erased!",
Alignment::Start,
theme::TEXT_NORMAL,
)
.vertically_aligned(Center);
let mut frame = Confirm::new(BLD_BG, icon, message, left, right, true);
let mut frame = Confirm::new(BLD_BG, "FACTORY RESET", alert, None, None, "RESET");
run(&mut frame)
}
#[no_mangle]
extern "C" fn screen_menu(bld_version: *const cty::c_char) -> u32 {
let bld_version = unwrap!(unsafe { from_c_str(bld_version) });
run(&mut Menu::new(bld_version))
extern "C" fn screen_menu(_bld_version: *const cty::c_char) -> u32 {
run(&mut Menu::new())
}
#[no_mangle]
@ -201,97 +202,113 @@ extern "C" fn screen_intro(
let version = unwrap!(unsafe { from_c_str(version) });
let bld_version = unwrap!(unsafe { from_c_str(bld_version) });
run(&mut Intro::new(bld_version, vendor, version))
let mut title_str: BootloaderString = String::new();
unwrap!(title_str.push_str("BOOTLOADER "));
unwrap!(title_str.push_str(bld_version));
let mut version_str: BootloaderString = String::new();
unwrap!(version_str.push_str("Firmware version "));
unwrap!(version_str.push_str(version));
unwrap!(version_str.push_str("\nby "));
unwrap!(version_str.push_str(vendor));
let mut frame = Intro::new(title_str.as_str(), version_str.as_str());
run(&mut frame)
}
fn screen_progress(
text: &str,
text2: &str,
progress: u16,
initialize: bool,
fg_color: Color,
bg_color: Color,
_icon: Option<(&[u8], Color)>,
icon: Option<(Icon, Color)>,
) {
if initialize {
display::rect_fill(constant::screen(), bg_color);
}
let loader_area = Rect::new(Point::new(5, 24), Point::new(WIDTH - 5, 24 + 16));
let mut text = TextOverlay::new(text, Font::NORMAL);
text.place(loader_area.center() + Offset::y(Font::NORMAL.text_height() / 2));
let progress = if progress < 20 { 20 } else { progress };
let fill_to = (loader_area.width() as u32 * progress as u32) / 1000;
display::bar_with_text_and_fill(
loader_area,
Some(&text),
display::rect_rounded2_partial(
Rect::new(
screen().top_center() + Offset::new(-9, 3),
screen().top_center() + Offset::new(9, 18 + 3),
),
fg_color,
bg_color,
((100_u32 * progress as u32) / 1000) as _,
icon,
);
display::text_center(
screen().center() + Offset::y(8),
text,
Font::BOLD,
fg_color,
bg_color,
);
display::text_center(
screen().center() + Offset::y(20),
text2,
Font::BOLD,
fg_color,
bg_color,
0,
fill_to as _,
);
display::refresh();
}
#[no_mangle]
extern "C" fn screen_install_progress(progress: u16, initialize: bool, _initial_setup: bool) {
screen_progress(
"INSTALLING FIRMWARE",
"Installing",
"Firmware",
progress,
initialize,
BLD_FG,
BLD_BG,
None,
)
Some((Icon::new(ICON_SUCCESS), BLD_FG)),
);
}
#[no_mangle]
extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) {
screen_progress(
"WIPING DEVICE",
"Wiping",
"Trezor",
progress,
initialize,
theme::BLD_FG,
BLD_FG,
BLD_BG,
None,
)
Some((Icon::new(ICON_SUCCESS), BLD_FG)),
);
}
#[no_mangle]
extern "C" fn screen_connect() {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Waiting for host...").centered());
let mut frame =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut frame = Connect::new("Waiting for host...");
show(&mut frame);
}
#[no_mangle]
extern "C" fn screen_wipe_success() {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Device wiped").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "successfully.").centered());
let m_top =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let title = Label::new("Trezor Reset", Alignment::Center, theme::TEXT_BOLD)
.vertically_aligned(Alignment::Center);
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "PLEASE RECONNECT").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "THE DEVICE").centered());
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let content = Label::new(
"Reconnect\nthe device",
Alignment::Center,
theme::TEXT_NORMAL,
)
.vertically_aligned(Alignment::Center);
let mut frame = ResultScreen::new(
BLD_FG,
BLD_BG,
Icon::new(ICON_SUCCESS),
m_top,
m_bottom,
Icon::new(ICON_SPINNER),
title,
content,
true,
);
show(&mut frame);
@ -299,119 +316,83 @@ extern "C" fn screen_wipe_success() {
#[no_mangle]
extern "C" fn screen_wipe_fail() {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Device wipe was").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "not successful.").centered());
let m_top =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let title = Label::new("Reset failed", Alignment::Center, theme::TEXT_BOLD)
.vertically_aligned(Alignment::Center);
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "PLEASE RECONNECT").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "THE DEVICE").centered());
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let content = Label::new(
"Reconnect\nthe device",
Alignment::Center,
theme::TEXT_NORMAL,
)
.vertically_aligned(Alignment::Center);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, Icon::new(ICON_FAIL), m_top, m_bottom, true);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, Icon::new(ICON_ALERT), title, content, true);
show(&mut frame);
}
#[no_mangle]
extern "C" fn screen_boot_empty(_firmware_present: bool, _fading: bool) {
Icon::new(LOGO_EMPTY).draw(SCREEN_ADJ.center(), CENTER, BLD_FG, BLD_BG);
extern "C" fn screen_boot_empty(_firmware_present: bool) {
display::rect_fill(constant::screen(), BLD_BG);
Icon::new(LOGO_EMPTY).draw(
screen().top_center() + Offset::y(11),
TOP_CENTER,
BLD_FG,
BLD_BG,
);
display::refresh();
if !_firmware_present {
time::sleep(Duration::from_millis(1000));
}
}
#[no_mangle]
extern "C" fn screen_install_fail() {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Firmware installation was").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "not successful.").centered());
let m_top =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "PLEASE RECONNECT").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "THE DEVICE").centered());
let title = Label::new("Install failed", Alignment::Center, theme::TEXT_BOLD)
.vertically_aligned(Alignment::Center);
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, Icon::new(ICON_FAIL), m_top, m_bottom, true);
show(&mut frame);
}
fn screen_install_success_bld(msg: &'static str, complete_draw: bool) {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Firmware installed").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "successfully.").centered());
let m_top =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, msg).centered());
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let content = Label::new(
"Reconnect\nthe device",
Alignment::Center,
theme::TEXT_NORMAL,
)
.vertically_aligned(Alignment::Center);
let mut frame = ResultScreen::new(
BLD_FG,
BLD_BG,
Icon::new(ICON_SUCCESS),
m_top,
m_bottom,
complete_draw,
);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, Icon::new(ICON_ALERT), title, content, true);
show(&mut frame);
}
fn screen_install_success_initial(msg: &'static str, complete_draw: bool) {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Firmware installed").centered());
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "successfully.").centered());
let m_top =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_NORMAL, msg).centered());
#[no_mangle]
extern "C" fn screen_install_success(
reboot_msg: *const cty::c_char,
_initial_setup: bool,
complete_draw: bool,
) {
let msg = unwrap!(unsafe { from_c_str(reboot_msg) });
let title = Label::new("Installed", Alignment::Center, theme::TEXT_BOLD)
.vertically_aligned(Alignment::Center);
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let content = Label::new(msg, Alignment::Center, theme::TEXT_NORMAL)
.vertically_aligned(Alignment::Center);
let mut frame = ResultScreen::new(
BLD_FG,
BLD_BG,
Icon::new(ICON_SUCCESS),
m_top,
m_bottom,
Icon::new(ICON_SPINNER),
title,
content,
complete_draw,
);
show(&mut frame);
}
#[no_mangle]
extern "C" fn screen_install_success(
reboot_msg: *const cty::c_char,
initial_setup: bool,
complete_draw: bool,
) {
let msg = unwrap!(unsafe { from_c_str(reboot_msg) });
if initial_setup {
screen_install_success_initial(msg, complete_draw)
} else {
screen_install_success_bld(msg, complete_draw)
}
extern "C" fn screen_welcome() {
let mut frame = Welcome::new();
show(&mut frame);
}
#[no_mangle]
extern "C" fn screen_welcome() {
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&theme::TEXT_BOLD, "Get started with").centered());
messages.add(Paragraph::new(&theme::TEXT_BOLD, "your trezor at").centered());
messages.add(Paragraph::new(&theme::TEXT_BOLD, "trezor.io/start").centered());
let mut frame =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
extern "C" fn screen_welcome_model() {
let mut frame = WelcomeScreen::new();
show(&mut frame);
}

@ -1,24 +1,19 @@
use crate::ui::{
component::text::TextStyle,
display::{Color, Font},
geometry::Offset,
};
use super::super::{
component::ButtonStyleSheet,
theme::{BG, BLACK, FG, WHITE},
};
pub const WHITE: Color = Color::white();
pub const BLACK: Color = Color::black();
pub const BLD_BG: Color = BLACK;
pub const BLD_FG: Color = WHITE;
pub fn bld_button_default() -> ButtonStyleSheet {
ButtonStyleSheet::new(BG, FG, false, false, None, Offset::zero())
}
pub fn bld_button_cancel() -> ButtonStyleSheet {
ButtonStyleSheet::new(FG, BG, false, false, None, Offset::zero())
}
pub const LOGO_EMPTY: &[u8] = include_res!("model_tr/res/logo_22_33_empty.toif");
pub const ICON_TRASH: &[u8] = include_res!("model_tr/res/trash.toif");
pub const ICON_ALERT: &[u8] = include_res!("model_tr/res/alert.toif");
pub const ICON_SPINNER: &[u8] = include_res!("model_tr/res/spinner.toif");
pub const ICON_REDO: &[u8] = include_res!("model_tr/res/redo.toif");
pub const ICON_EXIT: &[u8] = include_res!("model_tr/res/exit.toif");
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG);
pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG);
pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::BOLD, BLD_FG, BLD_BG, BLD_FG, BLD_FG);

@ -1,54 +0,0 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display::{self, Font},
geometry::{Point, Rect},
};
use super::theme::{BLD_BG, BLD_FG};
pub struct Title {
version: &'static str,
area: Rect,
}
impl Title {
pub fn new(version: &'static str) -> Self {
Self {
version,
area: Rect::zero(),
}
}
}
impl Component for Title {
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) {
display::text_top_left(
self.area.top_left(),
"BOOTLOADER",
Font::NORMAL,
BLD_FG,
BLD_BG,
);
display::text_top_left(
Point::new(self.area.top_left().x + 65, self.area.top_left().y),
self.version,
Font::NORMAL,
BLD_FG,
BLD_BG,
);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {}
}

@ -0,0 +1,57 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
constant::screen,
display::{self, Font},
geometry::{Offset, Rect},
model_tr::bootloader::theme::{BLD_BG, BLD_FG},
};
pub struct Welcome {
bg: Pad,
}
impl Welcome {
pub fn new() -> Self {
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
}
}
}
impl Component for Welcome {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(screen());
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
self.bg.paint();
display::text_center(
screen().top_center() + Offset::y(20),
"Get started with",
Font::NORMAL,
BLD_FG,
BLD_BG,
);
display::text_center(
screen().top_center() + Offset::y(35),
"your Trezor at",
Font::NORMAL,
BLD_FG,
BLD_BG,
);
display::text_center(
screen().top_center() + Offset::y(50),
"trezor.io/start",
Font::BOLD,
BLD_FG,
BLD_BG,
);
}
}

@ -0,0 +1,93 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
display,
display::Icon,
geometry::{Alignment::Center, Offset, Point, Rect, TOP_LEFT, TOP_RIGHT},
};
use crate::ui::model_tr::{
theme,
theme::{BG, FG},
};
pub struct ErrorScreen<T> {
bg: Pad,
show_icons: bool,
title: Child<Label<T>>,
message: Child<Label<T>>,
footer: Child<Label<T>>,
area: Rect,
}
impl<T: AsRef<str>> ErrorScreen<T> {
pub fn new(title: T, message: T, footer: T) -> Self {
let title = Label::new(title, Center, theme::TEXT_BOLD);
let message = Label::new(message, Center, theme::TEXT_NORMAL).vertically_aligned(Center);
let footer = Label::new(footer, Center, theme::TEXT_NORMAL).vertically_aligned(Center);
Self {
bg: Pad::with_background(BG).with_clear(),
show_icons: true,
title: Child::new(title),
message: Child::new(message),
footer: Child::new(footer),
area: Rect::zero(),
}
}
}
impl<T: AsRef<str>> Component for ErrorScreen<T> {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(screen());
let title_area = Rect::new(screen().top_left(), screen().top_right() + Offset::y(11));
self.title.place(title_area);
let text_width = self.title.inner().max_size().x;
if text_width > title_area.width() - 2 * 12 {
self.show_icons = false;
}
let message_area = Rect::new(
title_area.bottom_left(),
title_area.bottom_right() + Offset::y(32),
);
self.message.place(message_area);
let footer_area = Rect::new(
screen().bottom_left() + Offset::y(-20),
screen().bottom_right(),
);
self.footer.place(footer_area);
self.area = bounds;
screen()
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
self.bg.paint();
if self.show_icons {
Icon::new(theme::ICON_WARN_TITLE).draw(screen().top_left(), TOP_LEFT, FG, BG);
Icon::new(theme::ICON_WARN_TITLE).draw(screen().top_right(), TOP_RIGHT, FG, BG);
}
self.title.paint();
self.message.paint();
// divider line
let bar = Rect::from_center_and_size(
Point::new(self.area.center().x, 43),
Offset::new(self.area.width(), 1),
);
display::rect_fill(bar, FG);
self.footer.paint();
}
}

@ -1,18 +1,8 @@
#[cfg(feature = "micropython")]
use crate::micropython::buffer::StrBuffer;
use crate::ui::{
component::{
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
Component,
},
display::Icon,
geometry::LinearPlacement,
};
use super::{
component::ResultScreen,
constant,
theme::{BLACK, ICON_FAIL, TEXT_BOLD, TEXT_NORMAL, WHITE},
component::Component,
model_tr::{component::ErrorScreen, constant},
};
#[cfg(not(feature = "micropython"))]
@ -34,21 +24,7 @@ pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
let msg = unsafe { get_str(msg) };
let footer = unsafe { get_str(footer) };
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&TEXT_BOLD, title).centered());
messages.add(Paragraph::new(&TEXT_NORMAL, msg).centered());
let m_top =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut messages = ParagraphVecShort::new();
messages.add(Paragraph::new(&TEXT_BOLD, footer).centered());
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut frame = ResultScreen::new(WHITE, BLACK, Icon::new(ICON_FAIL), m_top, m_bottom, true);
let mut frame = ErrorScreen::new(title, msg, footer);
frame.place(constant::screen());
frame.paint();
}

@ -72,6 +72,7 @@ pub const ICON_SUCCESS: &[u8] = include_res!("model_tr/res/success.toif");
pub const ICON_TICK: &[u8] = include_res!("model_tr/res/tick.toif"); // 8*6
pub const ICON_TICK_FAT: &[u8] = include_res!("model_tr/res/tick_fat.toif"); // 8*6
pub const ICON_WARNING: &[u8] = include_res!("model_tr/res/warning.toif"); // 12*12
pub const ICON_WARN_TITLE: &[u8] = include_res!("model_tr/res/bld_header_warn.toif");
// checklist settings
pub const CHECKLIST_SPACING: i16 = 5;

@ -132,6 +132,21 @@ static inline void spi_send(const uint8_t *data, int len) {
}
}
void display_handle_init(void) {
spi_handle.Instance = OLED_SPI;
spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
spi_handle.Init.Direction = SPI_DIRECTION_2LINES;
spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;
spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;
spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
spi_handle.Init.CRCPolynomial = 7;
spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;
spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;
spi_handle.Init.NSS = SPI_NSS_HARD_OUTPUT;
spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;
spi_handle.Init.Mode = SPI_MODE_MASTER;
}
void display_init(void) {
OLED_DC_CLK_ENA();
OLED_CS_CLK_ENA();
@ -167,18 +182,7 @@ void display_init(void) {
GPIO_InitStructure.Pin = OLED_SPI_MOSI_PIN;
HAL_GPIO_Init(OLED_SPI_MOSI_PORT, &GPIO_InitStructure);
spi_handle.Instance = OLED_SPI;
spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
spi_handle.Init.Direction = SPI_DIRECTION_2LINES;
spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;
spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;
spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
spi_handle.Init.CRCPolynomial = 7;
spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;
spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;
spi_handle.Init.NSS = SPI_NSS_HARD_OUTPUT;
spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;
spi_handle.Init.Mode = SPI_MODE_MASTER;
display_handle_init();
if (HAL_OK != HAL_SPI_Init(&spi_handle)) {
// TODO: error
return;
@ -230,7 +234,7 @@ void display_init(void) {
display_refresh();
}
void display_reinit(void) { display_init(); }
void display_reinit(void) { display_handle_init(); }
static inline uint8_t reverse_byte(uint8_t b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;

@ -1 +1 @@
Subproject commit e63e8b868ef717ae84a694eaa6782899a396a1af
Subproject commit a973bbca756ca0c9219deefea2873d3cc774839d
Loading…
Cancel
Save