feat(core/rust): add bootloader design for T2B1

[no changelog]
pull/2961/head
tychovrahe 1 year ago committed by Jiří Musil
parent e928568339
commit 531511407b

@ -221,7 +221,7 @@ build_cross: ## build mpy-cross port
## clean commands:
clean: clean_boardloader clean_bootloader clean_bootloader_ci clean_prodtest clean_reflash clean_firmware clean_unix clean_cross ## clean all
clean: clean_boardloader clean_bootloader clean_bootloader_emu clean_bootloader_ci clean_prodtest clean_reflash clean_firmware clean_unix clean_cross ## clean all
rm -f ".sconsign.dblite"
clean_boardloader: ## clean boardloader build
@ -233,6 +233,9 @@ clean_bootloader: ## clean bootloader build
clean_bootloader_ci: ## clean bootloader_ci build
rm -rf $(BOOTLOADER_CI_BUILD_DIR)
clean_bootloader_emu: ## clean bootloader_emu build
rm -rf $(BOOTLOADER_EMU_BUILD_DIR)
clean_prodtest: ## clean prodtest build
rm -rf $(PRODTEST_BUILD_DIR)

@ -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

@ -29,9 +29,9 @@ SOURCE_MOD = []
if TREZOR_MODEL in ('1', '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
@ -165,7 +165,7 @@ env.Replace(
'BOOTLOADER',
'TREZOR_EMULATOR',
'HW_MODEL=' + MODEL_AS_NUMBER,
'HW_REVISION=0',
'HW_REVISION=' + ('6' if TREZOR_MODEL in ('R',) else '0'),
'TREZOR_MODEL_'+TREZOR_MODEL,
'TREZOR_BOARD=\\"board-unix.h\\"',
'PB_FIELD_16BIT',
@ -225,7 +225,7 @@ def cargo_build():
cargo_opts = [
f'--target={RUST_TARGET}',
f'--target-dir=../../build/bootloader_emu/rust',
'--target-dir=../../build/bootloader_emu/rust',
'--no-default-features',
'--features ' + ','.join(features),
'-Z build-std=core',
@ -239,9 +239,9 @@ rust = env.Command(
source='',
action=cargo_build(), )
env.Append(LINKFLAGS=f' -L{RUST_LIBDIR}')
env.Append(LINKFLAGS=f' -l{RUST_LIB}')
env.Append(LINKFLAGS=f' -lm')
env.Append(LINKFLAGS=f'-L{RUST_LIBDIR}')
env.Append(LINKFLAGS=f'-l{RUST_LIB}')
env.Append(LINKFLAGS='-lm')
#
# Program objects

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

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

@ -25,5 +25,4 @@ uint32_t screen_install_fail(void);
void screen_welcome_model(void);
void screen_welcome(void);
void screen_boot_empty(bool fading);
void display_image(int16_t x, int16_t y, const uint8_t* data, uint32_t datalen);

@ -1,22 +1,20 @@
use crate::ui::{
component::{
text::paragraphs::{ParagraphVecShort, Paragraphs},
Child, Component, Event, EventCtx, Pad,
},
constant::screen,
display::{Color, Icon},
geometry::{Point, Rect, CENTER},
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
display::{self, Color, Font},
geometry::{Point, Rect},
};
use super::{
super::{
component::Button,
constant::{HEIGHT, WIDTH},
theme::WHITE,
component::{ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos},
theme::{BUTTON_HEIGHT, TITLE_AREA_HEIGHT},
},
theme::WHITE,
ReturnToC,
};
const ALERT_AREA_START: i16 = 39;
#[derive(Copy, Clone)]
pub enum ConfirmMsg {
Cancel = 1,
@ -29,94 +27,176 @@ 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,
title: &'static str,
message: Child<Label<&'a str>>,
alert: Option<Label<&'a str>>,
info_title: Option<&'static str>,
info_text: Option<Label<&'a str>>,
button_text: &'static str,
buttons: ButtonController<&'static str>,
/// Whether we are on the info screen (optional extra screen)
showing_info_screen: 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,
title: &'static str,
message: Label<&'a str>,
alert: Option<Label<&'a str>>,
button_text: &'static str,
) -> Self {
let mut instance = Self {
bg: Pad::with_background(bg_color),
let btn_layout = Self::get_button_layout_general(false, button_text, false);
Self {
bg: Pad::with_background(bg_color).with_clear(),
bg_color,
icon,
title,
message: Child::new(message),
left: Child::new(left),
right: Child::new(right),
confirm_left,
};
instance.bg.clear();
instance
alert,
info_title: None,
info_text: None,
button_text,
buttons: ButtonController::new(btn_layout),
showing_info_screen: false,
}
}
/// Adding optional info screen
pub fn with_info_screen(mut self, info_title: &'static str, info_text: Label<&'a str>) -> Self {
self.info_title = Some(info_title);
self.info_text = Some(info_text);
self.buttons = ButtonController::new(self.get_button_layout());
self
}
fn has_info_screen(&self) -> bool {
self.info_title.is_some()
}
fn get_button_layout(&self) -> ButtonLayout<&'static str> {
Self::get_button_layout_general(
self.showing_info_screen,
self.button_text,
self.has_info_screen(),
)
}
/// Not relying on self here, to call it in constructor.
fn get_button_layout_general(
showing_info_screen: bool,
button_text: &'static str,
has_info_screen: bool,
) -> ButtonLayout<&'static str> {
if showing_info_screen {
ButtonLayout::arrow_none_none()
} else if has_info_screen {
ButtonLayout::cancel_armed_text(button_text, "i")
} else {
ButtonLayout::cancel_none_text(button_text)
}
}
/// Reflecting the current page in the buttons.
fn update_buttons(&mut self) {
let btn_layout = self.get_button_layout();
self.buttons.set(btn_layout);
}
fn update_everything(&mut self, ctx: &mut EventCtx) {
self.bg.clear();
self.update_buttons();
self.info_text.request_complete_repaint(ctx);
self.message.request_complete_repaint(ctx);
self.alert.request_complete_repaint(ctx);
self.buttons.request_complete_repaint(ctx);
self.request_complete_repaint(ctx);
}
}
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);
// Divide the screen into areas
let (_title_area, minus_title) = bounds.split_top(TITLE_AREA_HEIGHT);
let (between_title_and_buttons, button_area) = minus_title.split_bottom(BUTTON_HEIGHT);
// Texts for the main screen
let (message_area, alert_area) = if self.alert.is_some() {
between_title_and_buttons.split_top(ALERT_AREA_START - TITLE_AREA_HEIGHT)
} else {
(between_title_and_buttons, Rect::zero())
};
self.message.place(message_area);
self.alert.place(alert_area);
// Text for the info screen
self.info_text.place(between_title_and_buttons);
let button_area = bounds.split_bottom(12).1;
self.left.place(button_area);
self.right.place(button_area);
self.buttons.place(button_area);
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
// TODO: to be fixed in bootloader branch
// 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)
// };
// };
None
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let msg = self.buttons.event(ctx, event);
if self.showing_info_screen {
// Showing the info screen currently - going back with the left button
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) = msg {
self.showing_info_screen = false;
self.update_everything(ctx);
};
None
} else if self.has_info_screen() {
// Being on the "main" screen but with an info screen available on the right
match msg {
Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) => Some(ConfirmMsg::Cancel),
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle)) => {
Some(ConfirmMsg::Confirm)
}
Some(ButtonControllerMsg::Triggered(ButtonPos::Right)) => {
self.showing_info_screen = true;
self.update_everything(ctx);
None
}
_ => None,
}
} else {
// There is just one main screen without info screen
match msg {
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,
);
}
let display_top_left = |text: &str| {
display::text_top_left(Point::zero(), text, Font::BOLD, WHITE, self.bg_color);
};
self.message.paint();
self.left.paint();
self.right.paint();
// We are either on the info screen or on the "main" screen
if self.showing_info_screen {
display_top_left(unwrap!(self.info_title));
self.info_text.paint();
} else {
display_top_left(self.title);
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,47 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::{self, Font},
geometry::{Offset, Rect},
};
use super::theme::{BLD_BG, BLD_FG};
pub struct Connect {
bg: Pad,
message: &'static str,
}
impl Connect {
pub fn new(message: &'static str) -> Self {
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
message,
}
}
}
impl Component for Connect {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
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(
self.bg.area.center() + Offset::y(font.text_height() / 2),
self.message,
font,
BLD_FG,
BLD_BG,
);
}
}

@ -1,26 +1,25 @@
use crate::ui::{
component::{
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
Child, Component, Event, EventCtx, Pad,
},
geometry::{LinearPlacement, Point, Rect},
component::{Child, Component, Event, EventCtx, Label, Pad},
geometry::{Alignment, Rect, TOP_LEFT, TOP_RIGHT},
};
use super::super::{
bootloader::{
theme::{bld_button_default, BLD_BG, TEXT_NORMAL},
title::Title,
ReturnToC,
use super::{
super::{
component::{ButtonController, ButtonControllerMsg::Triggered, ButtonLayout, ButtonPos},
theme::{BUTTON_HEIGHT, ICON_WARN_TITLE, TITLE_AREA_HEIGHT},
},
component::{Button, ButtonPos},
constant::{HEIGHT, WIDTH},
theme::{BLD_BG, BLD_FG, TEXT_NORMAL},
ReturnToC,
};
const LEFT_BUTTON_TEXT: &str = "INSTALL FW";
const RIGHT_BUTTON_TEXT: &str = "MENU";
#[repr(u32)]
#[derive(Copy, Clone)]
pub enum IntroMsg {
Menu = 1,
Host = 2,
GoToMenu = 1,
InstallFirmware = 2,
}
impl ReturnToC for IntroMsg {
fn return_to_c(self) -> u32 {
@ -28,87 +27,75 @@ 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());
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),
};
instance.bg.clear();
instance
impl<'a> Intro<'a> {
pub fn new(title: &'a str, content: &'a str) -> Self {
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
title: Child::new(
Label::new(title, Alignment::Center, TEXT_NORMAL)
.vertically_aligned(Alignment::Center),
),
buttons: Child::new(ButtonController::new(ButtonLayout::text_none_text(
LEFT_BUTTON_TEXT,
RIGHT_BUTTON_TEXT,
))),
text: Child::new(
Label::new(content, Alignment::Start, TEXT_NORMAL)
.vertically_aligned(Alignment::Center),
),
}
}
}
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.title
.place(Rect::new(Point::new(10, 0), Point::new(128, 8)));
self.bg.place(bounds);
let button_area = bounds.split_bottom(12).1;
self.host.place(button_area);
self.menu.place(button_area);
// Title on top, buttons on bottom, text in between
let (title_area, text_and_buttons_area) = bounds.split_top(TITLE_AREA_HEIGHT);
let (text_area, buttons_area) = text_and_buttons_area.split_bottom(BUTTON_HEIGHT);
self.text
.place(Rect::new(Point::new(10, 20), Point::new(118, 50)));
self.title.place(title_area);
self.buttons.place(buttons_area);
self.text.place(text_area);
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
// TODO: to be fixed in bootloader branch
// if let Some(Clicked) = self.menu.event(ctx, event) {
// return Some(Self::Msg::Menu);
// };
// if let Some(Clicked) = self.host.event(ctx, event) {
// return Some(Self::Msg::Host);
// };
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let msg = self.buttons.event(ctx, event);
if let Some(Triggered(ButtonPos::Left)) = msg {
return Some(Self::Msg::InstallFirmware);
};
if let Some(Triggered(ButtonPos::Right)) = msg {
return Some(Self::Msg::GoToMenu);
};
None
}
fn paint(&mut self) {
self.bg.paint();
self.title.paint();
let area = self.bg.area;
ICON_WARN_TITLE.draw(area.top_left(), TOP_LEFT, BLD_FG, BLD_BG);
ICON_WARN_TITLE.draw(area.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,152 @@
#[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, Event, EventCtx, Pad},
constant::screen,
display,
display::{Font, Icon},
geometry::{Offset, Point, Rect, CENTER},
};
use super::super::{
bootloader::{theme::BLD_BG, title::Title},
constant::{HEIGHT, WIDTH},
use super::{
super::component::{Choice, ChoiceFactory, ChoicePage},
theme::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH},
ReturnToC,
};
#[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;
const SCREEN_CENTER: Point = screen().center();
pub struct MenuChoice {
first_line: &'static str,
second_line: &'static str,
icon: Icon,
}
impl MenuChoice {
pub fn new(first_line: &'static str, second_line: &'static str, icon: Icon) -> Self {
Self {
first_line,
second_line,
icon,
}
}
}
impl Choice<&'static str> for MenuChoice {
fn paint_center(&self, _area: Rect, _inverse: bool) {
// Icon on top and two lines of text below
self.icon
.draw(SCREEN_CENTER + Offset::y(-20), CENTER, BLD_FG, BLD_BG);
display::text_center(SCREEN_CENTER, self.first_line, Font::NORMAL, BLD_FG, BLD_BG);
display::text_center(
SCREEN_CENTER + Offset::y(10),
self.second_line,
Font::NORMAL,
BLD_FG,
BLD_BG,
);
}
}
#[cfg(feature = "ui_debug")]
impl Trace for MenuChoice {
fn trace(&self, t: &mut dyn Tracer) {
t.component("MenuChoice");
}
}
pub struct MenuChoiceFactory;
impl MenuChoiceFactory {
const CHOICES: [(&'static str, &'static str, Icon); 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 Action = MenuMsg;
type Item = MenuChoice;
fn count(&self) -> usize {
CHOICE_LENGTH
}
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
let choice_item = MenuChoice::new(
Self::CHOICES[choice_index].0,
Self::CHOICES[choice_index].1,
Self::CHOICES[choice_index].2,
);
let action = match choice_index {
0 => MenuMsg::FactoryReset,
1 => MenuMsg::Reboot,
2 => MenuMsg::Close,
_ => unreachable!(),
};
(choice_item, action)
}
}
pub struct Menu {
bg: Pad,
title: Child<Title>,
pad: Pad,
choice_page: Child<ChoicePage<MenuChoiceFactory, &'static str, MenuMsg>>,
}
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 {
pad: Pad::with_background(BLD_BG).with_clear(),
choice_page: Child::new(
ChoicePage::new(choices)
.with_carousel(true)
.with_only_one_item(true),
),
}
}
}
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.pad.place(bounds);
self.choice_page.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> {
self.choice_page.event(ctx, event)
}
fn paint(&mut self) {
self.bg.paint();
self.title.paint();
self.pad.paint();
self.choice_page.paint();
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.choice_page.bounds(sink)
}
}

@ -1,42 +1,38 @@
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,
component::{Component, Event, EventCtx, Label, LineBreaking::BreakWordsNoHyphen, Never},
constant::SCREEN,
display::{self, Color, Font, Icon},
event::ButtonEvent,
geometry::{Alignment, Alignment::Center, Offset, Rect, TOP_CENTER},
util::{from_c_array, from_c_str},
},
};
use heapless::String;
use super::{
component::{ResultScreen, WelcomeScreen},
theme::ICON_SUCCESS,
};
mod confirm;
mod connect;
mod intro;
mod menu;
mod theme;
mod title;
use crate::ui::{
component::{
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
Event, EventCtx,
},
constant::{screen, WIDTH},
display::{Color, Icon, TextOverlay},
event::ButtonEvent,
geometry::{LinearPlacement, Offset, Rect, CENTER},
util::{from_c_array, from_c_str},
};
use super::{
component::{Button, ButtonPos, ResultScreen},
constant,
theme::{ICON_FAIL, ICON_SUCCESS, LOGO_EMPTY},
};
mod welcome;
use confirm::Confirm;
use connect::Connect;
use intro::Intro;
use menu::Menu;
use theme::{bld_button_cancel, bld_button_default, BLD_BG, BLD_FG};
use theme::{BLD_BG, BLD_FG, ICON_ALERT, ICON_SPINNER, LOGO_EMPTY};
use welcome::Welcome;
const SCREEN_ADJ: Rect = screen().split_top(64).0;
pub type BootloaderString = String<128>;
pub trait ReturnToC {
fn return_to_c(self) -> u32;
@ -76,8 +72,9 @@ where
F: Component,
F::Msg: ReturnToC,
{
frame.place(SCREEN_ADJ);
frame.place(SCREEN);
frame.paint();
display::refresh();
while button_eval().is_some() {}
@ -92,6 +89,7 @@ where
}
frame.paint();
display::refresh();
}
}
}
@ -100,7 +98,7 @@ fn show<F>(frame: &mut F)
where
F: Component,
{
frame.place(SCREEN_ADJ);
frame.place(SCREEN);
display::sync();
frame.paint();
display::refresh();
@ -111,81 +109,74 @@ 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 mut frame = Confirm::new(
BLD_BG,
icon,
Paragraphs::new(message).with_placement(LinearPlacement::vertical().align_at_center()),
left,
right,
false,
);
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, title_str, message, alert, "INSTALL")
.with_info_screen("FW FINGERPRINT", fingerprint);
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 message = 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", message, 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]
@ -199,196 +190,184 @@ 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);
display::rect_fill(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,
0,
fill_to as _,
((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,
);
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_SUCCESS, BLD_FG)),
);
}
#[no_mangle]
extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) {
screen_progress(
"WIPING DEVICE",
"Resetting",
"Trezor",
progress,
initialize,
theme::BLD_FG,
BLD_FG,
BLD_BG,
None,
)
Some((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_SUCCESS, m_top, m_bottom, true);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, true);
show(&mut frame);
}
#[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(
"Please reconnect\nthe device",
Alignment::Center,
theme::TEXT_NORMAL,
)
.vertically_aligned(Alignment::Center);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_FAIL, m_top, m_bottom, true);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, 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(SCREEN, BLD_BG);
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 m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_FAIL, m_top, m_bottom, true);
show(&mut frame);
}
let title = Label::new("Install failed", Alignment::Center, theme::TEXT_BOLD)
.vertically_aligned(Alignment::Center);
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 mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SUCCESS, m_top, m_bottom, complete_draw);
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());
let m_bottom =
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
let content = Label::new(
"Please reconnect\nthe device",
Alignment::Center,
theme::TEXT_NORMAL,
)
.vertically_aligned(Alignment::Center);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SUCCESS, m_top, m_bottom, complete_draw);
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true);
show(&mut frame);
}
#[no_mangle]
extern "C" fn screen_install_success(
reboot_msg: *const cty::c_char,
initial_setup: bool,
_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)
}
let title = Label::new("Firmware installed", Alignment::Center, theme::TEXT_BOLD)
.vertically_aligned(Alignment::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_SPINNER, title, content, complete_draw);
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());
let mut frame = Welcome::new();
show(&mut frame);
}
#[no_mangle]
extern "C" fn screen_welcome_model() {
let mut frame = WelcomeScreen::new();
show(&mut frame);
}

@ -1,24 +1,21 @@
use crate::ui::{
component::text::TextStyle,
display::{Color, Font},
geometry::Offset,
display::{toif::Icon, Color, Font},
};
use super::super::{
component::ButtonStyleSheet,
theme::{BG, BLACK, FG, WHITE},
};
pub use super::super::theme::{BLACK, WHITE};
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())
}
include_icon!(LOGO_EMPTY, "model_tr/res/logo_22_33_empty.toif");
include_icon!(ICON_TRASH, "model_tr/res/trash.toif");
include_icon!(ICON_ALERT, "model_tr/res/alert.toif");
include_icon!(ICON_SPINNER, "model_tr/res/spinner.toif");
include_icon!(ICON_REDO, "model_tr/res/redo.toif");
include_icon!(ICON_EXIT, "model_tr/res/exit.toif");
include_icon!(ICON_INFO, "model_tr/res/info.toif");
include_icon!(ICON_INFO_INVERTED, "model_tr/res/info_inverted.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,60 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::{self, Font},
geometry::{Offset, Rect},
};
use super::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(bounds);
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
self.bg.paint();
let top_center = self.bg.area.top_center();
display::text_center(
top_center + Offset::y(24),
"Get started with",
Font::NORMAL,
BLD_FG,
BLD_BG,
);
display::text_center(
top_center + Offset::y(32),
"your Trezor at",
Font::NORMAL,
BLD_FG,
BLD_BG,
);
display::text_center(
top_center + Offset::y(48),
"trezor.io/start",
Font::BOLD,
BLD_FG,
BLD_BG,
);
}
}

@ -0,0 +1,4 @@
pub enum CancelConfirmMsg {
Cancelled,
Confirmed,
}

@ -1,5 +1,5 @@
use crate::{
strutil::{ShortString, StringType},
strutil::StringType,
time::Duration,
ui::{
component::{Component, Event, EventCtx, Never},
@ -124,7 +124,18 @@ where
};
let content_width = match &self.content {
ButtonContent::Text(text) => style.font.visible_text_width(text.as_ref()),
ButtonContent::Icon(icon) => icon.toif.width() - 1,
ButtonContent::Icon(icon, icon_pressed) => {
let width = if self.state == State::Pressed {
if let Some(icon_pressed) = icon_pressed {
icon_pressed.toif.width()
} else {
icon.toif.width()
}
} else {
icon.toif.width()
};
width - 1
}
};
content_width + 2 * outline
};
@ -241,7 +252,8 @@ where
match &self.content {
ButtonContent::Text(text) => {
display::text_left(
self.get_text_baseline(style),
self.get_text_baseline(style)
- Offset::x(style.font.start_x_bearing(text.as_ref())),
text.as_ref(),
style.font,
text_color,
@ -424,12 +436,12 @@ impl<T> ButtonDetails<T> {
/// Down arrow to signal paginating forward. Takes half the screen's width
pub fn down_arrow_icon_wide() -> Self {
Self::icon(theme::ICON_ARROW_DOWN).fixed_width(HALF_SCREEN_BUTTON_WIDTH)
Self::icon(theme::ICON_ARROW_DOWN).with_fixed_width(HALF_SCREEN_BUTTON_WIDTH)
}
/// Up arrow to signal paginating back. Takes half the screen's width
pub fn up_arrow_icon_wide() -> Self {
Self::icon(theme::ICON_ARROW_UP).fixed_width(HALF_SCREEN_BUTTON_WIDTH)
Self::icon(theme::ICON_ARROW_UP).with_fixed_width(HALF_SCREEN_BUTTON_WIDTH)
}
/// Icon of a bin to signal deleting.
@ -471,8 +483,8 @@ impl<T> ButtonDetails<T> {
self
}
/// Width of the button.
pub fn fixed_width(mut self, width: i16) -> Self {
/// Specifying the width of the button.
pub fn with_fixed_width(mut self, width: i16) -> Self {
self.fixed_width = Some(width);
self
}
@ -880,6 +892,9 @@ impl ButtonActions {
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
use crate::strutil::ShortString;
#[cfg(feature = "ui_debug")]
impl<T: StringType> crate::trace::Trace for Button<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {

@ -77,7 +77,7 @@ where
fn paint_left(&self) {
let baseline = Point::new(self.pad.area.x0, self.y_baseline());
common::display(baseline, &self.text, self.font);
common::display_left(baseline, &self.text, self.font);
}
fn paint_center(&self) {
@ -108,7 +108,7 @@ where
};
let baseline = Point::new(self.pad.area.x0 + x_offset, self.y_baseline());
common::display(baseline, &text_to_display, self.font);
common::display_left(baseline, &text_to_display, self.font);
}
}

@ -6,7 +6,7 @@ use crate::ui::{
use super::theme;
/// Display white text on black background
pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
pub fn display_left<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
display::text_left(baseline, text.as_ref(), font, theme::FG, theme::BG);
}

@ -0,0 +1,96 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
display,
geometry::{Alignment::Center, Offset, Point, Rect, TOP_LEFT, TOP_RIGHT},
};
use super::super::{
theme,
theme::{BG, FG, TITLE_AREA_HEIGHT},
};
const FOOTER_AREA_HEIGHT: i16 = 20;
const MESSAGE_AREA_HEIGHT: i16 = 32;
const DIVIDER_POSITION: i16 = 43;
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 * TITLE_AREA_HEIGHT {
self.show_icons = false;
}
let message_area = Rect::new(
title_area.bottom_left(),
title_area.bottom_right() + Offset::y(MESSAGE_AREA_HEIGHT),
);
self.message.place(message_area);
let footer_area = Rect::new(
screen().bottom_left() + Offset::y(-FOOTER_AREA_HEIGHT),
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 {
theme::ICON_WARN_TITLE.draw(screen().top_left(), TOP_LEFT, FG, BG);
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, DIVIDER_POSITION),
Offset::new(self.area.width(), 1),
);
display::rect_fill(bar, FG);
self.footer.paint();
}
}

@ -1,7 +1,7 @@
use crate::{
strutil::StringType,
ui::{
component::{base::Component, text::layout::LayoutFit, FormattedText, Paginate},
component::{base::Component, FormattedText, Paginate},
geometry::Rect,
},
};
@ -200,6 +200,9 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
use crate::ui::component::text::layout::LayoutFit;
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Page<T>
where

@ -6,20 +6,23 @@ use crate::{
},
};
use super::{
super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos},
choice_item::ChoiceItem,
};
use super::super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos};
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
const DEFAULT_Y_BASELINE: i16 = 20;
pub trait Choice<T: StringType> {
// Only `paint_center` is required, the rest is optional
// and therefore has a default implementation.
fn paint_center(&self, area: Rect, inverse: bool);
fn width_center(&self) -> i16;
fn width_center(&self) -> i16 {
0
}
fn paint_side(&self, area: Rect);
fn width_side(&self) -> i16;
fn paint_side(&self, _area: Rect) {}
fn width_side(&self) -> i16 {
0
}
fn btn_layout(&self) -> ButtonLayout<T> {
ButtonLayout::default_three_icons()
@ -37,9 +40,10 @@ pub trait Choice<T: StringType> {
/// This way, no more than one item is stored in memory at any time.
pub trait ChoiceFactory<T: StringType> {
type Action;
type Item: Choice<T>;
fn count(&self) -> usize;
fn get(&self, index: usize) -> (ChoiceItem<T>, Self::Action);
fn get(&self, index: usize) -> (Self::Item, Self::Action);
}
/// General component displaying a set of items on the screen
@ -223,7 +227,7 @@ where
}
/// Getting the choice on the current index
pub fn get_current_choice(&self) -> (ChoiceItem<T>, A) {
pub fn get_current_choice(&self) -> (<F as ChoiceFactory<T>>::Item, A) {
self.choices.get(self.page_counter)
}
@ -436,6 +440,7 @@ impl<F, T, A> crate::trace::Trace for ChoicePage<F, T, A>
where
F: ChoiceFactory<T, Action = A>,
T: StringType + Clone,
<F as ChoiceFactory<T>>::Item: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ChoicePage");

@ -20,14 +20,15 @@ impl ChoiceFactoryNumberInput {
}
}
impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryNumberInput {
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryNumberInput {
type Action = u32;
type Item = ChoiceItem<T>;
fn count(&self) -> usize {
(self.max - self.min + 1) as usize
}
fn get(&self, choice_index: usize) -> (ChoiceItem<T>, Self::Action) {
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
let num = self.min + choice_index as u32;
let text: String<10> = String::from(num);
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
@ -47,7 +48,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryNumberInput {
/// Simple wrapper around `ChoicePage` that allows for
/// inputting a list of values and receiving the chosen one.
pub struct NumberInput<T: StringType> {
pub struct NumberInput<T: StringType + Clone> {
choice_page: ChoicePage<ChoiceFactoryNumberInput, T, u32>,
min: u32,
}

@ -4,7 +4,6 @@ use crate::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
display::Icon,
geometry::Rect,
model_tr::layout::CancelConfirmMsg,
util::char_to_string,
},
};
@ -12,7 +11,8 @@ use crate::{
use heapless::String;
use super::super::{
theme, ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
theme, ButtonDetails, ButtonLayout, CancelConfirmMsg, ChangingTextLine, ChoiceFactory,
ChoiceItem, ChoicePage,
};
/// Defines the choices currently available on the screen
@ -210,8 +210,9 @@ impl ChoiceFactoryPassphrase {
}
}
impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryPassphrase {
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPassphrase {
type Action = PassphraseAction;
type Item = ChoiceItem<T>;
fn count(&self) -> usize {
let length = get_category_length(&self.current_category);
@ -221,7 +222,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryPassphrase {
_ => length + 1,
}
}
fn get(&self, choice_index: usize) -> (ChoiceItem<T>, Self::Action) {
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
match self.current_category {
ChoiceCategory::Menu => self.get_menu_item(choice_index),
_ => self.get_character_item(choice_index),
@ -230,7 +231,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryPassphrase {
}
/// Component for entering a passphrase.
pub struct PassphraseEntry<T: StringType> {
pub struct PassphraseEntry<T: StringType + Clone> {
choice_page: ChoicePage<ChoiceFactoryPassphrase, T, PassphraseAction>,
passphrase_dots: Child<ChangingTextLine<String<MAX_PASSPHRASE_LENGTH>>>,
show_plain_passphrase: bool,

@ -5,12 +5,12 @@ use crate::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
display::Icon,
geometry::Rect,
model_tr::layout::CancelConfirmMsg,
},
};
use super::super::{
theme, ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
theme, ButtonDetails, ButtonLayout, CancelConfirmMsg, ChangingTextLine, ChoiceFactory,
ChoiceItem, ChoicePage,
};
use heapless::String;
@ -44,10 +44,11 @@ const CHOICES: [(&str, PinAction, Option<Icon>); CHOICE_LENGTH] = [
struct ChoiceFactoryPIN;
impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryPIN {
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
type Action = PinAction;
type Item = ChoiceItem<T>;
fn get(&self, choice_index: usize) -> (ChoiceItem<T>, Self::Action) {
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
let (choice_str, action, icon) = CHOICES[choice_index];
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
@ -72,7 +73,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryPIN {
}
/// Component for entering a PIN.
pub struct PinEntry<T: StringType> {
pub struct PinEntry<T: StringType + Clone> {
choice_page: ChoicePage<ChoiceFactoryPIN, T, PinAction>,
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
subprompt_line: Child<ChangingTextLine<T>>,

@ -28,14 +28,15 @@ impl<T: StringType> ChoiceFactorySimple<T> {
}
}
impl<T: StringType> ChoiceFactory<T> for ChoiceFactorySimple<T> {
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactorySimple<T> {
type Action = usize;
type Item = ChoiceItem<T>;
fn count(&self) -> usize {
self.choices.len()
}
fn get(&self, choice_index: usize) -> (ChoiceItem<T>, Self::Action) {
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
let text = &self.choices[choice_index];
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
@ -58,7 +59,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactorySimple<T> {
/// inputting a list of values and receiving the chosen one.
pub struct SimpleChoice<T>
where
T: StringType,
T: StringType + Clone,
{
choice_page: ChoicePage<ChoiceFactorySimple<T>, T, usize>,
pub return_index: bool,

@ -58,8 +58,9 @@ impl ChoiceFactoryWordlist {
}
}
impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryWordlist {
impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
type Action = WordlistAction;
type Item = ChoiceItem<T>;
fn count(&self) -> usize {
// Accounting for the DELETE option (+1)
@ -70,7 +71,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryWordlist {
}
}
fn get(&self, choice_index: usize) -> (ChoiceItem<T>, Self::Action) {
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
// Putting DELETE as the first option in both cases
// (is a requirement for WORDS, doing it for LETTERS as well to unite it)
if choice_index == DELETE_INDEX {
@ -101,7 +102,7 @@ impl<T: StringType> ChoiceFactory<T> for ChoiceFactoryWordlist {
}
/// Component for entering a mnemonic from a wordlist - BIP39 or SLIP39.
pub struct WordlistEntry<T: StringType> {
pub struct WordlistEntry<T: StringType + Clone> {
choice_page: ChoicePage<ChoiceFactoryWordlist, T, WordlistAction>,
chosen_letters: Child<ChangingTextLine<String<{ MAX_WORD_LENGTH + 1 }>>>,
textbox: TextBox<MAX_WORD_LENGTH>,

@ -1,18 +1,21 @@
mod button;
mod button_controller;
mod common;
mod error;
mod hold_to_confirm;
mod input_methods;
mod loader;
mod result;
mod welcome_screen;
use super::{constant, theme};
use super::{common_messages, constant, theme};
pub use button::{
Button, ButtonAction, ButtonActions, ButtonContent, ButtonDetails, ButtonLayout, ButtonPos,
ButtonStyle, ButtonStyleSheet,
};
pub use button_controller::{ButtonController, ButtonControllerMsg};
pub use common_messages::CancelConfirmMsg;
pub use error::ErrorScreen;
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
pub use input_methods::{
choice::{Choice, ChoiceFactory, ChoicePage},
@ -28,6 +31,7 @@ mod coinjoin_progress;
mod flow;
mod flow_pages;
mod frame;
#[cfg(feature = "micropython")]
mod homescreen;
mod page;
mod progress;
@ -45,6 +49,7 @@ pub use coinjoin_progress::CoinJoinProgress;
pub use flow::Flow;
pub use flow_pages::{FlowPages, Page};
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
#[cfg(feature = "micropython")]
pub use homescreen::{Homescreen, Lockscreen};
pub use input_methods::{
number_input::NumberInput,

@ -1,33 +1,31 @@
use crate::{
strutil::StringType,
ui::{
component::{
text::paragraphs::{ParagraphVecShort, Paragraphs},
Child, Component, Event, EventCtx, Never, Pad,
},
constant::{screen, HEIGHT, WIDTH},
display::{Color, Icon},
geometry::{Offset, Point, Rect, CENTER},
},
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::{screen, HEIGHT, WIDTH},
display::{Color, Icon},
geometry::{Offset, Point, Rect, CENTER},
};
pub struct ResultScreen<T> {
const MESSAGE_AREA_START: i16 = 26;
const FOOTER_AREA_START: i16 = 40;
const ICON_TOP: i16 = 12;
pub struct ResultScreen<'a> {
bg: Pad,
small_pad: Pad,
fg_color: Color,
bg_color: Color,
icon: Icon,
message_top: Child<Paragraphs<ParagraphVecShort<T>>>,
message_bottom: Child<Paragraphs<ParagraphVecShort<T>>>,
message_top: Child<Label<&'static str>>,
message_bottom: Child<Label<&'a str>>,
}
impl<T: StringType> ResultScreen<T> {
impl<'a> ResultScreen<'a> {
pub fn new(
fg_color: Color,
bg_color: Color,
icon: Icon,
message_top: Paragraphs<ParagraphVecShort<T>>,
message_bottom: Paragraphs<ParagraphVecShort<T>>,
title: Label<&'static str>,
content: Label<&'a str>,
complete_draw: bool,
) -> Self {
let mut instance = Self {
@ -36,8 +34,8 @@ impl<T: StringType> ResultScreen<T> {
fg_color,
bg_color,
icon,
message_top: Child::new(message_top),
message_bottom: Child::new(message_bottom),
message_top: Child::new(title),
message_bottom: Child::new(content),
};
if complete_draw {
@ -49,17 +47,18 @@ impl<T: StringType> ResultScreen<T> {
}
}
impl<T: StringType> Component for ResultScreen<T> {
impl<'a> Component for ResultScreen<'a> {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg
.place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT)));
self.bg.place(bounds);
self.message_top
.place(Rect::new(Point::new(0, 26), Point::new(WIDTH, 40)));
self.message_top.place(Rect::new(
Point::new(0, MESSAGE_AREA_START),
Point::new(WIDTH, FOOTER_AREA_START),
));
let bottom_area = Rect::new(Point::new(0, 40), Point::new(WIDTH, HEIGHT));
let bottom_area = Rect::new(Point::new(0, FOOTER_AREA_START), Point::new(WIDTH, HEIGHT));
self.small_pad.place(bottom_area);
self.message_bottom.place(bottom_area);
@ -76,7 +75,7 @@ impl<T: StringType> Component for ResultScreen<T> {
self.small_pad.paint();
self.icon.draw(
screen().top_center() + Offset::y(12),
screen().top_center() + Offset::y(ICON_TOP),
CENTER,
self.fg_color,
self.bg_color,

@ -12,7 +12,7 @@ use crate::{
use heapless::{String, Vec};
use super::{common::display, scrollbar::SCROLLBAR_SPACE, theme, title::Title, ScrollBar};
use super::{common::display_left, scrollbar::SCROLLBAR_SPACE, theme, title::Title, ScrollBar};
const WORDS_PER_PAGE: usize = 3;
const EXTRA_LINE_HEIGHT: i16 = 2;
@ -140,8 +140,8 @@ where
}
let word = &self.share_words[index];
let baseline = self.area.top_left() + Offset::new(NUMBER_X_OFFSET, y_offset);
display(baseline, &inttostr!(index as u8 + 1), NUMBER_FONT);
display(baseline + Offset::x(NUMBER_WORD_OFFSET), &word, WORD_FONT);
display_left(baseline, &inttostr!(index as u8 + 1), NUMBER_FONT);
display_left(baseline + Offset::x(NUMBER_WORD_OFFSET), &word, WORD_FONT);
}
}
}

@ -14,7 +14,9 @@ pub const MODEL_NAME: &str = "Trezor Model R";
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)
}
pub const SIZE: Offset = size();
pub const fn screen() -> Rect {
Rect::from_top_left_and_size(Point::zero(), size())
Rect::from_top_left_and_size(Point::zero(), SIZE)
}
pub const SCREEN: Rect = screen();

@ -45,7 +45,7 @@ use crate::{
use super::{
component::{
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage,
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, CancelConfirmMsg,
CancelInfoConfirmMsg, CoinJoinProgress, Flow, FlowPages, Frame, Homescreen, Lockscreen,
NumberInput, Page, PassphraseEntry, PinEntry, Progress, ScrollableContent, ScrollableFrame,
ShareWords, ShowMore, SimpleChoice, WelcomeScreen, WordlistEntry, WordlistType,
@ -53,11 +53,6 @@ use super::{
constant, theme,
};
pub enum CancelConfirmMsg {
Cancelled,
Confirmed,
}
impl From<CancelConfirmMsg> for Obj {
fn from(value: CancelConfirmMsg) -> Self {
match value {

@ -1,5 +1,6 @@
#[cfg(feature = "bootloader")]
pub mod bootloader;
pub mod common_messages;
pub mod component;
pub mod constant;
#[cfg(feature = "micropython")]

@ -1,18 +1,8 @@
#[cfg(feature = "micropython")]
use crate::micropython::buffer::StrBuffer;
use crate::ui::{
component::{
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
Component,
},
geometry::LinearPlacement,
};
use crate::ui::component::base::Component;
use super::{
component::ResultScreen,
constant,
theme::{BLACK, ICON_FAIL, TEXT_BOLD, TEXT_NORMAL, WHITE},
};
use super::{component::ErrorScreen, constant};
#[cfg(not(feature = "micropython"))]
// SAFETY: Actually safe but see below
@ -33,21 +23,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_FAIL, m_top, m_bottom, true);
let mut frame = ErrorScreen::new(title, msg, footer);
frame.place(constant::screen());
frame.paint();
}

@ -44,7 +44,7 @@ pub fn textstyle_number_bold_or_mono(num: i32) -> &'static TextStyle {
}
// BLD icons
pub const LOGO_EMPTY: &[u8] = include_res!("model_tr/res/trezor_empty.toif");
include_icon!(LOGO_EMPTY, "model_tr/res/trezor_empty.toif");
include_icon!(ICON_FAIL, "model_tr/res/fail.toif");
// Firmware icons
@ -58,6 +58,10 @@ include_icon!(ICON_ARROW_DOWN, "model_tr/res/arrow_down.toif"); // 10*6
include_icon!(ICON_ARROW_BACK_UP, "model_tr/res/arrow_back_up.toif"); // 8*8
include_icon!(ICON_BIN, "model_tr/res/bin.toif"); // 10*10
include_icon!(ICON_CANCEL, "model_tr/res/cancel_no_outline.toif"); // 8*8
include_icon!(
ICON_CANCEL_INVERTED,
"model_tr/res/cancel_no_outline_inverted.toif"
); // 8*8
include_icon!(ICON_DELETE, "model_tr/res/delete.toif"); // 10*7
include_icon!(ICON_EYE, "model_tr/res/eye_round.toif"); // 12*7
include_icon!(ICON_LOCK, "model_tr/res/lock.toif"); // 10*10
@ -69,6 +73,7 @@ include_icon!(ICON_SUCCESS, "model_tr/res/success.toif");
include_icon!(ICON_TICK, "model_tr/res/tick.toif"); // 8*6
include_icon!(ICON_TICK_FAT, "model_tr/res/tick_fat.toif"); // 8*6
include_icon!(ICON_WARNING, "model_tr/res/warning.toif"); // 12*12
include_icon!(ICON_WARN_TITLE, "model_tr/res/bld_header_warn.toif");
// checklist settings
pub const CHECKLIST_SPACING: i16 = 5;
@ -83,6 +88,7 @@ pub const BUTTON_CONTENT_HEIGHT: i16 = 7;
pub const BUTTON_OUTLINE: i16 = 3;
pub const BUTTON_ARMS: i16 = 2;
pub const BUTTON_HEIGHT: i16 = BUTTON_CONTENT_HEIGHT + 2 * BUTTON_OUTLINE;
pub const TITLE_AREA_HEIGHT: i16 = 12;
// How many pixels should be between text and icons.
pub const ELLIPSIS_ICON_MARGIN: i16 = 4;

@ -1,5 +1,3 @@
#[cfg(not(feature = "bootloader"))]
use crate::ui::display;
#[cfg(feature = "bootloader")]
use crate::ui::model_tt::bootloader::theme::DEVICE_NAME;
use crate::ui::{
@ -14,6 +12,8 @@ const TEXT_BOTTOM_MARGIN: i16 = 24; // matching the homescreen label margin
const ICON_TOP_MARGIN: i16 = 48;
#[cfg(not(feature = "bootloader"))]
const MODEL_NAME_FONT: display::Font = display::Font::DEMIBOLD;
#[cfg(not(feature = "bootloader"))]
use crate::ui::display;
pub struct WelcomeScreen {
area: Rect,

@ -14,7 +14,9 @@ pub const MODEL_NAME: &str = "Trezor Model T";
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)
}
pub const SIZE: Offset = size();
pub const fn screen() -> Rect {
Rect::from_top_left_and_size(Point::zero(), size())
Rect::from_top_left_and_size(Point::zero(), SIZE)
}
pub const SCREEN: Rect = screen();

@ -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;

@ -18,6 +18,10 @@
#include "display-unix.h"
#ifdef TREZOR_MODEL_R
#define USE_BUTTON 1
#elif TREZOR_MODEL_T
#define USE_TOUCH 1
#endif
#endif //_BOARD_UNIX_H

@ -92,6 +92,12 @@ uint32_t touch_is_detected(void) { return _touch_detected; }
#include "button.h"
static char last_left = 0, last_right = 0;
char button_state_left(void) { return last_left; }
char button_state_right(void) { return last_right; }
uint32_t button_read(void) {
SDL_Event event;
SDL_PumpEvents();
@ -103,8 +109,10 @@ uint32_t button_read(void) {
}
switch (event.key.keysym.sym) {
case SDLK_LEFT:
last_left = 1;
return BTN_EVT_DOWN | BTN_LEFT;
case SDLK_RIGHT:
last_right = 1;
return BTN_EVT_DOWN | BTN_RIGHT;
}
break;
@ -114,8 +122,10 @@ uint32_t button_read(void) {
}
switch (event.key.keysym.sym) {
case SDLK_LEFT:
last_left = 0;
return BTN_EVT_UP | BTN_LEFT;
case SDLK_RIGHT:
last_right = 0;
return BTN_EVT_UP | BTN_RIGHT;
}
break;
@ -124,4 +134,6 @@ uint32_t button_read(void) {
return 0;
}
void button_init(void) {}
#endif

Loading…
Cancel
Save