|
|
@ -16,9 +16,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
import click
|
|
|
|
import click
|
|
|
|
|
|
|
|
|
|
|
|
from .. import device, messages
|
|
|
|
from .. import device, firmware, messages, toif
|
|
|
|
from . import ChoiceType, with_client
|
|
|
|
from . import ChoiceType, with_client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
from PIL import Image
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
|
|
Image = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270}
|
|
|
|
ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270}
|
|
|
|
SAFETY_LEVELS = {
|
|
|
|
SAFETY_LEVELS = {
|
|
|
|
"strict": messages.SafetyCheckLevel.Strict,
|
|
|
|
"strict": messages.SafetyCheckLevel.Strict,
|
|
|
@ -26,6 +32,51 @@ SAFETY_LEVELS = {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def image_to_t1(filename: str) -> bytes:
|
|
|
|
|
|
|
|
if Image is None:
|
|
|
|
|
|
|
|
raise click.ClickException(
|
|
|
|
|
|
|
|
"Image library is missing. Please install via 'pip install Pillow'."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
image = Image.open(filename)
|
|
|
|
|
|
|
|
if image.size != (128, 64):
|
|
|
|
|
|
|
|
raise click.ClickException("Wrong size of the image - should be 128x64")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
image = image.convert("1")
|
|
|
|
|
|
|
|
return image.tobytes("raw", "1")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def image_to_tt(filename: str) -> bytes:
|
|
|
|
|
|
|
|
if filename.endswith(".toif"):
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
toif_image = toif.load(filename)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
raise click.ClickException("TOIF file is corrupted") from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elif Image is None:
|
|
|
|
|
|
|
|
raise click.ClickException(
|
|
|
|
|
|
|
|
"Image library is missing. Please install via 'pip install Pillow'."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
image = Image.open(filename)
|
|
|
|
|
|
|
|
toif_image = toif.from_image(image)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
raise click.ClickException(
|
|
|
|
|
|
|
|
"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.mode != firmware.ToifMode.full_color:
|
|
|
|
|
|
|
|
raise click.ClickException("Wrong image mode - should be full_color")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toif_image = toif.from_image(image)
|
|
|
|
|
|
|
|
return toif_image.to_bytes()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@click.group(name="set")
|
|
|
|
@click.group(name="set")
|
|
|
|
def cli():
|
|
|
|
def cli():
|
|
|
|
"""Device settings."""
|
|
|
|
"""Device settings."""
|
|
|
@ -108,36 +159,27 @@ def flags(client, flags):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
|
|
|
@cli.command()
|
|
|
|
@click.argument(
|
|
|
|
@click.argument("filename")
|
|
|
|
"filename", type=click.Path(dir_okay=False, readable=True), required=False
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
@click.option(
|
|
|
|
@click.option(
|
|
|
|
"-f", "--filename", "_ignore", is_flag=True, hidden=True, expose_value=False
|
|
|
|
"-f", "--filename", "_ignore", is_flag=True, hidden=True, expose_value=False
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@with_client
|
|
|
|
@with_client
|
|
|
|
def homescreen(client, filename):
|
|
|
|
def homescreen(client, filename):
|
|
|
|
"""Set new homescreen."""
|
|
|
|
"""Set new homescreen.
|
|
|
|
if filename is None:
|
|
|
|
|
|
|
|
img = b"\x00"
|
|
|
|
To revert to default homescreen, use 'trezorctl set homescreen default'
|
|
|
|
elif filename.endswith(".toif"):
|
|
|
|
"""
|
|
|
|
img = open(filename, "rb").read()
|
|
|
|
if filename == "default":
|
|
|
|
if img[:8] != b"TOIf\x90\x00\x90\x00":
|
|
|
|
img = b""
|
|
|
|
raise click.ClickException("File is not a TOIF file with size of 144x144")
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
from PIL import Image
|
|
|
|
# use Click's facility to validate the path for us
|
|
|
|
|
|
|
|
param = click.Path(dir_okay=False, readable=True, exists=True)
|
|
|
|
im = Image.open(filename)
|
|
|
|
param.convert(filename, None, None)
|
|
|
|
if im.size != (128, 64):
|
|
|
|
if client.features.model == "1":
|
|
|
|
raise click.ClickException("Wrong size of the image")
|
|
|
|
img = image_to_t1(filename)
|
|
|
|
im = im.convert("1")
|
|
|
|
else:
|
|
|
|
pix = im.load()
|
|
|
|
img = image_to_tt(filename)
|
|
|
|
img = bytearray(1024)
|
|
|
|
|
|
|
|
for j in range(64):
|
|
|
|
|
|
|
|
for i in range(128):
|
|
|
|
|
|
|
|
if pix[i, j]:
|
|
|
|
|
|
|
|
o = i + j * 128
|
|
|
|
|
|
|
|
img[o // 8] |= 1 << (7 - o % 8)
|
|
|
|
|
|
|
|
img = bytes(img)
|
|
|
|
|
|
|
|
return device.apply_settings(client, homescreen=img)
|
|
|
|
return device.apply_settings(client, homescreen=img)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|