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:
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.
|
||||
///
|
||||
|
59
core/embed/rust/src/storage/mod.rs
Normal file
59
core/embed/rust/src/storage/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
621
core/embed/rust/src/ui/model_tt/component/homescreen/render.rs
Normal file
621
core/embed/rust/src/ui/model_tt/component/homescreen/render.rs
Normal 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();
|
||||
}
|
@ -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,
|
||||
|
BIN
core/embed/rust/src/ui/model_tt/res/bg.jpg
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/bg.jpg
Normal file
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:
|
||||
|
6
core/src/trezor/enums/HomescreenFormat.py
Normal file
6
core/src/trezor/enums/HomescreenFormat.py
Normal file
@ -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…
Reference in New Issue
Block a user