1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-14 17:31:04 +00:00

feat(core/rust): homescreen background image

[no changelog]
This commit is contained in:
tychovrahe 2022-10-13 13:37:02 +02:00 committed by TychoVrahe
parent 14f8e88e01
commit d00e87ea80
41 changed files with 2419 additions and 1174 deletions

View File

@ -27,6 +27,15 @@ enum SafetyCheckLevel {
PromptTemporarily = 2; // like PromptAlways but reverts to Strict after reboot 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 * Request: Reset device to default state and ask for device details
* @start * @start
@ -112,6 +121,7 @@ message Features {
optional uint32 display_rotation = 39; // in degrees from North optional uint32 display_rotation = 39; // in degrees from North
optional bool experimental_features = 40; // are experimental message types enabled? optional bool experimental_features = 40; // are experimental message types enabled?
optional bool busy = 41; // is the device busy, showing "Do not disconnect"? 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
} }
/** /**

View File

@ -133,7 +133,7 @@ env.Replace(
'-fstack-protector-all ' '-fstack-protector-all '
+ CPU_CCFLAGS + CCFLAGS_MOD, + CPU_CCFLAGS + CCFLAGS_MOD,
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB', 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=[ CPPPATH=[
'embed/boardloader', 'embed/boardloader',
'embed/trezorhal', 'embed/trezorhal',

View File

@ -192,7 +192,7 @@ env.Replace(
'-fstack-protector-all ' '-fstack-protector-all '
+ CPU_CCFLAGS + CCFLAGS_MOD, + CPU_CCFLAGS + CCFLAGS_MOD,
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB', 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=[ CPPPATH=[
'embed/rust', 'embed/rust',
'embed/bootloader', 'embed/bootloader',

View File

@ -183,6 +183,7 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/crc32.c', 'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c', 'vendor/micropython/lib/uzlib/tinflate.c',
] ]
CPPDEFINES_MOD += [ CPPDEFINES_MOD += [
'TREZOR_UI2', 'TREZOR_UI2',
'USE_RUST_LOADER' 'USE_RUST_LOADER'
@ -478,7 +479,7 @@ env.Replace(
'-fstack-protector-all ' '-fstack-protector-all '
+ CPU_CCFLAGS + CCFLAGS_MOD, + CPU_CCFLAGS + CCFLAGS_MOD,
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB', 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=[ CPPPATH=[
'.', '.',
'embed/rust', 'embed/rust',

View File

@ -24,15 +24,12 @@
#if USE_DMA2D #if USE_DMA2D
#if defined BOOTLOADER
#define BUFFER_SECTION __attribute__((section(".buf")))
#else
#define BUFFER_SECTION
#endif
#define BUFFERS_16BPP 3 #define BUFFERS_16BPP 3
#define BUFFERS_4BPP 3 #define BUFFERS_4BPP 3
#define BUFFERS_TEXT 1 #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 text_buffer_height = FONT_MAX_HEIGHT;
const int32_t buffer_width = DISPLAY_RESX; 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_16bpp_t line_buffers_16bpp[BUFFERS_16BPP];
BUFFER_SECTION line_buffer_4bpp_t line_buffers_4bpp[BUFFERS_4BPP]; BUFFER_SECTION line_buffer_4bpp_t line_buffers_4bpp[BUFFERS_4BPP];
BUFFER_SECTION buffer_text_t text_buffers[BUFFERS_TEXT]; 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) { line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear) {
if (idx >= BUFFERS_16BPP) { if (idx >= BUFFERS_16BPP) {
@ -71,4 +71,37 @@ buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear) {
return &text_buffers[idx]; 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 #endif

View File

@ -27,7 +27,7 @@
#define BUFFER_PIXELS DISPLAY_RESX #define BUFFER_PIXELS DISPLAY_RESX
#define TEXT_BUFFER_HEIGHT 24 #define TEXT_BUFFER_HEIGHT 32
#if TEXT_BUFFER_HEIGHT < FONT_MAX_HEIGHT #if TEXT_BUFFER_HEIGHT < FONT_MAX_HEIGHT
#error Text buffer height is too small, please adjust to match used fonts #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_16BPP_SIZE BUFFER_PIXELS * 2
#define LINE_BUFFER_4BPP_SIZE BUFFER_PIXELS / 2 #define LINE_BUFFER_4BPP_SIZE BUFFER_PIXELS / 2
#define TEXT_BUFFER_SIZE (BUFFER_PIXELS * TEXT_BUFFER_HEIGHT) / 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 { typedef __attribute__((aligned(4))) struct {
uint8_t buffer[LINE_BUFFER_16BPP_SIZE]; uint8_t buffer[LINE_BUFFER_16BPP_SIZE];
@ -49,11 +67,26 @@ typedef __attribute__((aligned(4))) struct {
uint8_t buffer[TEXT_BUFFER_SIZE]; uint8_t buffer[TEXT_BUFFER_SIZE];
} buffer_text_t; } 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 text_buffer_height;
extern const int32_t buffer_width; extern const int32_t buffer_width;
line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear); 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); 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_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 #endif // _BUFFERS_H

View File

@ -149,6 +149,45 @@ void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b,
PIXELDATA_DIRTY(); 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) #define UZLIB_WINDOW_SIZE (1 << 10)
static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window, static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window,

View File

@ -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(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, void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b,
uint8_t r); 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, bool display_toif_info(const uint8_t *buf, uint32_t len, uint16_t *out_w,
uint16_t *out_h, toif_format_t *out_format); uint16_t *out_h, toif_format_t *out_format);

View File

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

View File

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

View File

@ -287,6 +287,7 @@ fn generate_trezorhal_bindings() {
.allowlist_function("display_text_width") .allowlist_function("display_text_width")
.allowlist_function("display_bar") .allowlist_function("display_bar")
.allowlist_function("display_bar_radius") .allowlist_function("display_bar_radius")
.allowlist_function("display_bar_radius_buffer")
.allowlist_function("display_icon") .allowlist_function("display_icon")
.allowlist_function("display_image") .allowlist_function("display_image")
.allowlist_function("display_toif_info") .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_16bpp")
.allowlist_function("buffers_get_line_buffer_4bpp") .allowlist_function("buffers_get_line_buffer_4bpp")
.allowlist_function("buffers_get_text_buffer") .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("text_buffer_height")
.allowlist_var("buffer_width") .allowlist_var("buffer_width")
//usb //usb

View File

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

View File

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

View File

@ -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> { impl<T: ?Sized> Gc<T> {
/// Construct a `Gc` from a raw pointer. /// Construct a `Gc` from a raw pointer.
/// ///

View File

@ -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(())
}
}

View File

@ -1,9 +1,11 @@
use super::ffi; use super::ffi;
pub use ffi::{ pub use ffi::{
buffer_text_t as BufferText, line_buffer_16bpp_t as LineBuffer16Bpp, buffer_blurring_t as BlurringBuffer, buffer_text_t as BufferText,
line_buffer_4bpp_t as LineBuffer4Bpp, 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 /// 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()) 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())
}
}

View File

@ -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) { pub fn icon(x: i16, y: i16, w: i16, h: i16, data: &[u8], fgcolor: u16, bgcolor: u16) {
unsafe { unsafe {
ffi::display_icon( ffi::display_icon(

View File

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

View File

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

View File

@ -19,6 +19,15 @@ use crate::{
use cstr_core::cstr; use cstr_core::cstr;
use heapless::Vec; 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> { pub fn iter_into_objs<const N: usize>(iterable: Obj) -> Result<[Obj; N], Error> {
let err = Error::ValueError(cstr!("Invalid iterable length")); let err = Error::ValueError(cstr!("Invalid iterable length"));
let mut vec = Vec::<Obj, N>::new(); 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) } 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) }
}

View File

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

View File

@ -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();
}

View File

@ -33,7 +33,8 @@ use crate::{
obj::{ComponentMsgObj, LayoutObj}, obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED, INFO}, result::{CANCELLED, CONFIRMED, INFO},
util::{ 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.""" /// """Disable animations, debug builds only."""
Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).as_obj(), 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( /// def confirm_action(
/// *, /// *,
/// title: str, /// title: str,

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -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: &[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"); 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. // Scrollbar/PIN dots.
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif"); 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"); pub const DOT_INACTIVE: &[u8] = include_res!("model_tt/res/scroll-inactive.toif");

View File

@ -72,6 +72,16 @@ def disable_animation(disable: bool) -> None:
"""Disable animations, debug builds only.""" """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 # rust/src/ui/model_tt/layout.rs
def confirm_action( def confirm_action(
*, *,

View File

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

View File

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

View File

@ -14,7 +14,7 @@ BRT_PROTECT_CALL = ButtonRequestType.ProtectCall # CACHE
def _validate_homescreen(homescreen: bytes) -> None: def _validate_homescreen(homescreen: bytes) -> None:
from trezor import ui import trezorui2
import storage.device as storage_device import storage.device as storage_device
if homescreen == b"": if homescreen == b"":
@ -26,13 +26,17 @@ def _validate_homescreen(homescreen: bytes) -> None:
) )
try: 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: except ValueError:
raise DataError("Invalid homescreen") 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: async def apply_settings(ctx: Context, msg: ApplySettings) -> Success:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@
# You should have received a copy of the License along with this library. # 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>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import os
from typing import TYPE_CHECKING, Optional, cast from typing import TYPE_CHECKING, Optional, cast
import click import click
@ -59,7 +60,7 @@ def image_to_t1(filename: str) -> bytes:
return image.tobytes("raw", "1") return image.tobytes("raw", "1")
def image_to_tt(filename: str) -> bytes: def image_to_toif_144x144(filename: str) -> bytes:
if filename.endswith(".toif"): if filename.endswith(".toif"):
try: try:
toif_image = toif.load(filename) toif_image = toif.load(filename)
@ -89,6 +90,40 @@ def image_to_tt(filename: str) -> bytes:
return toif_image.to_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: def _should_remove(enable: Optional[bool], remove: bool) -> bool:
"""Helper to decide whether to remove something or not. """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": if client.features.model == "1":
img = image_to_t1(filename) img = image_to_t1(filename)
else: 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) return device.apply_settings(client, homescreen=img)

View File

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

View File

@ -133,6 +133,52 @@ def test_apply_settings_passphrase_on_device(client: Client):
def test_apply_homescreen_toif(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" 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: with client:
_set_expected_responses(client) _set_expected_responses(client)
device.apply_settings(client, homescreen=img) device.apply_settings(client, homescreen=img)
@ -141,26 +187,106 @@ def test_apply_homescreen_toif(client: Client):
device.apply_settings(client, homescreen=b"") 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 @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: with pytest.raises(exceptions.TrezorFailure), client:
client.use_pin_sequence([PIN4]) _set_expected_responses(client)
device.apply_settings(client, homescreen=toif_data) 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 @pytest.mark.skip_t2

File diff suppressed because it is too large Load Diff