diff --git a/common/protob/messages-management.proto b/common/protob/messages-management.proto index 79e3bce95..0caa98320 100644 --- a/common/protob/messages-management.proto +++ b/common/protob/messages-management.proto @@ -32,8 +32,9 @@ enum SafetyCheckLevel { * Format of the homescreen image */ enum HomescreenFormat { - Toif144x144 = 1; - Jpeg240x240 = 2; + Toif = 1; // full-color toif + Jpeg = 2; // jpeg + ToiG = 3; // greyscale toif } /** @@ -121,11 +122,13 @@ 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 + optional HomescreenFormat homescreen_format = 42; // format of the homescreen, 1 = TOIf, 2 = jpg, 3 = TOIG optional bool hide_passphrase_from_host = 43; // should we hide the passphrase when it comes from host? optional string internal_model = 44; // internal model name optional uint32 unit_color = 45; // color of the unit/device optional bool unit_btconly = 46; // unit/device is intended as bitcoin only + optional uint32 homescreen_width = 47; // homescreen width in pixels + optional uint32 homescreen_height = 48; // homescreen height in pixels } /** diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index 5a1641808..09cfa1643 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -285,6 +285,8 @@ fn generate_trezorhal_bindings() { .allowlist_function("display_set_window") .allowlist_function("display_sync") .allowlist_var("DISPLAY_DATA_ADDRESS") + .allowlist_var("DISPLAY_RESX") + .allowlist_var("DISPLAY_RESY") .allowlist_type("toif_format_t") // fonts .allowlist_function("font_height") diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs index 060fa66b3..0a31a869d 100644 --- a/core/embed/rust/src/trezorhal/display.rs +++ b/core/embed/rust/src/trezorhal/display.rs @@ -4,6 +4,8 @@ use cty::c_int; use crate::trezorhal::buffers::BufferText; +pub use ffi::{DISPLAY_RESX, DISPLAY_RESY}; + #[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)] pub enum ToifFormat { FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE as _, diff --git a/core/embed/rust/src/ui/model_tr/constant.rs b/core/embed/rust/src/ui/model_tr/constant.rs index e43dfdcec..e897f9d66 100644 --- a/core/embed/rust/src/ui/model_tr/constant.rs +++ b/core/embed/rust/src/ui/model_tr/constant.rs @@ -1,7 +1,9 @@ use crate::ui::geometry::{Offset, Point, Rect}; -pub const WIDTH: i16 = 128; -pub const HEIGHT: i16 = 64; +use crate::trezorhal::display::{DISPLAY_RESX, DISPLAY_RESY}; + +pub const WIDTH: i16 = DISPLAY_RESX as _; +pub const HEIGHT: i16 = DISPLAY_RESY as _; pub const LINE_SPACE: i16 = 1; pub const FONT_BPP: i16 = 1; diff --git a/core/embed/rust/src/ui/model_tt/constant.rs b/core/embed/rust/src/ui/model_tt/constant.rs index f62eb3288..f84b97cf3 100644 --- a/core/embed/rust/src/ui/model_tt/constant.rs +++ b/core/embed/rust/src/ui/model_tt/constant.rs @@ -1,7 +1,9 @@ use crate::ui::geometry::{Offset, Point, Rect}; -pub const WIDTH: i16 = 240; -pub const HEIGHT: i16 = 240; +use crate::trezorhal::display::{DISPLAY_RESX, DISPLAY_RESY}; + +pub const WIDTH: i16 = DISPLAY_RESX as _; +pub const HEIGHT: i16 = DISPLAY_RESY as _; pub const LINE_SPACE: i16 = 4; pub const FONT_BPP: i16 = 4; diff --git a/core/embed/trezorhal/boards/stm32f429i-disc1.h b/core/embed/trezorhal/boards/stm32f429i-disc1.h index 8cdaa552b..755746cfa 100644 --- a/core/embed/trezorhal/boards/stm32f429i-disc1.h +++ b/core/embed/trezorhal/boards/stm32f429i-disc1.h @@ -1,6 +1,11 @@ #ifndef _STM32F429I_DISC1_H #define _STM32F429I_DISC1_H +#define MAX_DISPLAY_RESX 240 +#define MAX_DISPLAY_RESY 320 +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 320 + #define USE_I2C 1 #define USE_TOUCH 1 #define USE_SDRAM 1 diff --git a/core/embed/trezorhal/boards/trezor_t.h b/core/embed/trezorhal/boards/trezor_t.h index a2111853d..a5676fea9 100644 --- a/core/embed/trezorhal/boards/trezor_t.h +++ b/core/embed/trezorhal/boards/trezor_t.h @@ -1,6 +1,9 @@ #ifndef _TREZOR_T_H #define _TREZOR_T_H +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 240 + #define USE_SD_CARD 1 #define USE_I2C 1 #define USE_TOUCH 1 diff --git a/core/embed/trezorhal/displays/ltdc.h b/core/embed/trezorhal/displays/ltdc.h index 63848cf1a..7d57c8dcf 100644 --- a/core/embed/trezorhal/displays/ltdc.h +++ b/core/embed/trezorhal/displays/ltdc.h @@ -4,10 +4,6 @@ #include STM32_HAL_H -#define MAX_DISPLAY_RESX 240 -#define MAX_DISPLAY_RESY 320 -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 #define TREZOR_FONT_BPP 4 extern uint8_t *const DISPLAY_DATA_ADDRESS; diff --git a/core/embed/trezorhal/displays/st7789v.h b/core/embed/trezorhal/displays/st7789v.h index 5d15c66b6..012638c9b 100644 --- a/core/embed/trezorhal/displays/st7789v.h +++ b/core/embed/trezorhal/displays/st7789v.h @@ -6,8 +6,6 @@ // ILI9341V, GC9307 and ST7789V drivers support 240px x 320px display resolution #define MAX_DISPLAY_RESX 240 #define MAX_DISPLAY_RESY 320 -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 #define TREZOR_FONT_BPP 4 #ifdef USE_DISP_I8080_16BIT_DW diff --git a/core/src/apps/base.py b/core/src/apps/base.py index 2a5c44e13..53a1b88ed 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -44,6 +44,7 @@ def get_features() -> Features: from trezor.enums import Capability from trezor.messages import Features + from trezor.ui import WIDTH, HEIGHT from apps.common import mnemonic, safety_checks @@ -62,11 +63,17 @@ def get_features() -> Features: pin_protection=config.has_pin(), unlocked=config.is_unlocked(), busy=busy_expiry_ms() > 0, - homescreen_format=HomescreenFormat.Jpeg240x240, + homescreen_width=WIDTH, + homescreen_height=HEIGHT, unit_color=utils.unit_color(), unit_btconly=utils.unit_btconly(), ) + if utils.MODEL in ("1", "R"): + f.homescreen_format = HomescreenFormat.ToiG + else: + f.homescreen_format = HomescreenFormat.Jpeg + if utils.BITCOIN_ONLY: f.capabilities = [ Capability.Bitcoin, diff --git a/core/src/apps/management/apply_settings.py b/core/src/apps/management/apply_settings.py index 090b6d0b1..264c81037 100644 --- a/core/src/apps/management/apply_settings.py +++ b/core/src/apps/management/apply_settings.py @@ -15,27 +15,31 @@ if TYPE_CHECKING: BRT_PROTECT_CALL = ButtonRequestType.ProtectCall # CACHE -if utils.MODEL == "R": +if utils.MODEL in ("1", "R"): def _validate_homescreen_model_specific(homescreen: bytes) -> None: + from trezor.ui import WIDTH, HEIGHT + try: w, h, is_grayscale = trezorui2.toif_info(homescreen) except ValueError: raise DataError("Invalid homescreen") - if w != 128 or h != 64: - raise DataError("Homescreen must be 128x64 pixel large") + if w != WIDTH or h != HEIGHT: + raise DataError(f"Homescreen must be {WIDTH}x{HEIGHT} pixel large") if not is_grayscale: raise DataError("Homescreen must be grayscale") else: def _validate_homescreen_model_specific(homescreen: bytes) -> None: + from trezor.ui import WIDTH, HEIGHT + try: 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 w != WIDTH or h != HEIGHT: + raise DataError(f"Homescreen must be {WIDTH}x{HEIGHT} pixel large") if mcu_height > 16: raise DataError("Unsupported jpeg type") try: diff --git a/core/src/trezor/enums/HomescreenFormat.py b/core/src/trezor/enums/HomescreenFormat.py index 9d72d56d1..a21e6b274 100644 --- a/core/src/trezor/enums/HomescreenFormat.py +++ b/core/src/trezor/enums/HomescreenFormat.py @@ -2,5 +2,6 @@ # fmt: off # isort:skip_file -Toif144x144 = 1 -Jpeg240x240 = 2 +Toif = 1 +Jpeg = 2 +ToiG = 3 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 7d4e516aa..e562b556f 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -410,8 +410,9 @@ if TYPE_CHECKING: PromptTemporarily = 2 class HomescreenFormat(IntEnum): - Toif144x144 = 1 - Jpeg240x240 = 2 + Toif = 1 + Jpeg = 2 + ToiG = 3 class Capability(IntEnum): Bitcoin = 1 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 8cd432f99..f111e2375 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -2112,6 +2112,8 @@ if TYPE_CHECKING: internal_model: "str | None" unit_color: "int | None" unit_btconly: "bool | None" + homescreen_width: "int | None" + homescreen_height: "int | None" def __init__( self, @@ -2159,6 +2161,8 @@ if TYPE_CHECKING: internal_model: "str | None" = None, unit_color: "int | None" = None, unit_btconly: "bool | None" = None, + homescreen_width: "int | None" = None, + homescreen_height: "int | None" = None, ) -> None: pass diff --git a/python/src/trezorlib/cli/settings.py b/python/src/trezorlib/cli/settings.py index d7f98061a..1cd5118ab 100644 --- a/python/src/trezorlib/cli/settings.py +++ b/python/src/trezorlib/cli/settings.py @@ -69,47 +69,11 @@ def image_to_t1(filename: Path) -> bytes: return image.tobytes("raw", "1") -def image_to_tr(filename: Path) -> bytes: - if not PIL_AVAILABLE: - raise click.ClickException( - "Image library is missing. Please install via 'pip install Pillow'." - ) - - try: - image = Image.open(filename) - except Exception as e: - raise click.ClickException("Failed to load image") from e - - if image.size != T1_TR_IMAGE_SIZE: - if click.confirm( - f"Image is not 128x64, but {image.size}. Do you want to resize it automatically?", - default=True, - ): - image = image.resize(T1_TR_IMAGE_SIZE, Image.ANTIALIAS) - else: - raise click.ClickException("Wrong size of the image - should be 128x64") - - image = image.convert("1") # black-and-white - toif_image = toif.from_image(image) - return toif_image.to_bytes() - - -def image_to_tt(client: "TrezorClient", path: Path) -> bytes: - if client.features.homescreen_format == messages.HomescreenFormat.Jpeg240x240: - return image_to_jpeg_240x240(path) - elif client.features.homescreen_format in ( - messages.HomescreenFormat.Toif144x144, - None, - ): - return image_to_toif_144x144(path) - else: - raise click.ClickException("Unknown image format requested by the device.") - - -def image_to_toif_144x144(filename: Path) -> bytes: +def image_to_toif(filename: Path, width: int, height: int, greyscale: bool) -> bytes: if filename.suffix == ".toif": try: toif_image = toif.from_bytes(filename.read_bytes()) + image = toif_image.to_image() except Exception as e: raise click.ClickException("TOIF file is corrupted") from e @@ -127,16 +91,30 @@ def image_to_toif_144x144(filename: Path) -> bytes: "Failed to convert image to Trezor format" ) from e - if toif_image.size != (144, 144): - raise click.ClickException("Wrong size of image - should be 144x144") + if toif_image.size != (width, height): + if click.confirm( + f"Image is not {width}x{height}, but {image.size[0]}x{image.size[1]}. Do you want to resize it automatically?", + default=True, + ): + image = image.resize((width, height), Image.ANTIALIAS) + else: + raise click.ClickException( + f"Wrong size of image - should be {width}x{height}" + ) - if toif_image.mode != toif.ToifMode.full_color: + if greyscale: + image = image.convert("1") + toif_image = toif.from_image(image) + + if not greyscale and toif_image.mode != toif.ToifMode.full_color: raise click.ClickException("Wrong image mode - should be full_color") + if greyscale and toif_image.mode != toif.ToifMode.grayscale_eh: + raise click.ClickException("Wrong image mode - should be grayscale_eh") return toif_image.to_bytes() -def image_to_jpeg_240x240(filename: Path) -> bytes: +def image_to_jpeg(filename: Path, width: int, height: int) -> bytes: if filename.suffix in (".jpg", ".jpeg") and not PIL_AVAILABLE: click.echo("Warning: Image library is missing, skipping image validation.") return filename.read_bytes() @@ -151,8 +129,16 @@ def image_to_jpeg_240x240(filename: Path) -> bytes: except Exception as e: raise click.ClickException("Failed to open image") from e - if image.size != (240, 240): - raise click.ClickException("Wrong size of image - should be 240x240") + if image.size != (width, height): + if click.confirm( + f"Image is not {width}x{height}, but {image.size[0]}x{image.size[1]}. Do you want to resize it automatically?", + default=True, + ): + image = image.resize((width, height), Image.ANTIALIAS) + else: + raise click.ClickException( + f"Wrong size of image - should be {width}x{height}" + ) if image.mode != "RGB": image = image.convert("RGB") @@ -281,12 +267,45 @@ def homescreen(client: "TrezorClient", filename: str) -> str: if client.features.model == "1": img = image_to_t1(path) - elif client.features.model == "R": - img = image_to_tr(path) - elif client.features.model == "T": - img = image_to_tt(client, path) else: - raise click.ClickException("Unknown device model") + if client.features.homescreen_format == messages.HomescreenFormat.Jpeg: + width = ( + client.features.homescreen_width + if client.features.homescreen_width is not None + else 240 + ) + height = ( + client.features.homescreen_height + if client.features.homescreen_height is not None + else 240 + ) + img = image_to_jpeg(path, width, height) + elif client.features.homescreen_format == messages.HomescreenFormat.ToiG: + width = client.features.homescreen_width + height = client.features.homescreen_height + if width is None or height is None: + raise click.ClickException("Device did not report homescreen size.") + img = image_to_toif(path, width, height, True) + elif ( + client.features.homescreen_format == messages.HomescreenFormat.Toif + or client.features.homescreen_format is None + ): + width = ( + client.features.homescreen_width + if client.features.homescreen_width is not None + else 144 + ) + height = ( + client.features.homescreen_height + if client.features.homescreen_height is not None + else 144 + ) + img = image_to_toif(path, width, height, False) + + else: + raise click.ClickException( + "Unknown image format requested by the device." + ) return device.apply_settings(client, homescreen=img) diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 1d26e3015..2d6926b4c 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -440,8 +440,9 @@ class SafetyCheckLevel(IntEnum): class HomescreenFormat(IntEnum): - Toif144x144 = 1 - Jpeg240x240 = 2 + Toif = 1 + Jpeg = 2 + ToiG = 3 class Capability(IntEnum): @@ -3163,6 +3164,8 @@ class Features(protobuf.MessageType): 44: protobuf.Field("internal_model", "string", repeated=False, required=False, default=None), 45: protobuf.Field("unit_color", "uint32", repeated=False, required=False, default=None), 46: protobuf.Field("unit_btconly", "bool", repeated=False, required=False, default=None), + 47: protobuf.Field("homescreen_width", "uint32", repeated=False, required=False, default=None), + 48: protobuf.Field("homescreen_height", "uint32", repeated=False, required=False, default=None), } def __init__( @@ -3212,6 +3215,8 @@ class Features(protobuf.MessageType): internal_model: Optional["str"] = None, unit_color: Optional["int"] = None, unit_btconly: Optional["bool"] = None, + homescreen_width: Optional["int"] = None, + homescreen_height: Optional["int"] = None, ) -> None: self.capabilities: Sequence["Capability"] = capabilities if capabilities is not None else [] self.major_version = major_version @@ -3257,6 +3262,8 @@ class Features(protobuf.MessageType): self.internal_model = internal_model self.unit_color = unit_color self.unit_btconly = unit_btconly + self.homescreen_width = homescreen_width + self.homescreen_height = homescreen_height class LockDevice(protobuf.MessageType):