feat(core/rust): homescreen background image

[no changelog]
pull/2758/head
tychovrahe 2 years ago committed by TychoVrahe
parent 14f8e88e01
commit d00e87ea80

@ -27,6 +27,15 @@ enum SafetyCheckLevel {
PromptTemporarily = 2; // like PromptAlways but reverts to Strict after reboot
}
/**
* Format of the homescreen image
*/
enum HomescreenFormat {
Toif144x144 = 1;
Jpeg240x240 = 2;
}
/**
* Request: Reset device to default state and ask for device details
* @start
@ -112,6 +121,7 @@ message Features {
optional uint32 display_rotation = 39; // in degrees from North
optional bool experimental_features = 40; // are experimental message types enabled?
optional bool busy = 41; // is the device busy, showing "Do not disconnect"?
optional HomescreenFormat homescreen_format = 42; // format of the homescreen, 1 = TOIf 144x144, 2 = jpg 240x240
}
/**

@ -133,7 +133,7 @@ env.Replace(
'-fstack-protector-all '
+ CPU_CCFLAGS + CCFLAGS_MOD,
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB',
LINKFLAGS='-T embed/boardloader/memory.ld -Wl,--gc-sections -Wl,-Map=build/boardloader/boardloader.map -Wl,--warn-common',
LINKFLAGS='-T embed/boardloader/memory.ld -Wl,--gc-sections -Wl,-Map=build/boardloader/boardloader.map -Wl,--warn-common -Wl,--print-memory-usage',
CPPPATH=[
'embed/boardloader',
'embed/trezorhal',

@ -192,7 +192,7 @@ env.Replace(
'-fstack-protector-all '
+ CPU_CCFLAGS + CCFLAGS_MOD,
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB',
LINKFLAGS='-T embed/bootloader/memory.ld -Wl,--gc-sections -Wl,-Map=build/bootloader/bootloader.map -Wl,--warn-common',
LINKFLAGS='-T embed/bootloader/memory.ld -Wl,--gc-sections -Wl,-Map=build/bootloader/bootloader.map -Wl,--warn-common -Wl,--print-memory-usage',
CPPPATH=[
'embed/rust',
'embed/bootloader',

@ -183,6 +183,7 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
]
CPPDEFINES_MOD += [
'TREZOR_UI2',
'USE_RUST_LOADER'
@ -478,7 +479,7 @@ env.Replace(
'-fstack-protector-all '
+ CPU_CCFLAGS + CCFLAGS_MOD,
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB',
LINKFLAGS='-T embed/firmware/memory_${TREZOR_MODEL}%s.ld -Wl,--gc-sections -Wl,-Map=build/firmware/firmware.map -Wl,--warn-common' % LD_VARIANT,
LINKFLAGS='-T embed/firmware/memory_${TREZOR_MODEL}%s.ld -Wl,--gc-sections -Wl,--print-memory-usage -Wl,-Map=build/firmware/firmware.map -Wl,--warn-common' % LD_VARIANT,
CPPPATH=[
'.',
'embed/rust',

@ -24,15 +24,12 @@
#if USE_DMA2D
#if defined BOOTLOADER
#define BUFFER_SECTION __attribute__((section(".buf")))
#else
#define BUFFER_SECTION
#endif
#define BUFFERS_16BPP 3
#define BUFFERS_4BPP 3
#define BUFFERS_TEXT 1
#define BUFFERS_JPEG 1
#define BUFFERS_JPEG_WORK 1
#define BUFFERS_BLURRING 1
const int32_t text_buffer_height = FONT_MAX_HEIGHT;
const int32_t buffer_width = DISPLAY_RESX;
@ -40,6 +37,9 @@ const int32_t buffer_width = DISPLAY_RESX;
BUFFER_SECTION line_buffer_16bpp_t line_buffers_16bpp[BUFFERS_16BPP];
BUFFER_SECTION line_buffer_4bpp_t line_buffers_4bpp[BUFFERS_4BPP];
BUFFER_SECTION buffer_text_t text_buffers[BUFFERS_TEXT];
NODMA_BUFFER_SECTION buffer_jpeg_t jpeg_buffers[BUFFERS_JPEG];
NODMA_BUFFER_SECTION buffer_jpeg_work_t jpeg_work_buffers[BUFFERS_JPEG_WORK];
NODMA_BUFFER_SECTION buffer_blurring_t blurring_buffers[BUFFERS_BLURRING];
line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear) {
if (idx >= BUFFERS_16BPP) {
@ -71,4 +71,37 @@ buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear) {
return &text_buffers[idx];
}
buffer_jpeg_t* buffers_get_jpeg_buffer(uint16_t idx, bool clear) {
if (idx >= BUFFERS_JPEG) {
return NULL;
}
if (clear) {
memzero(&jpeg_buffers[idx], sizeof(jpeg_buffers[idx]));
}
return &jpeg_buffers[idx];
}
buffer_jpeg_work_t* buffers_get_jpeg_work_buffer(uint16_t idx, bool clear) {
if (idx >= BUFFERS_JPEG_WORK) {
return NULL;
}
if (clear) {
memzero(&jpeg_work_buffers[idx], sizeof(jpeg_work_buffers[idx]));
}
return &jpeg_work_buffers[idx];
}
buffer_blurring_t* buffers_get_blurring_buffer(uint16_t idx, bool clear) {
if (idx >= BUFFERS_BLURRING) {
return NULL;
}
if (clear) {
memzero(&blurring_buffers[idx], sizeof(blurring_buffers[idx]));
}
return &blurring_buffers[idx];
}
#endif

@ -27,7 +27,7 @@
#define BUFFER_PIXELS DISPLAY_RESX
#define TEXT_BUFFER_HEIGHT 24
#define TEXT_BUFFER_HEIGHT 32
#if TEXT_BUFFER_HEIGHT < FONT_MAX_HEIGHT
#error Text buffer height is too small, please adjust to match used fonts
@ -36,6 +36,24 @@
#define LINE_BUFFER_16BPP_SIZE BUFFER_PIXELS * 2
#define LINE_BUFFER_4BPP_SIZE BUFFER_PIXELS / 2
#define TEXT_BUFFER_SIZE (BUFFER_PIXELS * TEXT_BUFFER_HEIGHT) / 2
#define JPEG_BUFFER_SIZE (BUFFER_PIXELS * 16)
// 3100 is needed according to tjpgd docs,
// 256 because we need non overlapping memory in rust
// 6 << 10 is for huffman decoding table
#define JPEG_WORK_SIZE (3100 + 256 + (6 << 10))
#if defined BOOTLOADER
#define BUFFER_SECTION __attribute__((section(".buf")))
#else
#define BUFFER_SECTION
#endif
#if defined BOOTLOADER || defined TREZOR_EMULATOR
#define NODMA_BUFFER_SECTION
#else
#define NODMA_BUFFER_SECTION __attribute__((section(".no_dma_buffers")))
#endif
typedef __attribute__((aligned(4))) struct {
uint8_t buffer[LINE_BUFFER_16BPP_SIZE];
@ -49,11 +67,26 @@ typedef __attribute__((aligned(4))) struct {
uint8_t buffer[TEXT_BUFFER_SIZE];
} buffer_text_t;
typedef __attribute__((aligned(4))) struct {
uint16_t buffer[JPEG_BUFFER_SIZE];
} buffer_jpeg_t;
typedef __attribute__((aligned(4))) struct {
uint8_t buffer[JPEG_WORK_SIZE];
} buffer_jpeg_work_t;
typedef __attribute__((aligned(4))) struct {
uint16_t buffer[10][3][BUFFER_PIXELS];
} buffer_blurring_t;
extern const int32_t text_buffer_height;
extern const int32_t buffer_width;
line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear);
line_buffer_4bpp_t* buffers_get_line_buffer_4bpp(uint16_t idx, bool clear);
buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear);
buffer_jpeg_t* buffers_get_jpeg_buffer(uint16_t idx, bool clear);
buffer_jpeg_work_t* buffers_get_jpeg_work_buffer(uint16_t idx, bool clear);
buffer_blurring_t* buffers_get_blurring_buffer(uint16_t idx, bool clear);
#endif // _BUFFERS_H

@ -149,6 +149,45 @@ void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b,
PIXELDATA_DIRTY();
}
void display_bar_radius_buffer(int x, int y, int w, int h, uint8_t r,
buffer_text_t *buffer) {
if (h > 32) {
return;
}
if (r != 2 && r != 4 && r != 8 && r != 16) {
return;
} else {
r = 16 / r;
}
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1);
for (int j = y0; j <= y1; j++) {
for (int i = x0; i <= x1; i++) {
int rx = i - x;
int ry = j - y;
int p = j * DISPLAY_RESX + i;
uint8_t c = 0;
if (rx < CORNER_RADIUS / r && ry < CORNER_RADIUS / r) {
c = cornertable[rx * r + ry * r * CORNER_RADIUS];
} else if (rx < CORNER_RADIUS / r && ry >= h - CORNER_RADIUS / r) {
c = cornertable[rx * r + (h - 1 - ry) * r * CORNER_RADIUS];
} else if (rx >= w - CORNER_RADIUS / r && ry < CORNER_RADIUS / r) {
c = cornertable[(w - 1 - rx) * r + ry * r * CORNER_RADIUS];
} else if (rx >= w - CORNER_RADIUS / r && ry >= h - CORNER_RADIUS / r) {
c = cornertable[(w - 1 - rx) * r + (h - 1 - ry) * r * CORNER_RADIUS];
} else {
c = 15;
}
int b = p / 2;
if (p % 2) {
buffer->buffer[b] |= c << 4;
} else {
buffer->buffer[b] |= (c);
}
}
}
}
#define UZLIB_WINDOW_SIZE (1 << 10)
static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window,

@ -61,6 +61,8 @@ void display_clear(void);
void display_bar(int x, int y, int w, int h, uint16_t c);
void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b,
uint8_t r);
void display_bar_radius_buffer(int x, int y, int w, int h, uint8_t r,
buffer_text_t *buffer);
bool display_toif_info(const uint8_t *buf, uint32_t len, uint16_t *out_w,
uint16_t *out_h, toif_format_t *out_format);

@ -83,4 +83,9 @@ SECTIONS {
. = 37K; /* this acts as a build time assertion that at least this much memory is available for heap use */
. = ABSOLUTE(sram_end); /* this explicitly sets the end of the heap */
} >SRAM
.data_ccm : ALIGN(4) {
*(.no_dma_buffers*);
. = ALIGN(4);
} >CCMRAM
}

@ -8,7 +8,7 @@ build = "build.rs"
[features]
default = ["model_tt"]
bitcoin_only = []
model_tt = ["touch"]
model_tt = ["touch", "jpeg"]
model_t1 = ["buttons"]
model_tr = ["buttons"]
micropython = []
@ -19,8 +19,9 @@ ui_debug = []
buttons = []
touch = []
clippy = []
jpeg = []
debug = ["ui_debug"]
test = ["cc", "glob", "micropython", "protobuf", "ui", "ui_debug"]
test = ["cc", "glob", "micropython", "protobuf", "ui", "ui_debug", "dma2d"]
[lib]
crate-type = ["staticlib"]

@ -287,6 +287,7 @@ fn generate_trezorhal_bindings() {
.allowlist_function("display_text_width")
.allowlist_function("display_bar")
.allowlist_function("display_bar_radius")
.allowlist_function("display_bar_radius_buffer")
.allowlist_function("display_icon")
.allowlist_function("display_image")
.allowlist_function("display_toif_info")
@ -328,6 +329,9 @@ fn generate_trezorhal_bindings() {
.allowlist_function("buffers_get_line_buffer_16bpp")
.allowlist_function("buffers_get_line_buffer_4bpp")
.allowlist_function("buffers_get_text_buffer")
.allowlist_function("buffers_get_jpeg_buffer")
.allowlist_function("buffers_get_jpeg_work_buffer")
.allowlist_function("buffers_get_blurring_buffer")
.allowlist_var("text_buffer_height")
.allowlist_var("buffer_width")
//usb

@ -17,6 +17,8 @@ static void _librust_qstrs(void) {
MP_QSTR_CANCELLED;
MP_QSTR_INFO;
MP_QSTR_disable_animation;
MP_QSTR_jpeg_info;
MP_QSTR_jpeg_test;
MP_QSTR_confirm_action;
MP_QSTR_confirm_blob;
MP_QSTR_confirm_properties;

@ -18,6 +18,7 @@ mod trezorhal;
mod micropython;
#[cfg(feature = "protobuf")]
mod protobuf;
mod storage;
mod time;
#[cfg(feature = "ui_debug")]
mod trace;

@ -38,6 +38,33 @@ impl<T> Gc<T> {
}
}
impl<T: Default> Gc<[T]> {
/// Allocate slice on the heap managed by the MicroPython garbage collector
/// and fill with default values.
pub fn new_slice(len: usize) -> Result<Self, Error> {
let layout = Layout::array::<T>(len).unwrap();
// TODO: Assert that `layout.align()` is the same as the GC alignment.
// SAFETY:
// - Unfortunately we cannot respect `layout.align()` as MicroPython GC does
// not support custom alignment.
// - `ptr` is guaranteed to stay valid as long as it's reachable from the stack
// or the MicroPython heap.
// EXCEPTION: Returns null instead of raising.
unsafe {
let raw = ffi::gc_alloc(layout.size(), 0);
if raw.is_null() {
return Err(Error::AllocationFailed);
}
let typed: *mut T = raw.cast();
for i in 0..len {
ptr::write(typed.add(i), T::default());
}
let array_ptr = ptr::slice_from_raw_parts_mut(typed, len);
Ok(Self::from_raw(array_ptr as _))
}
}
}
impl<T: ?Sized> Gc<T> {
/// Construct a `Gc` from a raw pointer.
///

@ -0,0 +1,59 @@
use crate::trezorhal::storage::{get, get_length};
pub const HOMESCREEN_MAX_SIZE: usize = 16384;
const STORAGE_VERSION_01: u8 = 1;
const STORAGE_VERSION_02: u8 = 2;
const STORAGE_VERSION_CURRENT: u8 = STORAGE_VERSION_02;
const FLAG_PUBLIC: u16 = 0x8000;
const FLAG_WRITE: u16 = 0xC000;
const APP_DEVICE: u16 = 0x0100;
const DEVICE_ID: u16 = FLAG_PUBLIC | APP_DEVICE;
const VERSION: u16 = APP_DEVICE | 0x0001;
const MNEMONIC_SECRET: u16 = APP_DEVICE | 0x0002;
// NOTE: 0x03 key was used in the past for LANGUAGE. Not used anymore.
const LABEL: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0004;
const USE_PASSPHRASE: u16 = APP_DEVICE | 0x0005;
const HOMESCREEN: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0006;
const NEEDS_BACKUP: u16 = APP_DEVICE | 0x0007;
const FLAGS: u16 = APP_DEVICE | 0x0008;
const U2F_COUNTER_PRIVATE: u16 = APP_DEVICE | 0x0009;
const U2F_COUNTER: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0009;
const PASSPHRASE_ALWAYS_ON_DEVICE: u16 = APP_DEVICE | 0x000A;
const UNFINISHED_BACKUP: u16 = APP_DEVICE | 0x000B;
const AUTOLOCK_DELAY_MS: u16 = APP_DEVICE | 0x000C;
const NO_BACKUP: u16 = APP_DEVICE | 0x000D;
const BACKUP_TYPE: u16 = APP_DEVICE | 0x000E;
const ROTATION: u16 = FLAG_PUBLIC | APP_DEVICE | 0x000F;
const SLIP39_IDENTIFIER: u16 = APP_DEVICE | 0x0010;
const SLIP39_ITERATION_EXPONENT: u16 = APP_DEVICE | 0x0011;
const SD_SALT_AUTH_KEY: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0012;
const INITIALIZED: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0013;
const SAFETY_CHECK_LEVEL: u16 = APP_DEVICE | 0x0014;
const EXPERIMENTAL_FEATURES: u16 = APP_DEVICE | 0x0015;
pub fn get_avatar_len() -> Result<usize, ()> {
let avatar_len_res = get_length(HOMESCREEN);
if let Ok(len) = avatar_len_res {
Ok(len)
} else {
Err(())
}
}
pub fn get_avatar(buffer: &mut [u8]) -> Result<usize, ()> {
let avatar_len_res = get_length(HOMESCREEN);
if let Ok(len) = avatar_len_res {
if len <= buffer.len() {
unwrap!(get(HOMESCREEN, buffer));
Ok(len)
} else {
Err(())
}
} else {
Err(())
}
}

@ -1,9 +1,11 @@
use super::ffi;
pub use ffi::{
buffer_text_t as BufferText, line_buffer_16bpp_t as LineBuffer16Bpp,
line_buffer_4bpp_t as LineBuffer4Bpp,
buffer_blurring_t as BlurringBuffer, buffer_text_t as BufferText,
line_buffer_16bpp_t as LineBuffer16Bpp, line_buffer_4bpp_t as LineBuffer4Bpp,
};
#[cfg(feature = "jpeg")]
pub use ffi::{buffer_jpeg_t as BufferJpeg, buffer_jpeg_work_t as BufferJpegWork};
/// Returns a buffer for one line of 16bpp data
///
@ -43,3 +45,44 @@ pub unsafe fn get_text_buffer(idx: u16, clear: bool) -> &'static mut BufferText
unwrap!(ptr.as_mut())
}
}
/// Returns a buffer for jpeg data
///
/// # Safety
///
/// This function is unsafe because the caller has to guarantee
/// that he doesn't use buffer on same index multiple times
#[cfg(feature = "jpeg")]
pub unsafe fn get_jpeg_buffer(idx: u16, clear: bool) -> &'static mut BufferJpeg {
unsafe {
let ptr = ffi::buffers_get_jpeg_buffer(idx, clear);
unwrap!(ptr.as_mut())
}
}
/// Returns a jpeg work buffer
///
/// # Safety
///
/// This function is unsafe because the caller has to guarantee
/// that he doesn't use buffer on same index multiple times
#[cfg(feature = "jpeg")]
pub unsafe fn get_jpeg_work_buffer(idx: u16, clear: bool) -> &'static mut BufferJpegWork {
unsafe {
let ptr = ffi::buffers_get_jpeg_work_buffer(idx, clear);
unwrap!(ptr.as_mut())
}
}
/// Returns a buffer for blurring data
///
/// # Safety
///
/// This function is unsafe because the caller has to guarantee
/// that he doesn't use buffer on same index multiple times
pub unsafe fn get_blurring_buffer(idx: u16, clear: bool) -> &'static mut BlurringBuffer {
unsafe {
let ptr = ffi::buffers_get_blurring_buffer(idx, clear);
unwrap!(ptr.as_mut())
}
}

@ -97,6 +97,12 @@ pub fn bar_radius(x: i16, y: i16, w: i16, h: i16, fgcolor: u16, bgcolor: u16, ra
}
}
pub fn bar_radius_buffer(x: i16, y: i16, w: i16, h: i16, radius: u8, buffer: &mut BufferText) {
unsafe {
ffi::display_bar_radius_buffer(x.into(), y.into(), w.into(), h.into(), radius, buffer as _)
}
}
pub fn icon(x: i16, y: i16, w: i16, h: i16, data: &[u8], fgcolor: u16, bgcolor: u16) {
unsafe {
ffi::display_icon(

@ -1,5 +1,6 @@
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(clippy::upper_case_acronyms)]
#![allow(non_snake_case)]
#![allow(dead_code)]

@ -170,7 +170,7 @@ pub fn toif_info(data: &[u8]) -> Option<(Offset, ToifFormat)> {
/// Aborts if the TOIF file does not have the correct grayscale flag, do not use
/// with user-supplied inputs.
fn toif_info_ensure(data: &[u8], format: ToifFormat) -> (Offset, &[u8]) {
pub(crate) fn toif_info_ensure(data: &[u8], format: ToifFormat) -> (Offset, &[u8]) {
let info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert_eq!(info.format, format);
let size = Offset::new(
@ -421,16 +421,29 @@ pub fn rect_rounded2_partial(
///
/// `buffer_bpp` determines size of pixel data
/// `data_width` sets the width of valid data in the `src_buffer`
fn position_buffer(
pub(crate) fn position_buffer(
dest_buffer: &mut [u8],
src_buffer: &[u8],
buffer_bpp: usize,
offset_x: i16,
data_width: i16,
) {
let start: usize = (offset_x).clamp(0, constant::WIDTH) as usize;
let end: usize = (offset_x + data_width).clamp(0, constant::WIDTH) as usize;
let data_width_even = if buffer_bpp == 4 && data_width % 2 != 0 {
data_width + 1
} else {
data_width
};
let mut start: usize = (offset_x).clamp(0, constant::WIDTH) as usize;
let mut end: usize = (offset_x + data_width_even).clamp(0, constant::WIDTH) as usize;
if buffer_bpp == 4 {
start &= !0x01;
end &= !0x01;
}
let width = end - start;
// if the offset is negative, need to skip beginning of uncompressed data
let x_sh = if offset_x < 0 {
(-offset_x).clamp(0, constant::WIDTH - width as i16) as usize

@ -19,6 +19,15 @@ use crate::{
use cstr_core::cstr;
use heapless::Vec;
#[cfg(feature = "jpeg")]
use crate::{
micropython::{
buffer::get_buffer,
ffi::{mp_obj_new_int, mp_obj_new_tuple},
},
ui::display::tjpgd::{jpeg_info, jpeg_test},
};
pub fn iter_into_objs<const N: usize>(iterable: Obj) -> Result<[Obj; N], Error> {
let err = Error::ValueError(cstr!("Invalid iterable length"));
let mut vec = Vec::<Obj, N>::new();
@ -240,3 +249,45 @@ pub extern "C" fn upy_disable_animation(disable: Obj) -> Obj {
};
unsafe { try_or_raise(block) }
}
#[cfg(feature = "jpeg")]
pub extern "C" fn upy_jpeg_info(data: Obj) -> Obj {
let block = || {
let buffer = unsafe { get_buffer(data) };
if let Ok(buffer) = buffer {
let info = jpeg_info(buffer);
if let Some(info) = info {
let obj = unsafe {
let values = [
mp_obj_new_int(info.0.x as _),
mp_obj_new_int(info.0.y as _),
mp_obj_new_int(info.1 as _),
];
mp_obj_new_tuple(3, values.as_ptr())
};
Ok(obj)
} else {
Err(Error::ValueError(cstr!("Invalid image format.")))
}
} else {
Err(Error::ValueError(cstr!("Buffer error.")))
}
};
unsafe { try_or_raise(block) }
}
#[cfg(feature = "jpeg")]
pub extern "C" fn upy_jpeg_test(data: Obj) -> Obj {
let block = || {
let buffer =
unsafe { get_buffer(data) }.map_err(|_| Error::ValueError(cstr!("Buffer error.")))?;
let result = jpeg_test(buffer);
Ok(result.into())
};
unsafe { try_or_raise(block) }
}

@ -1,22 +1,29 @@
mod render;
use crate::{
micropython::gc::Gc,
storage::{get_avatar, get_avatar_len},
time::{Duration, Instant},
trezorhal::usb::usb_configured,
ui::{
component::{Component, Empty, Event, EventCtx, Pad, TimerToken},
display::{self, Color, Font},
component::{Component, Event, EventCtx, Pad, TimerToken},
display::{self, tjpgd::jpeg_info, Color, Font},
event::{TouchEvent, USBEvent},
geometry::{Offset, Point, Rect},
model_tt::constant,
util::icon_text_center,
model_tt::{constant, theme::IMAGE_HOMESCREEN},
},
};
use super::{theme, Loader, LoaderMsg, NotificationFrame};
use render::{
homescreen, homescreen_blurred, HomescreenNotification, HomescreenText, HOMESCREEN_IMAGE_SIZE,
};
use super::{theme, Loader, LoaderMsg};
const AREA: Rect = constant::screen();
const TOP_CENTER: Point = AREA.top_center();
const LABEL_Y: i16 = 216;
const LOCKED_Y: i16 = 101;
const LOCKED_Y: i16 = 107;
const TAP_Y: i16 = 134;
const HOLD_Y: i16 = 35;
const LOADER_OFFSET: Offset = Offset::y(-10);
@ -27,9 +34,9 @@ pub struct Homescreen<T> {
label: T,
notification: Option<(T, u8)>,
hold_to_lock: bool,
usb_connected: bool,
loader: Loader,
pad: Pad,
paint_notification_only: bool,
delay: Option<TimerToken>,
}
@ -48,6 +55,7 @@ where
hold_to_lock,
loader: Loader::new().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
pad: Pad::with_background(theme::BG),
paint_notification_only: false,
delay: None,
}
}
@ -60,23 +68,23 @@ where
}
}
fn paint_notification(&self) {
fn get_notification(&self) -> Option<HomescreenNotification> {
if !usb_configured() {
let (color, icon) = Self::level_to_style(0);
NotificationFrame::<Empty, T>::paint_notification(
AREA,
Some(HomescreenNotification {
text: "NO USB CONNECTION",
icon,
"NO USB CONNECTION",
color,
);
})
} else if let Some((notification, level)) = &self.notification {
let (color, icon) = Self::level_to_style(*level);
NotificationFrame::<Empty, T>::paint_notification(
AREA,
Some(HomescreenNotification {
text: notification.as_ref(),
icon,
notification.as_ref(),
color,
);
})
} else {
None
}
}
@ -97,6 +105,7 @@ where
fn event_usb(&mut self, ctx: &mut EventCtx, event: Event) {
if let Event::USB(USBEvent::Connected(_)) = event {
self.paint_notification_only = true;
ctx.request_paint();
}
}
@ -136,6 +145,7 @@ where
Some(LoaderMsg::ShrunkCompletely) => {
self.loader.reset();
self.pad.clear();
self.paint_notification_only = false;
ctx.request_paint()
}
None => {}
@ -171,8 +181,34 @@ where
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
self.paint_loader();
} else {
self.paint_notification();
paint_label(self.label.as_ref(), false);
let mut label_style = theme::TEXT_BOLD;
label_style.text_color = theme::FG;
let text = HomescreenText {
text: self.label.as_ref(),
style: label_style,
offset: Offset::new(10, LABEL_Y),
icon: None,
};
let notification = self.get_notification();
let res = get_image();
if let Ok(data) = res {
homescreen(
data.as_ref(),
&[text],
notification,
self.paint_notification_only,
);
} else {
homescreen(
IMAGE_HOMESCREEN,
&[text],
notification,
self.paint_notification_only,
);
}
}
}
@ -225,25 +261,64 @@ where
} else {
("LOCKED", "Tap to unlock")
};
icon_text_center(
TOP_CENTER + Offset::y(LOCKED_Y),
theme::ICON_LOCK,
2,
locked,
theme::TEXT_BOLD,
Offset::zero(),
);
display::text_center(
TOP_CENTER + Offset::y(TAP_Y),
tap,
Font::NORMAL,
theme::OFF_WHITE,
theme::BG,
);
paint_label(self.label.as_ref(), true);
let mut tap_style = theme::TEXT_NORMAL;
tap_style.text_color = theme::OFF_WHITE;
let mut label_style = theme::TEXT_BOLD;
label_style.text_color = theme::GREY_LIGHT;
let texts: [HomescreenText; 3] = [
HomescreenText {
text: locked,
style: theme::TEXT_BOLD,
offset: Offset::new(10, LOCKED_Y),
icon: Some(theme::ICON_LOCK),
},
HomescreenText {
text: tap,
style: tap_style,
offset: Offset::new(10, TAP_Y),
icon: None,
},
HomescreenText {
text: self.label.as_ref(),
style: label_style,
offset: Offset::new(10, LABEL_Y),
icon: None,
},
];
let res = get_image();
if let Ok(data) = res {
homescreen_blurred(data.as_ref(), &texts);
} else {
homescreen_blurred(IMAGE_HOMESCREEN, &texts);
}
}
}
fn get_image() -> Result<Gc<[u8]>, ()> {
if let Ok(len) = get_avatar_len() {
let result = Gc::<[u8]>::new_slice(len);
if let Ok(mut buffer) = result {
let buf = unsafe { Gc::<[u8]>::as_mut(&mut buffer) };
if get_avatar(buf).is_ok() {
let jpeg = jpeg_info(buffer.as_ref());
if let Some((size, mcu_height)) = jpeg {
if size.x == HOMESCREEN_IMAGE_SIZE
&& size.y == HOMESCREEN_IMAGE_SIZE
&& mcu_height <= 16
{
return Ok(buffer);
}
}
}
}
};
Err(())
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Lockscreen<T> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
@ -251,18 +326,3 @@ impl<T> crate::trace::Trace for Lockscreen<T> {
d.close();
}
}
fn paint_label(label: &str, lockscreen: bool) {
let label_color = if lockscreen {
theme::GREY_MEDIUM
} else {
theme::FG
};
display::text_center(
TOP_CENTER + Offset::y(LABEL_Y),
label,
Font::BOLD,
label_color,
theme::BG,
);
}

@ -0,0 +1,621 @@
#[cfg(feature = "dma2d")]
use crate::trezorhal::{
buffers::{get_buffer_16bpp, get_buffer_4bpp, get_text_buffer, BufferText, LineBuffer4Bpp},
dma2d::{dma2d_setup_4bpp_over_16bpp, dma2d_start_blend, dma2d_wait_for_transfer},
};
use crate::{
trezorhal::{
buffers::{get_blurring_buffer, get_jpeg_buffer, get_jpeg_work_buffer, BufferJpeg},
display,
display::{bar_radius_buffer, ToifFormat},
uzlib::UzlibContext,
},
ui::{
constant::screen,
display::{position_buffer, set_window, toif_info_ensure, Color},
geometry::{Offset, Point, Rect},
},
};
use crate::ui::{
component::text::TextStyle,
constant::{HEIGHT, WIDTH},
display::tjpgd::{BufferInput, BufferOutput, JDEC},
model_tt::theme,
util::icon_text_center,
};
#[derive(Clone, Copy)]
pub struct HomescreenText<'a> {
pub text: &'a str,
pub style: TextStyle,
pub offset: Offset,
pub icon: Option<&'static [u8]>,
}
#[derive(Clone, Copy)]
pub struct HomescreenNotification<'a> {
pub text: &'a str,
pub icon: &'static [u8],
pub color: Color,
}
#[derive(Clone, Copy)]
struct HomescreenTextInfo {
pub text_area: Rect,
pub text_width: i16,
pub text_color: Color,
pub icon_area: Option<Rect>,
}
pub const HOMESCREEN_IMAGE_SIZE: i16 = 240;
const HOMESCREEN_MAX_ICON_SIZE: i16 = 20;
const NOTIFICATION_HEIGHT: i16 = 32;
const NOTIFICATION_BORDER: i16 = 8;
const NOTIFICATION_ICON_SPACE: i16 = 8;
const NOTIFICATION_TEXT_OFFSET: Offset = Offset::new(1, -2);
const TEXT_ICON_SPACE: i16 = 2;
const HOMESCREEN_DIM_HIEGHT: i16 = 30;
const HOMESCREEN_DIM_START: i16 = 195;
const HOMESCREEN_DIM: f32 = 0.85;
const HOMESCREEN_DIM_BORDER: i16 = 20;
const LOCKSCREEN_DIM: f32 = 0.85;
const LOCKSCREEN_DIM_BG: f32 = 0.0;
const BLUR_SIZE: usize = 9;
const BLUR_DIV: u32 =
((65536_f32 * (1_f32 - LOCKSCREEN_DIM_BG)) as u32) / ((BLUR_SIZE * BLUR_SIZE) as u32);
const DECOMP_LINES: usize = BLUR_SIZE + 1;
const BLUR_RADIUS: i16 = (BLUR_SIZE / 2) as i16;
const COLORS: usize = 3;
const RED_IDX: usize = 0;
const GREEN_IDX: usize = 1;
const BLUE_IDX: usize = 2;
fn homescreen_get_fg_text(
y_tmp: i16,
text_info: HomescreenTextInfo,
text_buffer: &BufferText,
fg_buffer: &mut LineBuffer4Bpp,
) -> bool {
if y_tmp >= text_info.text_area.y0 && y_tmp < text_info.text_area.y1 {
let y_pos = y_tmp - text_info.text_area.y0;
position_buffer(
&mut fg_buffer.buffer,
&text_buffer.buffer[(y_pos * WIDTH / 2) as usize..((y_pos + 1) * WIDTH / 2) as usize],
4,
text_info.text_area.x0,
text_info.text_width,
);
}
y_tmp == (text_info.text_area.y1 - 1)
}
fn homescreen_get_fg_icon(
y_tmp: i16,
text_info: HomescreenTextInfo,
icon_data: &[u8],
fg_buffer: &mut LineBuffer4Bpp,
) {
if let Some(icon_area) = text_info.icon_area {
let icon_size = icon_area.size();
if y_tmp >= icon_area.y0 && y_tmp < icon_area.y1 {
let y_pos = y_tmp - icon_area.y0;
position_buffer(
&mut fg_buffer.buffer,
&icon_data
[(y_pos * icon_size.x / 2) as usize..((y_pos + 1) * icon_size.x / 2) as usize],
4,
icon_area.x0,
icon_size.x,
);
}
}
}
fn homescreen_position_text(
text: &HomescreenText,
buffer: &mut BufferText,
icon_buffer: &mut [u8],
) -> HomescreenTextInfo {
let text_width = display::text_width(text.text, text.style.text_font.into());
let font_max_height = display::text_max_height(text.style.text_font.into());
let font_baseline = display::text_baseline(text.style.text_font.into());
let text_width_clamped = text_width.clamp(0, screen().width());
let icon_size = if let Some(icon) = text.icon {
let (icon_size, icon_data) = toif_info_ensure(icon, ToifFormat::GrayScaleEH);
assert!(icon_size.x <= HOMESCREEN_MAX_ICON_SIZE);
assert!(icon_size.y <= HOMESCREEN_MAX_ICON_SIZE);
let mut ctx = UzlibContext::new(icon_data, None);
unwrap!(ctx.uncompress(icon_buffer), "Decompression failed");
icon_size
} else {
Offset::zero()
};
let text_top = screen().y0 + text.offset.y - font_max_height + font_baseline;
let text_bottom = screen().y0 + text.offset.y + font_baseline;
let icon_left = screen().center().x - (text_width_clamped + icon_size.x + TEXT_ICON_SPACE) / 2;
let text_left = icon_left + icon_size.x + TEXT_ICON_SPACE;
let text_right = screen().center().x + (text_width_clamped + icon_size.x + TEXT_ICON_SPACE) / 2;
let text_area = Rect::new(
Point::new(text_left, text_top),
Point::new(text_right, text_bottom),
);
let icon_area = if text.icon.is_some() {
Some(Rect::from_top_left_and_size(
Point::new(icon_left, text_bottom - icon_size.y - font_baseline),
icon_size,
))
} else {
None
};
display::text_into_buffer(text.text, text.style.text_font.into(), buffer, 0);
HomescreenTextInfo {
text_area,
text_width,
text_color: text.style.text_color,
icon_area,
}
}
#[inline(always)]
fn homescreen_dim_area(x: i16, y: i16) -> bool {
y >= HOMESCREEN_DIM_START
&& (y > HOMESCREEN_DIM_START + 1
&& y < (HOMESCREEN_DIM_START + HOMESCREEN_DIM_HIEGHT - 1)
&& x > HOMESCREEN_DIM_BORDER
&& x < WIDTH - HOMESCREEN_DIM_BORDER)
|| (y > HOMESCREEN_DIM_START
&& y < (HOMESCREEN_DIM_START + HOMESCREEN_DIM_HIEGHT)
&& x > HOMESCREEN_DIM_BORDER + 1
&& x < WIDTH - (HOMESCREEN_DIM_BORDER + 1))
|| ((HOMESCREEN_DIM_START..=(HOMESCREEN_DIM_START + HOMESCREEN_DIM_HIEGHT)).contains(&y)
&& x > HOMESCREEN_DIM_BORDER + 2
&& x < WIDTH - (HOMESCREEN_DIM_BORDER + 2))
}
fn homescreen_line_blurred(
icon_data: &[u8],
text_buffer: &mut BufferText,
text_info: HomescreenTextInfo,
blurring: &BlurringContext,
y: i16,
) -> bool {
let t_buffer = unsafe { get_buffer_4bpp((y & 0x1) as u16, true) };
let mut img_buffer = unsafe { get_buffer_16bpp((y & 0x1) as u16, false) };
for x in 0..HOMESCREEN_IMAGE_SIZE {
let c = if homescreen_dim_area(x, y) {
let x = x as usize;
let coef = (65536_f32 * LOCKSCREEN_DIM) as u32;
let r = (blurring.totals[RED_IDX][x] as u32 * BLUR_DIV) >> 16;
let g = (blurring.totals[GREEN_IDX][x] as u32 * BLUR_DIV) >> 16;
let b = (blurring.totals[BLUE_IDX][x] as u32 * BLUR_DIV) >> 16;
let r = (((coef * r) >> 8) & 0xF800) as u16;
let g = (((coef * g) >> 13) & 0x07E0) as u16;
let b = (((coef * b) >> 19) & 0x001F) as u16;
r | g | b
} else {
let x = x as usize;
let r = (((blurring.totals[RED_IDX][x] as u32 * BLUR_DIV) >> 8) & 0xF800) as u16;
let g = (((blurring.totals[GREEN_IDX][x] as u32 * BLUR_DIV) >> 13) & 0x07E0) as u16;
let b = (((blurring.totals[BLUE_IDX][x] as u32 * BLUR_DIV) >> 19) & 0x001F) as u16;
r | g | b
};
let j = (2 * x) as usize;
img_buffer.buffer[j + 1] = (c >> 8) as u8;
img_buffer.buffer[j] = (c & 0xFF) as u8;
}
let done = homescreen_get_fg_text(y, text_info, text_buffer, t_buffer);
homescreen_get_fg_icon(y, text_info, icon_data, t_buffer);
dma2d_wait_for_transfer();
dma2d_setup_4bpp_over_16bpp(text_info.text_color.into());
dma2d_start_blend(&t_buffer.buffer, &img_buffer.buffer, WIDTH);
done
}
fn homescreen_line(
icon_data: &[u8],
text_buffer: &mut BufferText,
text_info: HomescreenTextInfo,
data_buffer: &mut BufferJpeg,
mcu_height: i16,
y: i16,
) -> bool {
let t_buffer = unsafe { get_buffer_4bpp((y & 0x1) as u16, true) };
let mut img_buffer = unsafe { get_buffer_16bpp((y & 0x1) as u16, false) };
let image_data = get_data(data_buffer, y, mcu_height);
for x in 0..HOMESCREEN_IMAGE_SIZE {
let d = image_data[x as usize];
let c = if homescreen_dim_area(x, y) {
let coef = (65536_f32 * HOMESCREEN_DIM) as u32;
let r = (d & 0xF800) >> 8;
let g = (d & 0x07E0) >> 3;
let b = (d & 0x001F) << 3;
let r = (((coef * r as u32) >> 8) & 0xF800) as u16;
let g = (((coef * g as u32) >> 13) & 0x07E0) as u16;
let b = (((coef * b as u32) >> 19) & 0x001F) as u16;
r | g | b
} else {
d
};
let j = 2 * x as usize;
img_buffer.buffer[j + 1] = (c >> 8) as u8;
img_buffer.buffer[j] = (c & 0xFF) as u8;
}
let done = homescreen_get_fg_text(y, text_info, text_buffer, t_buffer);
homescreen_get_fg_icon(y, text_info, icon_data, t_buffer);
dma2d_wait_for_transfer();
dma2d_setup_4bpp_over_16bpp(text_info.text_color.into());
dma2d_start_blend(&t_buffer.buffer, &img_buffer.buffer, WIDTH);
done
}
fn homescreen_next_text(
texts: &[HomescreenText],
text_buffer: &mut BufferText,
icon_data: &mut [u8],
text_info: HomescreenTextInfo,
text_idx: usize,
) -> (HomescreenTextInfo, usize) {
let mut next_text_idx = text_idx;
let mut next_text_info = text_info;
if next_text_idx < texts.len() {
if let Some(txt) = texts.get(next_text_idx) {
text_buffer.buffer.fill(0);
next_text_info = homescreen_position_text(txt, text_buffer, icon_data);
next_text_idx += 1;
}
}
(next_text_info, next_text_idx)
}
#[inline(always)]
fn update_accs_add(data: &[u16], idx: usize, acc_r: &mut u16, acc_g: &mut u16, acc_b: &mut u16) {
let d = data[idx];
let r = (d & 0xF800) >> 8;
let g = (d & 0x07E0) >> 3;
let b = (d & 0x001F) << 3;
*acc_r += r;
*acc_g += g;
*acc_b += b;
}
#[inline(always)]
fn update_accs_sub(data: &[u16], idx: usize, acc_r: &mut u16, acc_g: &mut u16, acc_b: &mut u16) {
let d = data[idx];
let r = (d & 0xF800) >> 8;
let g = (d & 0x07E0) >> 3;
let b = (d & 0x001F) << 3;
*acc_r -= r;
*acc_g -= g;
*acc_b -= b;
}
struct BlurringContext {
pub lines: &'static mut [[[u16; 240usize]; 3usize]],
pub totals: [[u16; HOMESCREEN_IMAGE_SIZE as usize]; COLORS],
line_num: i16,
add_idx: usize,
rem_idx: usize,
}
impl BlurringContext {
pub fn new() -> Self {
let mem = unsafe { get_blurring_buffer(0, true) };
Self {
lines: &mut mem.buffer[0..DECOMP_LINES],
totals: [[0; HOMESCREEN_IMAGE_SIZE as usize]; COLORS],
line_num: 0,
add_idx: 0,
rem_idx: 0,
}
}
fn clear(&mut self) {
for (i, total) in self.totals.iter_mut().enumerate() {
for line in self.lines.iter_mut() {
line[i].fill(0);
}
total.fill(0);
}
}
// computes color averages for one line of image data
fn compute_line_avgs(&mut self, buffer: &mut BufferJpeg, mcu_height: i16) {
let mut acc_r = 0;
let mut acc_g = 0;
let mut acc_b = 0;
let data = get_data(buffer, self.line_num, mcu_height);
for i in -BLUR_RADIUS..=BLUR_RADIUS {
let ic = i.clamp(0, HOMESCREEN_IMAGE_SIZE as i16 - 1) as usize;
update_accs_add(data, ic, &mut acc_r, &mut acc_g, &mut acc_b);
}
for i in 0..HOMESCREEN_IMAGE_SIZE {
self.lines[self.add_idx][RED_IDX][i as usize] = acc_r;
self.lines[self.add_idx][GREEN_IDX][i as usize] = acc_g;
self.lines[self.add_idx][BLUE_IDX][i as usize] = acc_b;
// clamping handles left and right edges
let ic = (i - BLUR_RADIUS).clamp(0, HOMESCREEN_IMAGE_SIZE as i16 - 1) as usize;
let ic2 = (i + BLUR_SIZE as i16 - BLUR_RADIUS)
.clamp(0, HOMESCREEN_IMAGE_SIZE as i16 - 1) as usize;
update_accs_add(data, ic2, &mut acc_r, &mut acc_g, &mut acc_b);
update_accs_sub(data, ic, &mut acc_r, &mut acc_g, &mut acc_b);
}
self.line_num += 1;
}
// adds one line of averages to sliding total averages
fn vertical_avg_add(&mut self) {
for i in 0..HOMESCREEN_IMAGE_SIZE as usize {
self.totals[RED_IDX][i] += self.lines[self.add_idx][RED_IDX][i];
self.totals[GREEN_IDX][i] += self.lines[self.add_idx][GREEN_IDX][i];
self.totals[BLUE_IDX][i] += self.lines[self.add_idx][BLUE_IDX][i];
}
}
// adds one line and removes one line of averages to/from sliding total averages
fn vertical_avg(&mut self) {
for i in 0..HOMESCREEN_IMAGE_SIZE as usize {
self.totals[RED_IDX][i] +=
self.lines[self.add_idx][RED_IDX][i] - self.lines[self.rem_idx][RED_IDX][i];
self.totals[GREEN_IDX][i] +=
self.lines[self.add_idx][GREEN_IDX][i] - self.lines[self.rem_idx][GREEN_IDX][i];
self.totals[BLUE_IDX][i] +=
self.lines[self.add_idx][BLUE_IDX][i] - self.lines[self.rem_idx][BLUE_IDX][i];
}
}
fn inc_add(&mut self) {
self.add_idx += 1;
if self.add_idx >= DECOMP_LINES {
self.add_idx = 0;
}
}
fn inc_rem(&mut self) {
self.rem_idx += 1;
if self.rem_idx >= DECOMP_LINES {
self.rem_idx = 0;
}
}
fn get_line_num(&self) -> i16 {
self.line_num
}
}
#[inline(always)]
fn get_data(buffer: &mut BufferJpeg, line_num: i16, mcu_height: i16) -> &mut [u16] {
let data_start = ((line_num % mcu_height) * WIDTH) as usize;
let data_end = (((line_num % mcu_height) + 1) * WIDTH) as usize;
&mut buffer.buffer[data_start..data_end]
}
pub fn homescreen_blurred(data: &[u8], texts: &[HomescreenText]) {
let mut icon_data = [0_u8; (HOMESCREEN_MAX_ICON_SIZE * HOMESCREEN_MAX_ICON_SIZE / 2) as usize];
let text_buffer = unsafe { get_text_buffer(0, true) };
let mut next_text_idx = 1;
let mut text_info =
homescreen_position_text(unwrap!(texts.get(0)), text_buffer, &mut icon_data);
let jpeg_pool = unsafe { get_jpeg_work_buffer(0, true).buffer.as_mut_slice() };
let jpeg_buffer = unsafe { get_jpeg_buffer(0, true) };
let mut jpeg_input = BufferInput(data);
let mut jpeg_output = BufferOutput::new(jpeg_buffer, WIDTH, 16);
let mut mcu_height = 8;
let mut jd: Option<JDEC> = JDEC::new(&mut jpeg_input, jpeg_pool).ok();
if let Some(dec) = &jd {
mcu_height = dec.mcu_height();
if !(dec.width() == WIDTH && dec.height() == HEIGHT && mcu_height <= 16) {
jd = None
}
}
jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output));
set_window(screen());
let mut blurring = BlurringContext::new();
// handling top edge case: preload the edge value N+1 times
blurring.compute_line_avgs(jpeg_output.buffer(), mcu_height);
for _ in 0..=BLUR_RADIUS {
blurring.vertical_avg_add();
}
blurring.inc_add();
// load enough values to be able to compute first line averages
for _ in 0..BLUR_RADIUS {
blurring.compute_line_avgs(jpeg_output.buffer(), mcu_height);
blurring.vertical_avg_add();
blurring.inc_add();
if (blurring.get_line_num() % mcu_height) == 0 {
jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output));
}
}
for y in 0..HEIGHT {
// several lines have been already decompressed before this loop, adjust for
// that
if y < HOMESCREEN_IMAGE_SIZE - (BLUR_RADIUS + 1) {
blurring.compute_line_avgs(jpeg_output.buffer(), mcu_height);
}
let done = homescreen_line_blurred(&icon_data, text_buffer, text_info, &blurring, y);
if done {
(text_info, next_text_idx) =
homescreen_next_text(texts, text_buffer, &mut icon_data, text_info, next_text_idx);
}
blurring.vertical_avg();
// handling bottom edge case: stop incrementing counter, adding the edge value
// for the rest of image
// the extra -1 is to indicate that this was the last decompressed line,
// in the next pass the docompression and compute_line_avgs won't happen
if y < HOMESCREEN_IMAGE_SIZE - (BLUR_RADIUS + 1) - 1 {
blurring.inc_add();
}
if y == HOMESCREEN_IMAGE_SIZE {
// reached end of image, clear avgs (display black)
blurring.clear();
}
// only start incrementing remove index when enough lines have been loaded
if y >= (BLUR_RADIUS) {
blurring.inc_rem();
}
if (blurring.get_line_num() % mcu_height) == 0 && (blurring.get_line_num() < HEIGHT) {
jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output));
}
}
dma2d_wait_for_transfer();
}
pub fn homescreen(
data: &[u8],
texts: &[HomescreenText],
notification: Option<HomescreenNotification>,
notification_only: bool,
) {
let mut icon_data = [0_u8; (HOMESCREEN_MAX_ICON_SIZE * HOMESCREEN_MAX_ICON_SIZE / 2) as usize];
let text_buffer = unsafe { get_text_buffer(0, true) };
let mut next_text_idx = 0;
let mut text_info = if let Some(notification) = notification {
bar_radius_buffer(
NOTIFICATION_BORDER,
0,
WIDTH - NOTIFICATION_BORDER * 2,
NOTIFICATION_HEIGHT,
2,
text_buffer,
);
let area = Rect::new(
Point::new(0, NOTIFICATION_BORDER),
Point::new(WIDTH, NOTIFICATION_HEIGHT + NOTIFICATION_BORDER),
);
HomescreenTextInfo {
text_area: area,
text_width: WIDTH,
text_color: notification.color,
icon_area: None,
}
} else {
next_text_idx += 1;
homescreen_position_text(unwrap!(texts.get(0)), text_buffer, &mut icon_data)
};
let jpeg_pool = unsafe { get_jpeg_work_buffer(0, true).buffer.as_mut_slice() };
let jpeg_buffer = unsafe { get_jpeg_buffer(0, true) };
let mut jpeg_input = BufferInput(data);
let mut jpeg_output = BufferOutput::new(jpeg_buffer, WIDTH, 16);
let mut mcu_height = 8;
let mut jd: Option<JDEC> = JDEC::new(&mut jpeg_input, jpeg_pool).ok();
if let Some(dec) = &jd {
mcu_height = dec.mcu_height();
if !(dec.width() == WIDTH && dec.height() == HEIGHT && mcu_height <= 16) {
jd = None
}
}
set_window(screen());
let mcu_height = mcu_height as i16;
for y in 0..HEIGHT {
if (y % mcu_height) == 0 {
jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output));
}
let done = homescreen_line(
&icon_data,
text_buffer,
text_info,
jpeg_output.buffer(),
mcu_height,
y,
);
if done {
if notification.is_some() && next_text_idx == 0 {
//finished notification area, let interrupt and draw the text
let notification = unwrap!(notification);
let style = TextStyle {
background_color: notification.color,
..theme::TEXT_BOLD
};
dma2d_wait_for_transfer();
icon_text_center(
text_info.text_area.center(),
notification.icon,
8,
notification.text,
style,
Offset::new(1, -2),
);
set_window(
screen()
.split_top(NOTIFICATION_HEIGHT + NOTIFICATION_BORDER)
.1,
);
}
if notification_only && next_text_idx == 0 {
dma2d_wait_for_transfer();
return;
}
(text_info, next_text_idx) =
homescreen_next_text(texts, text_buffer, &mut icon_data, text_info, next_text_idx);
}
}
dma2d_wait_for_transfer();
}

@ -33,7 +33,8 @@ use crate::{
obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED, INFO},
util::{
iter_into_array, iter_into_objs, upy_disable_animation, ConfirmBlob, PropsList,
iter_into_array, iter_into_objs, upy_disable_animation, upy_jpeg_info,
upy_jpeg_test, ConfirmBlob, PropsList,
},
},
},
@ -1299,6 +1300,14 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Disable animations, debug builds only."""
Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).as_obj(),
/// def jpeg_info(data: bytes) -> (width: int, height: int, mcu_height: int):
/// """Get JPEG image dimensions."""
Qstr::MP_QSTR_jpeg_info => obj_fn_1!(upy_jpeg_info).as_obj(),
/// def jpeg_test(data: bytes) -> bool:
/// """Test JPEG image."""
Qstr::MP_QSTR_jpeg_test => obj_fn_1!(upy_jpeg_test).as_obj(),
/// def confirm_action(
/// *,
/// title: str,

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

@ -78,6 +78,9 @@ pub const IMAGE_BG_TRIANGLE: &[u8] = include_res!("model_tt/res/triangle.toif");
pub const IMAGE_BG_BACK_BTN: &[u8] = include_res!("model_tt/res/back_btn.toif");
pub const IMAGE_BG_BACK_BTN_TALL: &[u8] = include_res!("model_tt/res/back_btn_tall.toif");
// Default homescreen
pub const IMAGE_HOMESCREEN: &[u8] = include_res!("model_tt/res/bg.jpg");
// Scrollbar/PIN dots.
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif");
pub const DOT_INACTIVE: &[u8] = include_res!("model_tt/res/scroll-inactive.toif");

@ -72,6 +72,16 @@ def disable_animation(disable: bool) -> None:
"""Disable animations, debug builds only."""
# rust/src/ui/model_tt/layout.rs
def jpeg_info(data: bytes) -> (width: int, height: int, mcu_height: int):
"""Get JPEG image dimensions."""
# rust/src/ui/model_tt/layout.rs
def jpeg_test(data: bytes) -> bool:
"""Test JPEG image."""
# rust/src/ui/model_tt/layout.rs
def confirm_action(
*,

@ -103,6 +103,8 @@ trezor.enums.DecredStakingSpendType
import trezor.enums.DecredStakingSpendType
trezor.enums.FailureType
import trezor.enums.FailureType
trezor.enums.HomescreenFormat
import trezor.enums.HomescreenFormat
trezor.enums.InputScriptType
import trezor.enums.InputScriptType
trezor.enums.MessageType

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
import storage.cache as storage_cache
import storage.device as storage_device
from trezor import config, utils, wire, workflow
from trezor.enums import MessageType
from trezor.enums import HomescreenFormat, MessageType
from trezor.messages import Success, UnlockPath
from . import workflow_handlers
@ -75,6 +75,7 @@ def get_features() -> Features:
pin_protection=config.has_pin(),
unlocked=config.is_unlocked(),
busy=busy_expiry_ms() > 0,
homescreen_format=HomescreenFormat.Jpeg240x240,
)
if utils.BITCOIN_ONLY:

@ -14,7 +14,7 @@ BRT_PROTECT_CALL = ButtonRequestType.ProtectCall # CACHE
def _validate_homescreen(homescreen: bytes) -> None:
from trezor import ui
import trezorui2
import storage.device as storage_device
if homescreen == b"":
@ -26,13 +26,17 @@ def _validate_homescreen(homescreen: bytes) -> None:
)
try:
w, h, toif_format = ui.display.toif_info(homescreen)
w, h, mcu_height = trezorui2.jpeg_info(homescreen)
except ValueError:
raise DataError("Invalid homescreen")
if w != 240 or h != 240:
raise DataError("Homescreen must be 240x240 pixel large")
if mcu_height > 16:
raise DataError("Unsupported jpeg type")
try:
trezorui2.jpeg_test(homescreen)
except ValueError:
raise DataError("Invalid homescreen")
if w != 144 or h != 144:
raise DataError("Homescreen must be 144x144 pixel large")
if toif_format != ui.display.TOIF_FULL_COLOR_BE:
raise DataError("Homescreen must be full-color TOIF image")
async def apply_settings(ctx: Context, msg: ApplySettings) -> Success:

@ -0,0 +1,6 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
Toif144x144 = 1
Jpeg240x240 = 2

@ -407,6 +407,10 @@ if TYPE_CHECKING:
PromptAlways = 1
PromptTemporarily = 2
class HomescreenFormat(IntEnum):
Toif144x144 = 1
Jpeg240x240 = 2
class Capability(IntEnum):
Bitcoin = 1
Bitcoin_like = 2

@ -39,6 +39,7 @@ if TYPE_CHECKING:
from trezor.enums import DecredStakingSpendType # noqa: F401
from trezor.enums import EthereumDataType # noqa: F401
from trezor.enums import FailureType # noqa: F401
from trezor.enums import HomescreenFormat # noqa: F401
from trezor.enums import InputScriptType # noqa: F401
from trezor.enums import MessageType # noqa: F401
from trezor.enums import MoneroNetworkType # noqa: F401
@ -2102,6 +2103,7 @@ if TYPE_CHECKING:
display_rotation: "int | None"
experimental_features: "bool | None"
busy: "bool | None"
homescreen_format: "HomescreenFormat | None"
def __init__(
self,
@ -2144,6 +2146,7 @@ if TYPE_CHECKING:
display_rotation: "int | None" = None,
experimental_features: "bool | None" = None,
busy: "bool | None" = None,
homescreen_format: "HomescreenFormat | None" = None,
) -> None:
pass

@ -332,7 +332,7 @@ class Layout(Component):
def _before_render(self) -> None:
# Before the first render, we dim the display.
backlight_fade(style.BACKLIGHT_DIM)
backlight_fade(style.BACKLIGHT_NONE)
# Clear the screen of any leftovers, make sure everything is marked for
# repaint (we can be running the same layout instance multiple times)
# and paint it.

@ -36,6 +36,7 @@ class RustLayout(ui.Layout):
button = loop.wait(io.BUTTON)
ui.display.clear()
self.layout.paint()
ui.refresh()
while True:
# Using `yield` instead of `await` to avoid allocations.
event, button_num = yield button
@ -46,6 +47,7 @@ class RustLayout(ui.Layout):
if msg is not None:
raise ui.Result(msg)
self.layout.paint()
ui.refresh()
def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator]
while True:
@ -55,6 +57,7 @@ class RustLayout(ui.Layout):
if msg is not None:
raise ui.Result(msg)
self.layout.paint()
ui.refresh()
async def confirm_action(

@ -52,6 +52,8 @@ class RustLayout(ui.Layout):
import storage.cache as storage_cache
painted = self.layout.paint()
ui.refresh()
if storage_cache.homescreen_shown is not None and painted:
storage_cache.homescreen_shown = None

@ -13,7 +13,7 @@ if TYPE_CHECKING:
if __debug__:
from trezor import io
from trezor import io, ui
from ... import Result
class _RustFidoLayoutImpl(RustLayout):
@ -42,6 +42,7 @@ if __debug__:
):
msg = self.layout.touch_event(event, x, y)
self.layout.paint()
ui.refresh()
if msg is not None:
raise Result(msg)

@ -20,6 +20,7 @@ class HomescreenBase(RustLayout):
def _paint(self) -> None:
self.layout.paint()
ui.refresh()
def _first_paint(self) -> None:
if storage_cache.homescreen_shown is not self.RENDER_INDICATOR:
@ -70,6 +71,7 @@ class Homescreen(HomescreenBase):
is_connected = await usbcheck
self.layout.usb_event(is_connected)
self.layout.paint()
ui.refresh()
def create_tasks(self) -> Tuple[loop.AwaitableTask, ...]:
return super().create_tasks() + (self.usb_checker_task(),)

@ -14,6 +14,7 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import os
from typing import TYPE_CHECKING, Optional, cast
import click
@ -59,7 +60,7 @@ def image_to_t1(filename: str) -> bytes:
return image.tobytes("raw", "1")
def image_to_tt(filename: str) -> bytes:
def image_to_toif_144x144(filename: str) -> bytes:
if filename.endswith(".toif"):
try:
toif_image = toif.load(filename)
@ -89,6 +90,40 @@ def image_to_tt(filename: str) -> bytes:
return toif_image.to_bytes()
def image_to_jpeg_240x240(filename: str) -> bytes:
if not (filename.endswith(".jpg") or filename.endswith(".jpeg")):
raise click.ClickException("Please use a jpg image")
elif not PIL_AVAILABLE:
raise click.ClickException(
"Image library is missing. Please install via 'pip install Pillow'."
)
else:
try:
image = Image.open(filename)
except Exception as e:
raise click.ClickException("Failed to open image") from e
if "progressive" in image.info:
raise click.ClickException("Progressive JPEG is not supported")
if image.size != (240, 240):
raise click.ClickException("Wrong size of image - should be 240x240")
image.close()
file_stats = os.stat(filename)
if file_stats.st_size > 16384:
raise click.ClickException("File is too big, please use maximum 16kB")
in_file = open(filename, "rb")
bytes = in_file.read()
in_file.close()
return bytes
def _should_remove(enable: Optional[bool], remove: bool) -> bool:
"""Helper to decide whether to remove something or not.
@ -208,7 +243,21 @@ def homescreen(client: "TrezorClient", filename: str) -> str:
if client.features.model == "1":
img = image_to_t1(filename)
else:
img = image_to_tt(filename)
if (
client.features.homescreen_format
== messages.HomescreenFormat.Jpeg240x240
):
img = image_to_jpeg_240x240(filename)
elif (
client.features.homescreen_format
== messages.HomescreenFormat.Toif144x144
or client.features.homescreen_format is None
):
img = image_to_toif_144x144(filename)
else:
raise click.ClickException(
"Unknown image format requested by the device."
)
return device.apply_settings(client, homescreen=img)

@ -437,6 +437,11 @@ class SafetyCheckLevel(IntEnum):
PromptTemporarily = 2
class HomescreenFormat(IntEnum):
Toif144x144 = 1
Jpeg240x240 = 2
class Capability(IntEnum):
Bitcoin = 1
Bitcoin_like = 2
@ -3137,6 +3142,7 @@ class Features(protobuf.MessageType):
39: protobuf.Field("display_rotation", "uint32", repeated=False, required=False, default=None),
40: protobuf.Field("experimental_features", "bool", repeated=False, required=False, default=None),
41: protobuf.Field("busy", "bool", repeated=False, required=False, default=None),
42: protobuf.Field("homescreen_format", "HomescreenFormat", repeated=False, required=False, default=None),
}
def __init__(
@ -3181,6 +3187,7 @@ class Features(protobuf.MessageType):
display_rotation: Optional["int"] = None,
experimental_features: Optional["bool"] = None,
busy: Optional["bool"] = None,
homescreen_format: Optional["HomescreenFormat"] = None,
) -> None:
self.capabilities: Sequence["Capability"] = capabilities if capabilities is not None else []
self.major_version = major_version
@ -3221,6 +3228,7 @@ class Features(protobuf.MessageType):
self.display_rotation = display_rotation
self.experimental_features = experimental_features
self.busy = busy
self.homescreen_format = homescreen_format
class LockDevice(protobuf.MessageType):

@ -133,6 +133,52 @@ def test_apply_settings_passphrase_on_device(client: Client):
def test_apply_homescreen_toif(client: Client):
img = b"TOIf\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV"
with pytest.raises(exceptions.TrezorFailure), client:
_set_expected_responses(client)
device.apply_settings(client, homescreen=img)
@pytest.mark.skip_t1
def test_apply_homescreen_jpeg(client: Client):
img = (
b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,"
b"\x00\x00\xff\xdb\x00C\x00\x02\x01\x01\x02\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x03\x05\x03\x03\x03\x03"
b"\x03\x06\x04\x04\x03\x05\x07\x06\x07\x07\x07\x06\x07\x07\x08\t\x0b\t\x08\x08\n\x08\x07\x07\n\r\n\n\x0b"
b"\x0c\x0c\x0c\x0c\x07\t\x0e\x0f\r\x0c\x0e\x0b\x0c\x0c\x0c\xff\xdb\x00C\x01\x02\x02\x02\x03\x03\x03\x06\x03"
b"\x03\x06\x0c\x08\x07\x08\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
b"\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
b"\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\xf0\x00\xf0\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00"
b"\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xff\xc4\x00\x14\x10\x01"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9f\xf0\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xff\xd9"
)
with client:
_set_expected_responses(client)
device.apply_settings(client, homescreen=img)
@ -141,26 +187,106 @@ def test_apply_homescreen_toif(client: Client):
device.apply_settings(client, homescreen=b"")
@pytest.mark.parametrize(
"toif_data",
[
# incomplete header
b"TOIf\x90\00\x90\x00~"
# wrong magic
b"XXXf\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV"
# wrong datasize in header
b"TOIf\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
# grayscale 144x144
b"TOIg\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV",
# fullcolor 128x128
b"TOIf\x80\x00\x80\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV",
],
)
@pytest.mark.skip_t1
def test_apply_homescreen_toif_fail(client: Client, toif_data):
def test_apply_homescreen_jpeg_progressive(client: Client):
img = (
b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,"
b"\x00\x00\xff\xdb\x00C\x00\x02\x01\x01\x02\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x03\x05\x03\x03\x03\x03"
b"\x03\x06\x04\x04\x03\x05\x07\x06\x07\x07\x07\x06\x07\x07\x08\t\x0b\t\x08\x08\n\x08\x07\x07\n\r\n\n\x0b"
b"\x0c\x0c\x0c\x0c\x07\t\x0e\x0f\r\x0c\x0e\x0b\x0c\x0c\x0c\xff\xdb\x00C\x01\x02\x02\x02\x03\x03\x03\x06\x03"
b"\x03\x06\x0c\x08\x07\x08\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
b"\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
b"\x0c\x0c\x0c\x0c\xff\xc2\x00\x11\x08\x00\xf0\x00\xf0\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00"
b"\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\xff\xc4\x00\x14\x01\x01"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x10\x03"
b"\x10\x00\x00\x01\x9f\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x03\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x90\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02a?\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x90\xff\xda\x00\x08\x01\x03\x01\x01?\x01a?\xff\xc4\x00\x14\x11\x01\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\xff\xda\x00\x08\x01\x02\x01\x01?\x01a?\xff\xc4"
b"\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\xff\xda\x00\x08\x01\x01"
b"\x00\x06?\x02a?\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90"
b"\xff\xda\x00\x08\x01\x01\x00\x01?!a?\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xc4\x00"
b"\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\xff\xda\x00\x08\x01\x03\x01"
b"\x01?\x10a?\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\xff"
b"\xda\x00\x08\x01\x02\x01\x01?\x10a?\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x90\xff\xda\x00\x08\x01\x01\x00\x01?\x10a?\xff\xd9"
)
with pytest.raises(exceptions.TrezorFailure), client:
client.use_pin_sequence([PIN4])
device.apply_settings(client, homescreen=toif_data)
_set_expected_responses(client)
device.apply_settings(client, homescreen=img)
@pytest.mark.skip_t1
def test_apply_homescreen_jpeg_wrong_size(client: Client):
img = (
b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,"
b"\x00\x00\xff\xdb\x00C\x00\x02\x01\x01\x02\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x03\x05\x03\x03\x03\x03"
b"\x03\x06\x04\x04\x03\x05\x07\x06\x07\x07\x07\x06\x07\x07\x08\t\x0b\t\x08\x08\n\x08\x07\x07\n\r\n\n\x0b"
b"\x0c\x0c\x0c\x0c\x07\t\x0e\x0f\r\x0c\x0e\x0b\x0c\x0c\x0c\xff\xdb\x00C\x01\x02\x02\x02\x03\x03\x03\x06\x03"
b"\x03\x06\x0c\x08\x07\x08\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
b"\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
b"\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\xf0\x00\xdc\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00"
b"\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xff\xc4\x00\x14\x10\x01"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9f\xf0\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x1f\xff\xd9"
)
with pytest.raises(exceptions.TrezorFailure), client:
_set_expected_responses(client)
device.apply_settings(client, homescreen=img)
@pytest.mark.skip_t2

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save