mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-03 12:00:59 +00:00
Merge branch 'master' into release/23.09
This commit is contained in:
commit
bb5b91b920
@ -349,10 +349,10 @@ combine: ## combine boardloader + bootloader + prodtest into one combined image
|
||||
$(PRODTEST_BUILD_DIR)/combined.bin
|
||||
|
||||
upload: ## upload firmware using trezorctl
|
||||
trezorctl firmware_update -f $(FIRMWARE_BUILD_DIR)/firmware.bin
|
||||
trezorctl firmware_update -s -f $(FIRMWARE_BUILD_DIR)/firmware.bin
|
||||
|
||||
upload_prodtest: ## upload prodtest using trezorctl
|
||||
trezorctl firmware_update -f $(PRODTEST_BUILD_DIR)/prodtest.bin
|
||||
trezorctl firmware_update -s -f $(PRODTEST_BUILD_DIR)/prodtest.bin
|
||||
|
||||
coverage: ## generate coverage report
|
||||
./tools/coverage-report
|
||||
|
@ -136,6 +136,7 @@ if TREZOR_MODEL in ('R', ):
|
||||
]
|
||||
SOURCE_TREZORHAL += [
|
||||
'embed/trezorhal/unix/secret.c',
|
||||
'embed/trezorhal/unix/optiga_hal.c',
|
||||
]
|
||||
|
||||
SOURCE_UNIX = [
|
||||
@ -206,7 +207,7 @@ env.Replace(
|
||||
'TREZOR_EMULATOR',
|
||||
CPU_MODEL,
|
||||
'HW_MODEL=' + MODEL_AS_NUMBER,
|
||||
'HW_REVISION=' + ('6' if TREZOR_MODEL in ('R',) else '0'),
|
||||
'HW_REVISION=' + ('10' if TREZOR_MODEL in ('R',) else '0'),
|
||||
'TREZOR_MODEL_'+TREZOR_MODEL,
|
||||
'TREZOR_BOARD=\\"boards/board-unix.h\\"',
|
||||
'MCU_TYPE='+CPU_MODEL,
|
||||
|
@ -380,6 +380,21 @@ env = Environment(ENV=os.environ, CFLAGS=f"{ARGUMENTS.get('CFLAGS', '')} -DPRODU
|
||||
|
||||
FEATURES_AVAILABLE = tools.configure_board(TREZOR_MODEL, FEATURES_WANTED, env, CPPDEFINES_HAL, SOURCE_HAL, PATH_HAL)
|
||||
|
||||
|
||||
if TREZOR_MODEL in ('T', 'DISC1'):
|
||||
UI_LAYOUT = 'UI_LAYOUT_TT'
|
||||
ui_layout_feature = 'model_tt'
|
||||
elif TREZOR_MODEL in ('1', 'R'):
|
||||
UI_LAYOUT = 'UI_LAYOUT_TR'
|
||||
ui_layout_feature = 'model_tr'
|
||||
else:
|
||||
raise ValueError('Unknown Trezor model')
|
||||
|
||||
if 'sd_card' in FEATURES_AVAILABLE:
|
||||
SDCARD = True
|
||||
else:
|
||||
SDCARD = False
|
||||
|
||||
env.Tool('micropython')
|
||||
|
||||
env.Replace(
|
||||
@ -430,6 +445,7 @@ env.Replace(
|
||||
'FIRMWARE',
|
||||
'TREZOR_MODEL_'+TREZOR_MODEL,
|
||||
'USE_HAL_DRIVER',
|
||||
UI_LAYOUT,
|
||||
] + CPPDEFINES_MOD + CPPDEFINES_HAL,
|
||||
ASFLAGS=env.get('ENV')['CPU_ASFLAGS'],
|
||||
ASPPFLAGS='$CFLAGS $CCFLAGS',
|
||||
@ -518,7 +534,7 @@ if FROZEN:
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'trezor/sdcard.py',
|
||||
] if TREZOR_MODEL not in ('T',) else []
|
||||
] if not SDCARD else []
|
||||
))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/crypto/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/*.py'))
|
||||
@ -529,27 +545,27 @@ if FROZEN:
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/fido.py',
|
||||
] if not EVERYTHING else []
|
||||
))
|
||||
if TREZOR_MODEL in ('T', 'DISC1'):
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/*.py',
|
||||
if UI_LAYOUT == 'UI_LAYOUT_TT':
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/fido.py',
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/tt/fido.py',
|
||||
] if not EVERYTHING else []
|
||||
))
|
||||
elif TREZOR_MODEL in ('1', 'R'):
|
||||
elif UI_LAYOUT == 'UI_LAYOUT_TR':
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tr/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/tr/fido.py',
|
||||
] if not EVERYTHING else []
|
||||
))
|
||||
else:
|
||||
raise ValueError('Unknown Trezor model')
|
||||
raise ValueError('Unknown layout')
|
||||
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/*.py'))
|
||||
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'storage/sd_salt.py',
|
||||
] if "sd_card" not in FEATURES_AVAILABLE else []
|
||||
] if not SDCARD else []
|
||||
))
|
||||
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py'))
|
||||
@ -573,14 +589,14 @@ if FROZEN:
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/common/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'apps/common/sdcard.py',
|
||||
] if "sd_card" not in FEATURES_AVAILABLE else []
|
||||
] if not SDCARD else []
|
||||
))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'apps/management/sd_protect.py',
|
||||
] if 'sd_card' not in FEATURES_AVAILABLE else [])
|
||||
] if not SDCARD else [])
|
||||
)
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/misc/*.py'))
|
||||
@ -645,7 +661,8 @@ if FROZEN:
|
||||
source_dir=SOURCE_PY_DIR,
|
||||
bitcoin_only=BITCOIN_ONLY,
|
||||
backlight='backlight' in FEATURES_AVAILABLE,
|
||||
optiga='optiga' in FEATURES_AVAILABLE
|
||||
optiga='optiga' in FEATURES_AVAILABLE,
|
||||
ui_layout=UI_LAYOUT,
|
||||
)
|
||||
|
||||
source_mpyc = env.FrozenCFile(
|
||||
@ -681,13 +698,7 @@ def cargo_build():
|
||||
else:
|
||||
profile = ''
|
||||
|
||||
# T1 does not have its own Rust feature, it shares it with TR
|
||||
if TREZOR_MODEL in ('1', 'R'):
|
||||
model_feature = 'model_tr'
|
||||
else:
|
||||
model_feature = 'model_tt'
|
||||
|
||||
features = ['micropython', 'protobuf', model_feature]
|
||||
features = ['micropython', 'protobuf', ui_layout_feature]
|
||||
if BITCOIN_ONLY == '1':
|
||||
features.append('bitcoin_only')
|
||||
features.append('ui')
|
||||
|
@ -387,7 +387,6 @@ SOURCE_UNIX = [
|
||||
if TREZOR_MODEL in ('T', 'R'):
|
||||
SOURCE_UNIX += [
|
||||
'embed/trezorhal/unix/sbu.c',
|
||||
'embed/trezorhal/unix/sdcard.c',
|
||||
]
|
||||
|
||||
if TREZOR_MODEL == 'R':
|
||||
@ -424,6 +423,24 @@ else:
|
||||
|
||||
env = Environment(ENV=os.environ, CFLAGS='%s -DPYOPT=%s -DBITCOIN_ONLY=%s %s' % (ARGUMENTS.get('CFLAGS', ''), PYOPT, BITCOIN_ONLY, STATIC))
|
||||
|
||||
|
||||
if TREZOR_MODEL in ('T',):
|
||||
UI_LAYOUT = 'UI_LAYOUT_TT'
|
||||
ui_layout_feature = 'model_tt'
|
||||
elif TREZOR_MODEL in ('1', 'R'):
|
||||
UI_LAYOUT = 'UI_LAYOUT_TR'
|
||||
ui_layout_feature = 'model_tr'
|
||||
else:
|
||||
raise ValueError('Unknown Trezor model')
|
||||
|
||||
if TREZOR_MODEL in ('T',):
|
||||
SDCARD = True
|
||||
SOURCE_UNIX += [
|
||||
'embed/trezorhal/unix/sdcard.c',
|
||||
]
|
||||
else:
|
||||
SDCARD = False
|
||||
|
||||
env.Tool('micropython')
|
||||
|
||||
env.Replace(
|
||||
@ -508,6 +525,7 @@ env.Replace(
|
||||
'TREZOR_BOARD=\\"boards/board-unix.h\\"',
|
||||
'MCU_TYPE='+CPU_MODEL,
|
||||
('MP_CONFIGFILE', '\\"embed/unix/mpconfigport.h\\"'),
|
||||
UI_LAYOUT,
|
||||
] + CPPDEFINES_MOD,
|
||||
ASPPFLAGS='$CFLAGS $CCFLAGS', )
|
||||
|
||||
@ -599,7 +617,7 @@ if FROZEN:
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'trezor/sdcard.py',
|
||||
] if TREZOR_MODEL not in ('T',) else []
|
||||
] if not SDCARD else []
|
||||
))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/crypto/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/*.py'))
|
||||
@ -610,27 +628,27 @@ if FROZEN:
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/fido.py',
|
||||
] if not EVERYTHING else []
|
||||
))
|
||||
if TREZOR_MODEL in ('T',):
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/*.py',
|
||||
if UI_LAYOUT == 'UI_LAYOUT_TT':
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/fido.py',
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/tt/fido.py',
|
||||
] if not EVERYTHING else []
|
||||
))
|
||||
elif TREZOR_MODEL in ('R'):
|
||||
elif UI_LAYOUT == 'UI_LAYOUT_TR':
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tr/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'trezor/ui/layouts/tr/fido.py',
|
||||
] if not EVERYTHING else []
|
||||
))
|
||||
else:
|
||||
raise ValueError('Unknown Trezor model')
|
||||
raise ValueError('Unknown layout')
|
||||
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/*.py'))
|
||||
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'storage/sd_salt.py',
|
||||
] if TREZOR_MODEL not in ('T',) else []
|
||||
] if not SDCARD else []
|
||||
))
|
||||
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py'))
|
||||
@ -654,14 +672,14 @@ if FROZEN:
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/common/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'apps/common/sdcard.py',
|
||||
] if TREZOR_MODEL not in ('T',) else []
|
||||
] if not SDCARD else []
|
||||
))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py'))
|
||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py',
|
||||
exclude=[
|
||||
SOURCE_PY_DIR + 'apps/management/sd_protect.py',
|
||||
] if TREZOR_MODEL not in ('T',) else [] + [
|
||||
] if not SDCARD else [] + [
|
||||
SOURCE_PY_DIR + 'apps/management/authenticate_device.py',
|
||||
] if TREZOR_MODEL not in ('R',) else [])
|
||||
)
|
||||
@ -728,7 +746,8 @@ if FROZEN:
|
||||
source_dir=SOURCE_PY_DIR,
|
||||
bitcoin_only=BITCOIN_ONLY,
|
||||
backlight=TREZOR_MODEL in ('T',),
|
||||
optiga=TREZOR_MODEL in ('R',)
|
||||
optiga=TREZOR_MODEL in ('R',),
|
||||
ui_layout=UI_LAYOUT,
|
||||
)
|
||||
|
||||
source_mpyc = env.FrozenCFile(
|
||||
@ -764,9 +783,7 @@ RUST_LIB = 'trezor_lib'
|
||||
RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
|
||||
|
||||
def cargo_build():
|
||||
# T1 does not have its own Rust feature, it shares it with TR
|
||||
model_feature = 'model_tr' if TREZOR_MODEL == '1' else f'model_t{TREZOR_MODEL.lower()}'
|
||||
features = ['micropython', 'protobuf', model_feature]
|
||||
features = ['micropython', 'protobuf', ui_layout_feature]
|
||||
if BITCOIN_ONLY == '1':
|
||||
features.append('bitcoin_only')
|
||||
features.append('ui')
|
||||
|
1
core/embed/bootloader/.changelog.d/3122.changed
Normal file
1
core/embed/bootloader/.changelog.d/3122.changed
Normal file
@ -0,0 +1 @@
|
||||
No longer erases seed when firmware is corrupted but firmware header is correct and signed. Added firmware corrupted info to bootloader screen.
|
@ -192,16 +192,18 @@ void ui_screen_boot_click(void) {
|
||||
void ui_screen_welcome(void) { screen_welcome(); }
|
||||
|
||||
uint32_t ui_screen_intro(const vendor_header *const vhdr,
|
||||
const image_header *const hdr) {
|
||||
const image_header *const hdr, bool fw_ok) {
|
||||
char bld_ver[32];
|
||||
char ver_str[64];
|
||||
format_ver("%d.%d.%d", VERSION_UINT32, bld_ver, sizeof(bld_ver));
|
||||
format_ver("%d.%d.%d", hdr->version, ver_str, sizeof(ver_str));
|
||||
|
||||
return screen_intro(bld_ver, vhdr->vstr, vhdr->vstr_len, ver_str);
|
||||
return screen_intro(bld_ver, vhdr->vstr, vhdr->vstr_len, ver_str, fw_ok);
|
||||
}
|
||||
|
||||
uint32_t ui_screen_menu(void) { return screen_menu(); }
|
||||
uint32_t ui_screen_menu(secbool firmware_present) {
|
||||
return screen_menu(firmware_present);
|
||||
}
|
||||
|
||||
// install UI
|
||||
|
||||
@ -242,17 +244,7 @@ void ui_screen_wipe_progress(int pos, int len) {
|
||||
|
||||
// done UI
|
||||
void ui_screen_done(uint8_t restart_seconds, secbool full_redraw) {
|
||||
const char *str;
|
||||
char count_str[24];
|
||||
if (restart_seconds >= 1) {
|
||||
mini_snprintf(count_str, sizeof(count_str), "RESTARTING IN %d",
|
||||
restart_seconds);
|
||||
str = count_str;
|
||||
} else {
|
||||
str = "RECONNECT THE DEVICE";
|
||||
}
|
||||
|
||||
screen_install_success(str, initial_setup, full_redraw);
|
||||
screen_install_success(restart_seconds, initial_setup, full_redraw);
|
||||
}
|
||||
|
||||
void ui_screen_boot_empty(bool fading) { screen_boot_empty(fading); }
|
||||
|
@ -31,6 +31,7 @@ typedef enum {
|
||||
SCREEN_WIPE_CONFIRM = 2,
|
||||
SCREEN_FINGER_PRINT = 3,
|
||||
SCREEN_WAIT_FOR_HOST = 4,
|
||||
SCREEN_WELCOME = 5,
|
||||
} screen_t;
|
||||
|
||||
void ui_screen_boot(const vendor_header* const vhdr,
|
||||
@ -42,9 +43,9 @@ void ui_click(void);
|
||||
void ui_screen_welcome(void);
|
||||
|
||||
uint32_t ui_screen_intro(const vendor_header* const vhdr,
|
||||
const image_header* const hdr);
|
||||
const image_header* const hdr, bool fw_ok);
|
||||
|
||||
uint32_t ui_screen_menu(void);
|
||||
uint32_t ui_screen_menu(secbool firmware_present);
|
||||
|
||||
uint32_t ui_screen_install_confirm(const vendor_header* const vhdr,
|
||||
const image_header* const hdr,
|
||||
|
@ -87,7 +87,8 @@ __attribute__((noreturn)) void jump_to(void *addr) {
|
||||
"STORAGE WAS ERASED");
|
||||
} else {
|
||||
printf("storage was retained\n");
|
||||
screen_install_success("STORAGE WAS RETAINED", true, true);
|
||||
screen_fatal_error_rust("BOOTLOADER EXIT", "Jumped to firmware",
|
||||
"STORAGE WAS RETAINED");
|
||||
}
|
||||
display_backlight(180);
|
||||
display_refresh();
|
||||
|
@ -35,6 +35,9 @@
|
||||
#ifdef USE_I2C
|
||||
#include "i2c.h"
|
||||
#endif
|
||||
#ifdef USE_OPTIGA
|
||||
#include "optiga_hal.h"
|
||||
#endif
|
||||
#ifdef USE_TOUCH
|
||||
#include "touch.h"
|
||||
#endif
|
||||
@ -81,11 +84,15 @@ static const uint8_t * const BOOTLOADER_KEYS[] = {
|
||||
#define USB_IFACE_NUM 0
|
||||
|
||||
typedef enum {
|
||||
CONTINUE = 0,
|
||||
RETURN = 1,
|
||||
SHUTDOWN = 2,
|
||||
SHUTDOWN = 0,
|
||||
CONTINUE_TO_FIRMWARE = 0xAABBCCDD,
|
||||
RETURN_TO_MENU = 0x55667788,
|
||||
} usb_result_t;
|
||||
|
||||
volatile secbool dont_optimize_out_true = sectrue;
|
||||
void failed_jump_to_firmware(void);
|
||||
volatile void (*firmware_jump_fn)(void) = failed_jump_to_firmware;
|
||||
|
||||
static void usb_init_all(secbool usb21_landing) {
|
||||
usb_dev_info_t dev_info = {
|
||||
.device_class = 0x00,
|
||||
@ -164,7 +171,7 @@ static usb_result_t bootloader_usb_loop(const vendor_header *const vhdr,
|
||||
hal_delay(100);
|
||||
usb_stop();
|
||||
usb_deinit();
|
||||
return RETURN;
|
||||
return RETURN_TO_MENU;
|
||||
}
|
||||
ui_screen_wipe();
|
||||
r = process_msg_WipeDevice(USB_IFACE_NUM, msg_size, buf);
|
||||
@ -200,7 +207,7 @@ static usb_result_t bootloader_usb_loop(const vendor_header *const vhdr,
|
||||
hal_delay(100);
|
||||
usb_stop();
|
||||
usb_deinit();
|
||||
return RETURN;
|
||||
return RETURN_TO_MENU;
|
||||
} else if (r == 0) { // last chunk received
|
||||
ui_screen_install_progress_upload(1000);
|
||||
ui_screen_done(4, sectrue);
|
||||
@ -213,7 +220,7 @@ static usb_result_t bootloader_usb_loop(const vendor_header *const vhdr,
|
||||
usb_stop();
|
||||
usb_deinit();
|
||||
ui_screen_boot_empty(true);
|
||||
return CONTINUE;
|
||||
return CONTINUE_TO_FIRMWARE;
|
||||
}
|
||||
break;
|
||||
case MessageType_MessageType_GetFeatures:
|
||||
@ -227,7 +234,7 @@ static usb_result_t bootloader_usb_loop(const vendor_header *const vhdr,
|
||||
hal_delay(100);
|
||||
usb_stop();
|
||||
usb_deinit();
|
||||
return RETURN;
|
||||
return RETURN_TO_MENU;
|
||||
}
|
||||
process_msg_UnlockBootloader(USB_IFACE_NUM, msg_size, buf);
|
||||
screen_unlock_bootloader_success();
|
||||
@ -294,237 +301,13 @@ static void check_bootloader_version(void) {
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef TREZOR_EMULATOR
|
||||
int main(void) {
|
||||
// grab "stay in bootloader" flag as soon as possible
|
||||
register uint32_t r11 __asm__("r11");
|
||||
volatile uint32_t stay_in_bootloader_flag = r11;
|
||||
#else
|
||||
int bootloader_main(void) {
|
||||
#endif
|
||||
|
||||
random_delays_init();
|
||||
// display_init_seq();
|
||||
#ifdef USE_DMA2D
|
||||
dma2d_init();
|
||||
#endif
|
||||
|
||||
display_reinit();
|
||||
|
||||
ui_screen_boot_empty(false);
|
||||
|
||||
mpu_config_bootloader();
|
||||
void failed_jump_to_firmware(void) {
|
||||
error_shutdown("INTERNAL ERROR", "(glitch)");
|
||||
}
|
||||
|
||||
void real_jump_to_firmware(void) {
|
||||
const image_header *hdr = NULL;
|
||||
vendor_header vhdr;
|
||||
// detect whether the device contains a valid firmware
|
||||
secbool firmware_present = sectrue;
|
||||
|
||||
if (sectrue != read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr)) {
|
||||
firmware_present = secfalse;
|
||||
}
|
||||
|
||||
if (sectrue == firmware_present) {
|
||||
firmware_present = check_vendor_header_keys(&vhdr);
|
||||
}
|
||||
|
||||
if (sectrue == firmware_present) {
|
||||
firmware_present = check_vendor_header_lock(&vhdr);
|
||||
}
|
||||
|
||||
if (sectrue == firmware_present) {
|
||||
hdr = read_image_header(
|
||||
(const uint8_t *)(size_t)(FIRMWARE_START + vhdr.hdrlen),
|
||||
FIRMWARE_IMAGE_MAGIC, FIRMWARE_IMAGE_MAXSIZE);
|
||||
if (hdr != (const image_header *)(size_t)(FIRMWARE_START + vhdr.hdrlen)) {
|
||||
firmware_present = secfalse;
|
||||
}
|
||||
}
|
||||
if (sectrue == firmware_present) {
|
||||
firmware_present = check_image_model(hdr);
|
||||
}
|
||||
if (sectrue == firmware_present) {
|
||||
firmware_present =
|
||||
check_image_header_sig(hdr, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub);
|
||||
}
|
||||
if (sectrue == firmware_present) {
|
||||
firmware_present = check_image_contents(
|
||||
hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, &FIRMWARE_AREA);
|
||||
}
|
||||
|
||||
#if defined TREZOR_MODEL_T
|
||||
set_core_clock(CLOCK_180_MHZ);
|
||||
display_set_little_endian();
|
||||
#endif
|
||||
|
||||
#ifdef USE_I2C
|
||||
i2c_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_TOUCH
|
||||
touch_power_on();
|
||||
touch_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
button_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_CONSUMPTION_MASK
|
||||
consumption_mask_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_RGB_LED
|
||||
rgb_led_init();
|
||||
#endif
|
||||
|
||||
unit_variant_init();
|
||||
|
||||
#if PRODUCTION
|
||||
check_bootloader_version();
|
||||
#endif
|
||||
|
||||
// was there reboot with request to stay in bootloader?
|
||||
secbool stay_in_bootloader = secfalse;
|
||||
if (stay_in_bootloader_flag == STAY_IN_BOOTLOADER_FLAG) {
|
||||
stay_in_bootloader = sectrue;
|
||||
}
|
||||
|
||||
// delay to detect touch or skip if we know we are staying in bootloader
|
||||
// anyway
|
||||
uint32_t touched = 0;
|
||||
#ifdef USE_TOUCH
|
||||
if (firmware_present == sectrue && stay_in_bootloader != sectrue) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
touched = touch_is_detected() | touch_read();
|
||||
if (touched) {
|
||||
break;
|
||||
}
|
||||
#ifdef TREZOR_EMULATOR
|
||||
hal_delay(25);
|
||||
#else
|
||||
hal_delay(1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#elif defined USE_BUTTON
|
||||
button_read();
|
||||
if (button_state_left() == 1) {
|
||||
touched = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// start the bootloader if no or broken firmware found ...
|
||||
if (firmware_present != sectrue) {
|
||||
#ifdef TREZOR_EMULATOR
|
||||
// wait a bit so that the empty lock icon is visible
|
||||
// (on a real device, we are waiting for touch init which takes longer)
|
||||
hal_delay(400);
|
||||
#endif
|
||||
// ignore stay in bootloader
|
||||
stay_in_bootloader = secfalse;
|
||||
touched = false;
|
||||
|
||||
ui_set_initial_setup(true);
|
||||
|
||||
// keep the model screen up for a while
|
||||
#ifndef USE_BACKLIGHT
|
||||
hal_delay(1500);
|
||||
#else
|
||||
// backlight fading takes some time so the explicit delay here is shorter
|
||||
hal_delay(1000);
|
||||
#endif
|
||||
|
||||
// show welcome screen
|
||||
ui_screen_welcome();
|
||||
|
||||
// erase storage
|
||||
ensure(flash_area_erase_bulk(STORAGE_AREAS, STORAGE_AREAS_COUNT, NULL),
|
||||
NULL);
|
||||
|
||||
// and start the usb loop
|
||||
if (bootloader_usb_loop(NULL, NULL) != CONTINUE) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ... or if user touched the screen on start
|
||||
// ... or we have stay_in_bootloader flag to force it
|
||||
if (touched || stay_in_bootloader == sectrue) {
|
||||
ui_set_initial_setup(false);
|
||||
|
||||
screen_t screen = SCREEN_INTRO;
|
||||
|
||||
while (true) {
|
||||
bool continue_to_firmware = false;
|
||||
uint32_t ui_result = 0;
|
||||
|
||||
switch (screen) {
|
||||
case SCREEN_INTRO:
|
||||
ui_result = ui_screen_intro(&vhdr, hdr);
|
||||
if (ui_result == 1) {
|
||||
screen = SCREEN_MENU;
|
||||
}
|
||||
if (ui_result == 2) {
|
||||
screen = SCREEN_WAIT_FOR_HOST;
|
||||
}
|
||||
break;
|
||||
case SCREEN_MENU:
|
||||
ui_result = ui_screen_menu();
|
||||
if (ui_result == 1) { // exit menu
|
||||
screen = SCREEN_INTRO;
|
||||
}
|
||||
if (ui_result == 2) { // reboot
|
||||
ui_screen_boot_empty(true);
|
||||
continue_to_firmware = true;
|
||||
}
|
||||
if (ui_result == 3) { // wipe
|
||||
screen = SCREEN_WIPE_CONFIRM;
|
||||
}
|
||||
break;
|
||||
case SCREEN_WIPE_CONFIRM:
|
||||
ui_result = screen_wipe_confirm();
|
||||
if (ui_result == INPUT_CANCEL) {
|
||||
// canceled
|
||||
screen = SCREEN_MENU;
|
||||
}
|
||||
if (ui_result == INPUT_CONFIRM) {
|
||||
ui_screen_wipe();
|
||||
secbool r = bootloader_WipeDevice();
|
||||
if (r != sectrue) { // error
|
||||
screen_wipe_fail();
|
||||
return 1;
|
||||
} else { // success
|
||||
screen_wipe_success();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SCREEN_WAIT_FOR_HOST:
|
||||
screen_connect();
|
||||
switch (bootloader_usb_loop(&vhdr, hdr)) {
|
||||
case CONTINUE:
|
||||
continue_to_firmware = true;
|
||||
break;
|
||||
case RETURN:
|
||||
screen = SCREEN_INTRO;
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (continue_to_firmware) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
vendor_header vhdr = {0};
|
||||
|
||||
ensure(read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr),
|
||||
"Firmware is corrupted");
|
||||
@ -554,7 +337,7 @@ int bootloader_main(void) {
|
||||
#ifdef USE_OPTIGA
|
||||
if (((vhdr.vtrust & VTRUST_SECRET) != 0) && (sectrue != secret_wiped())) {
|
||||
ui_screen_install_restricted();
|
||||
return 1;
|
||||
trezor_shutdown();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -589,6 +372,290 @@ int bootloader_main(void) {
|
||||
|
||||
mpu_config_off();
|
||||
jump_to(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE);
|
||||
}
|
||||
|
||||
#ifndef TREZOR_EMULATOR
|
||||
int main(void) {
|
||||
// grab "stay in bootloader" flag as soon as possible
|
||||
register uint32_t r11 __asm__("r11");
|
||||
volatile uint32_t stay_in_bootloader_flag = r11;
|
||||
#else
|
||||
int bootloader_main(void) {
|
||||
#endif
|
||||
|
||||
random_delays_init();
|
||||
// display_init_seq();
|
||||
#ifdef USE_DMA2D
|
||||
dma2d_init();
|
||||
#endif
|
||||
|
||||
display_reinit();
|
||||
|
||||
ui_screen_boot_empty(false);
|
||||
|
||||
mpu_config_bootloader();
|
||||
|
||||
#ifdef TREZOR_EMULATOR
|
||||
// wait a bit so that the empty lock icon is visible
|
||||
// (on a real device, we are waiting for touch init which takes longer)
|
||||
hal_delay(400);
|
||||
#endif
|
||||
|
||||
const image_header *hdr = NULL;
|
||||
vendor_header vhdr;
|
||||
|
||||
// detect whether the device contains a valid firmware
|
||||
volatile secbool vhdr_present = secfalse;
|
||||
volatile secbool vhdr_keys_ok = secfalse;
|
||||
volatile secbool vhdr_lock_ok = secfalse;
|
||||
volatile secbool img_hdr_ok = secfalse;
|
||||
volatile secbool model_ok = secfalse;
|
||||
volatile secbool header_present = secfalse;
|
||||
volatile secbool firmware_present = secfalse;
|
||||
volatile secbool firmware_present_backup = secfalse;
|
||||
|
||||
vhdr_present = read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr);
|
||||
|
||||
if (sectrue == vhdr_present) {
|
||||
vhdr_keys_ok = check_vendor_header_keys(&vhdr);
|
||||
}
|
||||
|
||||
if (sectrue == vhdr_keys_ok) {
|
||||
vhdr_lock_ok = check_vendor_header_lock(&vhdr);
|
||||
}
|
||||
|
||||
if (sectrue == vhdr_lock_ok) {
|
||||
hdr = read_image_header(
|
||||
(const uint8_t *)(size_t)(FIRMWARE_START + vhdr.hdrlen),
|
||||
FIRMWARE_IMAGE_MAGIC, FIRMWARE_IMAGE_MAXSIZE);
|
||||
if (hdr == (const image_header *)(size_t)(FIRMWARE_START + vhdr.hdrlen)) {
|
||||
img_hdr_ok = sectrue;
|
||||
}
|
||||
}
|
||||
if (sectrue == img_hdr_ok) {
|
||||
model_ok = check_image_model(hdr);
|
||||
}
|
||||
if (sectrue == model_ok) {
|
||||
header_present =
|
||||
check_image_header_sig(hdr, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub);
|
||||
}
|
||||
|
||||
if (sectrue == header_present) {
|
||||
firmware_present = check_image_contents(
|
||||
hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, &FIRMWARE_AREA);
|
||||
firmware_present_backup = firmware_present;
|
||||
}
|
||||
|
||||
#if defined TREZOR_MODEL_T
|
||||
set_core_clock(CLOCK_180_MHZ);
|
||||
display_set_little_endian();
|
||||
#endif
|
||||
|
||||
#ifdef USE_I2C
|
||||
i2c_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPTIGA
|
||||
optiga_hal_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_TOUCH
|
||||
touch_power_on();
|
||||
touch_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
button_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_CONSUMPTION_MASK
|
||||
consumption_mask_init();
|
||||
#endif
|
||||
|
||||
#ifdef USE_RGB_LED
|
||||
rgb_led_init();
|
||||
#endif
|
||||
|
||||
unit_variant_init();
|
||||
|
||||
#if PRODUCTION
|
||||
check_bootloader_version();
|
||||
#endif
|
||||
|
||||
// was there reboot with request to stay in bootloader?
|
||||
secbool stay_in_bootloader = secfalse;
|
||||
if (stay_in_bootloader_flag == STAY_IN_BOOTLOADER_FLAG) {
|
||||
stay_in_bootloader = sectrue;
|
||||
}
|
||||
|
||||
ensure(dont_optimize_out_true * (firmware_present == firmware_present_backup),
|
||||
NULL);
|
||||
|
||||
// delay to detect touch or skip if we know we are staying in bootloader
|
||||
// anyway
|
||||
uint32_t touched = 0;
|
||||
#ifdef USE_TOUCH
|
||||
if (firmware_present == sectrue && stay_in_bootloader != sectrue) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
touched = touch_is_detected() | touch_read();
|
||||
if (touched) {
|
||||
break;
|
||||
}
|
||||
#ifdef TREZOR_EMULATOR
|
||||
hal_delay(25);
|
||||
#else
|
||||
hal_delay(1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#elif defined USE_BUTTON
|
||||
button_read();
|
||||
if (button_state_left() == 1) {
|
||||
touched = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
ensure(dont_optimize_out_true * (firmware_present == firmware_present_backup),
|
||||
NULL);
|
||||
|
||||
// start the bootloader ...
|
||||
// ... if user touched the screen on start
|
||||
// ... or we have stay_in_bootloader flag to force it
|
||||
// ... or there is no valid firmware
|
||||
if (touched || stay_in_bootloader == sectrue || firmware_present != sectrue) {
|
||||
screen_t screen;
|
||||
if (header_present == sectrue) {
|
||||
ui_set_initial_setup(false);
|
||||
screen = SCREEN_INTRO;
|
||||
} else {
|
||||
screen = SCREEN_WELCOME;
|
||||
|
||||
// erase storage
|
||||
ensure(flash_area_erase_bulk(STORAGE_AREAS, STORAGE_AREAS_COUNT, NULL),
|
||||
NULL);
|
||||
|
||||
ui_set_initial_setup(true);
|
||||
|
||||
// keep the model screen up for a while
|
||||
#ifndef USE_BACKLIGHT
|
||||
hal_delay(1500);
|
||||
#else
|
||||
// backlight fading takes some time so the explicit delay here is
|
||||
// shorter
|
||||
hal_delay(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
while (true) {
|
||||
volatile secbool continue_to_firmware = secfalse;
|
||||
volatile secbool continue_to_firmware_backup = secfalse;
|
||||
uint32_t ui_result = 0;
|
||||
|
||||
switch (screen) {
|
||||
case SCREEN_WELCOME:
|
||||
|
||||
ui_screen_welcome();
|
||||
|
||||
// and start the usb loop
|
||||
switch (bootloader_usb_loop(NULL, NULL)) {
|
||||
case CONTINUE_TO_FIRMWARE:
|
||||
continue_to_firmware = sectrue;
|
||||
continue_to_firmware_backup = sectrue;
|
||||
break;
|
||||
case RETURN_TO_MENU:
|
||||
break;
|
||||
default:
|
||||
case SHUTDOWN:
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SCREEN_INTRO:
|
||||
ui_result = ui_screen_intro(&vhdr, hdr, firmware_present);
|
||||
if (ui_result == 1) {
|
||||
screen = SCREEN_MENU;
|
||||
}
|
||||
if (ui_result == 2) {
|
||||
screen = SCREEN_WAIT_FOR_HOST;
|
||||
}
|
||||
break;
|
||||
case SCREEN_MENU:
|
||||
ui_result = ui_screen_menu(firmware_present);
|
||||
if (ui_result == 0xAABBCCDD) { // exit menu
|
||||
screen = SCREEN_INTRO;
|
||||
}
|
||||
if (ui_result == 0x11223344) { // reboot
|
||||
ui_screen_boot_empty(true);
|
||||
continue_to_firmware = firmware_present;
|
||||
continue_to_firmware_backup = firmware_present_backup;
|
||||
}
|
||||
if (ui_result == 0x55667788) { // wipe
|
||||
screen = SCREEN_WIPE_CONFIRM;
|
||||
}
|
||||
break;
|
||||
case SCREEN_WIPE_CONFIRM:
|
||||
ui_result = screen_wipe_confirm();
|
||||
if (ui_result == INPUT_CANCEL) {
|
||||
// canceled
|
||||
screen = SCREEN_MENU;
|
||||
}
|
||||
if (ui_result == INPUT_CONFIRM) {
|
||||
ui_screen_wipe();
|
||||
secbool r = bootloader_WipeDevice();
|
||||
if (r != sectrue) { // error
|
||||
screen_wipe_fail();
|
||||
return 1;
|
||||
} else { // success
|
||||
screen_wipe_success();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SCREEN_WAIT_FOR_HOST:
|
||||
screen_connect();
|
||||
switch (bootloader_usb_loop(&vhdr, hdr)) {
|
||||
case CONTINUE_TO_FIRMWARE:
|
||||
continue_to_firmware = sectrue;
|
||||
continue_to_firmware_backup = sectrue;
|
||||
break;
|
||||
case RETURN_TO_MENU:
|
||||
screen = SCREEN_INTRO;
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (continue_to_firmware != continue_to_firmware_backup) {
|
||||
// erase storage if we saw flips randomly flip, most likely due to
|
||||
// glitch
|
||||
ensure(flash_area_erase_bulk(STORAGE_AREAS, STORAGE_AREAS_COUNT, NULL),
|
||||
NULL);
|
||||
}
|
||||
ensure(dont_optimize_out_true *
|
||||
(continue_to_firmware == continue_to_firmware_backup),
|
||||
NULL);
|
||||
if (sectrue == continue_to_firmware) {
|
||||
firmware_jump_fn = real_jump_to_firmware;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ensure(dont_optimize_out_true * (firmware_present == firmware_present_backup),
|
||||
NULL);
|
||||
if (sectrue == firmware_present) {
|
||||
firmware_jump_fn = real_jump_to_firmware;
|
||||
}
|
||||
|
||||
firmware_jump_fn();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -295,6 +295,7 @@ STATIC mp_obj_str_t mod_trezorutils_model_name_obj = {
|
||||
/// INTERNAL_MODEL: str
|
||||
/// EMULATOR: bool
|
||||
/// BITCOIN_ONLY: bool
|
||||
/// UI_LAYOUT: str
|
||||
|
||||
STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorutils)},
|
||||
@ -348,6 +349,13 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
|
||||
#else
|
||||
{MP_ROM_QSTR(MP_QSTR_BITCOIN_ONLY), mp_const_false},
|
||||
#endif
|
||||
#ifdef UI_LAYOUT_TT
|
||||
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_TT)},
|
||||
#elif UI_LAYOUT_TR
|
||||
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_TR)},
|
||||
#else
|
||||
#error Unknown layout
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorutils_globals,
|
||||
|
@ -300,6 +300,9 @@ void SVC_C_Handler(uint32_t *stack) {
|
||||
// raising privileges.
|
||||
stack[6] = (uintptr_t)reboot_to_bootloader;
|
||||
return;
|
||||
case SVC_GET_SYSTICK_VAL: {
|
||||
systick_val_copy = SysTick->VAL;
|
||||
} break;
|
||||
default:
|
||||
stack[0] = 0xffffffff;
|
||||
break;
|
||||
|
@ -1,6 +1,6 @@
|
||||
#define VERSION_MAJOR 2
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_PATCH 1
|
||||
#define VERSION_PATCH 2
|
||||
#define VERSION_BUILD 0
|
||||
|
||||
#define FIX_VERSION_MAJOR 2
|
||||
|
@ -6,12 +6,15 @@
|
||||
// - the third, fourth and fifth bytes are advance, bearingX and bearingY of the horizontal metrics of the glyph
|
||||
// - the rest is packed 1-bit glyph data
|
||||
|
||||
// NOTE: hand-changed all the advances with 8 to 7
|
||||
// NOTE: hand-changed the visuals of "m", "w", "M" and "W" to make it narrower
|
||||
|
||||
/* */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_32[] = { 0, 0, 7, 0, 0 }; // width hand-changed from 8 to 7 to have 9px space between words
|
||||
/* ! */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_33[] = { 1, 7, 7, 2, 7, 250 };
|
||||
/* " */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_34[] = { 3, 3, 7, 1, 7, 182, 128 };
|
||||
/* # */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_35[] = { 6, 6, 7, 0, 6, 75, 244, 146, 253, 32 };
|
||||
/* $ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_36[] = { 5, 7, 7, 0, 7, 35, 168, 226, 248, 128 };
|
||||
/* % */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_37[] = { 7, 7, 8, 0, 7, 65, 74, 162, 162, 169, 65, 0 };
|
||||
/* % */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_37[] = { 7, 7, 7, 0, 7, 65, 74, 162, 162, 169, 65, 0 };
|
||||
/* & */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_38[] = { 5, 7, 7, 0, 7, 116, 96, 232, 197, 224 };
|
||||
/* ' */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_39[] = { 1, 3, 7, 2, 7, 224 };
|
||||
/* ( */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_40[] = { 3, 7, 7, 2, 7, 42, 72, 136 };
|
||||
@ -38,7 +41,7 @@
|
||||
/* = */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_61[] = { 4, 3, 7, 1, 5, 240, 240 };
|
||||
/* > */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_62[] = { 3, 5, 7, 1, 6, 136, 168 };
|
||||
/* ? */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_63[] = { 5, 7, 7, 0, 7, 116, 66, 34, 0, 128 };
|
||||
/* @ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_64[] = { 7, 8, 8, 0, 7, 125, 6, 109, 90, 179, 160, 62, 0 };
|
||||
/* @ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_64[] = { 7, 8, 7, 0, 7, 125, 6, 109, 90, 179, 160, 62, 0 };
|
||||
/* A */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_65[] = { 5, 7, 7, 0, 7, 116, 99, 31, 198, 32 };
|
||||
/* B */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_66[] = { 5, 7, 7, 0, 7, 244, 99, 232, 199, 192 };
|
||||
/* C */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_67[] = { 5, 7, 7, 0, 7, 116, 97, 8, 69, 192 };
|
||||
@ -51,7 +54,7 @@
|
||||
/* J */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_74[] = { 6, 7, 7, 0, 7, 60, 32, 130, 10, 39, 0 };
|
||||
/* K */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_75[] = { 5, 7, 7, 0, 7, 140, 169, 138, 74, 32 };
|
||||
/* L */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_76[] = { 5, 7, 7, 0, 7, 132, 33, 8, 67, 224 };
|
||||
/* M */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_77[] = { 7, 7, 8, 0, 7, 131, 7, 29, 89, 48, 96, 128 };
|
||||
/* M */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_77[] = { 5, 7, 7, 0, 7, 142, 235, 24, 198, 32 };
|
||||
/* N */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_78[] = { 5, 7, 7, 0, 7, 140, 115, 89, 198, 32 };
|
||||
/* O */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_79[] = { 5, 7, 7, 0, 7, 116, 99, 24, 197, 192 };
|
||||
/* P */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_80[] = { 5, 7, 7, 0, 7, 244, 99, 31, 66, 0 };
|
||||
@ -61,7 +64,7 @@
|
||||
/* T */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_84[] = { 5, 7, 7, 0, 7, 249, 8, 66, 16, 128 };
|
||||
/* U */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_85[] = { 5, 7, 7, 0, 7, 140, 99, 24, 197, 192 };
|
||||
/* V */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_86[] = { 5, 7, 7, 0, 7, 140, 99, 24, 168, 128 };
|
||||
/* W */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_87[] = { 7, 7, 8, 0, 7, 131, 6, 76, 153, 50, 91, 0 };
|
||||
/* W */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_87[] = { 5, 7, 7, 0, 7, 140, 107, 90, 213, 64 };
|
||||
/* X */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_88[] = { 5, 7, 7, 0, 7, 140, 84, 69, 70, 32 };
|
||||
/* Y */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_89[] = { 5, 7, 7, 0, 7, 140, 84, 66, 16, 128 };
|
||||
/* Z */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_90[] = { 5, 7, 7, 0, 7, 248, 68, 68, 67, 224 };
|
||||
@ -69,7 +72,7 @@
|
||||
/* \ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_92[] = { 3, 7, 7, 1, 7, 145, 36, 72 };
|
||||
/* ] */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_93[] = { 3, 7, 7, 0, 7, 228, 146, 120 };
|
||||
/* ^ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_94[] = { 5, 3, 7, 0, 7, 34, 162 };
|
||||
/* _ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_95[] = { 8, 1, 8, 0, 0, 255, 0 };
|
||||
/* _ */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_95[] = { 8, 1, 7, 0, 0, 255, 0 };
|
||||
/* ` */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_96[] = { 2, 2, 7, 1, 7, 144 };
|
||||
/* a */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_97[] = { 5, 5, 7, 0, 5, 112, 95, 23, 128 };
|
||||
/* b */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_98[] = { 5, 7, 7, 0, 7, 132, 61, 24, 199, 192 };
|
||||
@ -83,7 +86,7 @@
|
||||
/* j */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_106[] = { 5, 8, 7, 0, 7, 8, 14, 16, 134, 46, 0 };
|
||||
/* k */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_107[] = { 5, 7, 7, 0, 7, 132, 37, 78, 74, 32 };
|
||||
/* l */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_108[] = { 5, 7, 7, 0, 7, 225, 8, 66, 19, 224 };
|
||||
/* m */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_109[] = { 7, 5, 8, 0, 5, 237, 38, 76, 24, 32 };
|
||||
/* m */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_109[] = { 5, 5, 7, 0, 5, 213, 107, 24, 128 };
|
||||
/* n */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_110[] = { 5, 5, 7, 0, 5, 244, 99, 24, 128 };
|
||||
/* o */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_111[] = { 5, 5, 7, 0, 5, 116, 99, 23, 0 };
|
||||
/* p */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_112[] = { 5, 6, 7, 0, 5, 244, 99, 232, 64 };
|
||||
@ -93,7 +96,7 @@
|
||||
/* t */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_116[] = { 4, 6, 7, 1, 6, 79, 68, 67, 0 };
|
||||
/* u */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_117[] = { 5, 5, 7, 0, 5, 140, 99, 23, 0 };
|
||||
/* v */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_118[] = { 5, 5, 7, 0, 5, 140, 98, 162, 0 };
|
||||
/* w */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_119[] = { 7, 5, 8, 0, 5, 131, 6, 76, 150, 192 };
|
||||
/* w */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_119[] = { 5, 5, 7, 0, 5, 140, 107, 85, 0 };
|
||||
/* x */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_120[] = { 5, 5, 7, 0, 5, 138, 136, 168, 128 };
|
||||
/* y */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_121[] = { 5, 6, 7, 0, 5, 140, 98, 240, 184 };
|
||||
/* z */ static const uint8_t Font_PixelOperatorMono_Regular_8_glyph_122[] = { 5, 5, 7, 0, 5, 248, 136, 143, 128 };
|
||||
|
@ -12,14 +12,15 @@ void screen_install_progress(int16_t progress, bool initialize,
|
||||
bool initial_setup);
|
||||
void screen_wipe_progress(int16_t progress, bool initialize);
|
||||
uint32_t screen_intro(const char* bld_version_str, const char* vendor_str,
|
||||
uint8_t vendor_str_len, const char* version_str);
|
||||
uint32_t screen_menu(void);
|
||||
uint8_t vendor_str_len, const char* version_str,
|
||||
bool fw_ok);
|
||||
uint32_t screen_menu(secbool firmware_present);
|
||||
void screen_connect(void);
|
||||
void screen_fatal_error_rust(const char* title, const char* msg,
|
||||
const char* footer);
|
||||
void screen_wipe_success(void);
|
||||
void screen_wipe_fail(void);
|
||||
uint32_t screen_install_success(const char* reboot_msg, bool initial_setup,
|
||||
uint32_t screen_install_success(uint8_t restart_seconds, bool initial_setup,
|
||||
bool complete_draw);
|
||||
uint32_t screen_install_fail(void);
|
||||
void screen_welcome_model(void);
|
||||
|
@ -9,7 +9,7 @@ pub mod dma2d;
|
||||
mod ffi;
|
||||
pub mod io;
|
||||
pub mod random;
|
||||
#[cfg(feature = "model_tr")]
|
||||
#[cfg(feature = "rgb_led")]
|
||||
pub mod rgb_led;
|
||||
pub mod slip39;
|
||||
pub mod storage;
|
||||
@ -18,6 +18,8 @@ pub mod uzlib;
|
||||
pub mod wordlist;
|
||||
|
||||
pub mod buffers;
|
||||
pub mod secbool;
|
||||
|
||||
#[cfg(not(feature = "micropython"))]
|
||||
pub mod time;
|
||||
|
||||
|
3
core/embed/rust/src/trezorhal/secbool.rs
Normal file
3
core/embed/rust/src/trezorhal/secbool.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use super::ffi;
|
||||
|
||||
pub use ffi::{secbool, secfalse, sectrue};
|
@ -59,8 +59,6 @@ pub struct Chunks {
|
||||
pub chunk_size: usize,
|
||||
/// How big will be the space between chunks (in pixels).
|
||||
pub x_offset: i16,
|
||||
/// Optional characters that are wider and should be accounted for
|
||||
pub wider_chars: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Chunks {
|
||||
@ -68,20 +66,6 @@ impl Chunks {
|
||||
Chunks {
|
||||
chunk_size,
|
||||
x_offset,
|
||||
wider_chars: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_wider_chars(mut self, wider_chars: &'static str) -> Self {
|
||||
self.wider_chars = Some(wider_chars);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_char_wider(self, ch: char) -> bool {
|
||||
if let Some(wider_chars) = self.wider_chars {
|
||||
wider_chars.contains(ch)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,6 +176,14 @@ impl TextStyle {
|
||||
self.text_font.text_width(ELLIPSIS)
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_page_ellipsis_icon_width(&self) -> i16 {
|
||||
if let Some((icon, _)) = self.prev_page_ellipsis_icon {
|
||||
icon.toif.width()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextLayout {
|
||||
@ -262,9 +254,21 @@ impl TextLayout {
|
||||
PageBreaking::CutAndInsertEllipsisBoth
|
||||
) && self.continues_from_prev_page
|
||||
{
|
||||
sink.prev_page_ellipsis(*cursor, self);
|
||||
// Move the cursor to the right, always the same distance
|
||||
cursor.x += self.style.prev_page_ellipsis_width();
|
||||
// Special case in chunkifying text - move the cursor so that we
|
||||
// start with the second chunk.
|
||||
if let Some(chunk_config) = self.style.chunks {
|
||||
// Showing the arrow at the last chunk position
|
||||
// Assuming mono-font, so all the letters have the same width
|
||||
let letter_size = self.style.text_font.text_width("a");
|
||||
let icon_offset = self.style.prev_page_ellipsis_icon_width() + 2;
|
||||
cursor.x += chunk_config.chunk_size as i16 * letter_size - icon_offset;
|
||||
sink.prev_page_ellipsis(*cursor, self);
|
||||
cursor.x += icon_offset + chunk_config.x_offset;
|
||||
} else {
|
||||
sink.prev_page_ellipsis(*cursor, self);
|
||||
cursor.x += self.style.prev_page_ellipsis_width();
|
||||
}
|
||||
}
|
||||
|
||||
while !remaining_text.is_empty() {
|
||||
@ -285,6 +289,20 @@ impl TextLayout {
|
||||
self.style.chunks,
|
||||
);
|
||||
|
||||
if let Some(chunk_config) = self.style.chunks {
|
||||
// Last chunk on the page should not be rendered, put just ellipsis there
|
||||
// Chunks is last when the next chunk would not fit on the page horizontally
|
||||
let is_last_chunk = (2 * span.advance.x - chunk_config.x_offset) > remaining_width;
|
||||
if is_last_line && is_last_chunk && remaining_text.len() > chunk_config.chunk_size {
|
||||
// Making sure no text is rendered here, and that we force a line break
|
||||
span.length = 0;
|
||||
span.advance.x = 2; // To start at the same horizontal line as the chunk itself
|
||||
span.advance.y = self.bounds.y1;
|
||||
span.insert_hyphen_before_line_break = false;
|
||||
span.skip_next_chars = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.x += match self.align {
|
||||
Alignment::Start => 0,
|
||||
Alignment::Center => (remaining_width - span.advance.x) / 2,
|
||||
@ -598,7 +616,6 @@ impl Span {
|
||||
|
||||
let mut span_width = 0;
|
||||
let mut found_any_whitespace = false;
|
||||
let mut chunks_wider_chars = 0;
|
||||
|
||||
let mut char_indices_iter = text.char_indices().peekable();
|
||||
// Iterating manually because we need a reference to the iterator inside the
|
||||
@ -611,14 +628,8 @@ impl Span {
|
||||
if let Some(chunkify_config) = chunks {
|
||||
if i == chunkify_config.chunk_size {
|
||||
line.advance.y = 0;
|
||||
// Decreasing the offset for each wider character in the chunk
|
||||
line.advance.x += chunkify_config.x_offset - chunks_wider_chars;
|
||||
line.advance.x += chunkify_config.x_offset;
|
||||
return line;
|
||||
} else {
|
||||
// Counting all the wider characters in the chunk
|
||||
if chunkify_config.is_char_wider(ch) {
|
||||
chunks_wider_chars += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ impl<'a> Component for Confirm<'a> {
|
||||
let msg = self.buttons.event(ctx, event);
|
||||
if self.showing_info_screen {
|
||||
// Showing the info screen currently - going back with the left button
|
||||
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) = msg {
|
||||
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) = msg {
|
||||
self.showing_info_screen = false;
|
||||
self.update_everything(ctx);
|
||||
};
|
||||
@ -163,11 +163,13 @@ impl<'a> Component for Confirm<'a> {
|
||||
} else if self.has_info_screen() {
|
||||
// Being on the "main" screen but with an info screen available on the right
|
||||
match msg {
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) => Some(ConfirmMsg::Cancel),
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle)) => {
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) => {
|
||||
Some(ConfirmMsg::Cancel)
|
||||
}
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, _)) => {
|
||||
Some(ConfirmMsg::Confirm)
|
||||
}
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Right)) => {
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Right, _)) => {
|
||||
self.showing_info_screen = true;
|
||||
self.update_everything(ctx);
|
||||
None
|
||||
@ -176,8 +178,10 @@ impl<'a> Component for Confirm<'a> {
|
||||
}
|
||||
} else if self.two_btn_confirm {
|
||||
match msg {
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) => Some(ConfirmMsg::Cancel),
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle)) => {
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) => {
|
||||
Some(ConfirmMsg::Cancel)
|
||||
}
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, _)) => {
|
||||
Some(ConfirmMsg::Confirm)
|
||||
}
|
||||
_ => None,
|
||||
@ -185,8 +189,12 @@ impl<'a> Component for Confirm<'a> {
|
||||
} else {
|
||||
// There is just one main screen without info screen
|
||||
match msg {
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Left)) => Some(ConfirmMsg::Cancel),
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Right)) => Some(ConfirmMsg::Confirm),
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) => {
|
||||
Some(ConfirmMsg::Cancel)
|
||||
}
|
||||
Some(ButtonControllerMsg::Triggered(ButtonPos::Right, _)) => {
|
||||
Some(ConfirmMsg::Confirm)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label, Pad},
|
||||
geometry::{Alignment2D, Rect},
|
||||
geometry::{Alignment, Alignment2D, Rect},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@ -32,10 +32,11 @@ pub struct Intro<'a> {
|
||||
title: Child<Label<&'a str>>,
|
||||
buttons: Child<ButtonController<&'static str>>,
|
||||
text: Child<Label<&'a str>>,
|
||||
warn: Option<Child<Label<&'a str>>>,
|
||||
}
|
||||
|
||||
impl<'a> Intro<'a> {
|
||||
pub fn new(title: &'a str, content: &'a str) -> Self {
|
||||
pub fn new(title: &'a str, content: &'a str, fw_ok: bool) -> Self {
|
||||
Self {
|
||||
bg: Pad::with_background(BLD_BG).with_clear(),
|
||||
title: Child::new(Label::centered(title, TEXT_NORMAL).vertically_centered()),
|
||||
@ -44,6 +45,10 @@ impl<'a> Intro<'a> {
|
||||
RIGHT_BUTTON_TEXT,
|
||||
))),
|
||||
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
|
||||
warn: (!fw_ok).then_some(Child::new(
|
||||
Label::new("FIRMWARE CORRUPTED", Alignment::Start, TEXT_NORMAL)
|
||||
.vertically_centered(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,16 +66,25 @@ impl<'a> Component for Intro<'a> {
|
||||
self.title.place(title_area);
|
||||
self.buttons.place(buttons_area);
|
||||
self.text.place(text_area);
|
||||
|
||||
if self.warn.is_some() {
|
||||
let (warn_area, text_area) = text_area.split_top(10);
|
||||
self.warn.place(warn_area);
|
||||
self.text.place(text_area);
|
||||
} else {
|
||||
self.text.place(text_area);
|
||||
}
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let msg = self.buttons.event(ctx, event);
|
||||
|
||||
if let Some(Triggered(ButtonPos::Left)) = msg {
|
||||
if let Some(Triggered(ButtonPos::Left, _)) = msg {
|
||||
return Some(Self::Msg::InstallFirmware);
|
||||
};
|
||||
if let Some(Triggered(ButtonPos::Right)) = msg {
|
||||
if let Some(Triggered(ButtonPos::Right, _)) = msg {
|
||||
return Some(Self::Msg::GoToMenu);
|
||||
};
|
||||
None
|
||||
@ -82,6 +96,7 @@ impl<'a> Component for Intro<'a> {
|
||||
let area = self.bg.area;
|
||||
ICON_WARN_TITLE.draw(area.top_left(), Alignment2D::TOP_LEFT, BLD_FG, BLD_BG);
|
||||
ICON_WARN_TITLE.draw(area.top_right(), Alignment2D::TOP_RIGHT, BLD_FG, BLD_BG);
|
||||
self.warn.paint();
|
||||
self.text.paint();
|
||||
self.buttons.paint();
|
||||
}
|
||||
@ -89,6 +104,7 @@ impl<'a> Component for Intro<'a> {
|
||||
#[cfg(feature = "ui_bounds")]
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
self.title.bounds(sink);
|
||||
self.warn.bounds(sink);
|
||||
self.text.bounds(sink);
|
||||
self.buttons.bounds(sink);
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
#[cfg(feature = "ui_debug")]
|
||||
use crate::trace::{Trace, Tracer};
|
||||
use crate::ui::{
|
||||
component::{Child, Component, Event, EventCtx, Pad},
|
||||
constant::screen,
|
||||
display,
|
||||
display::{Font, Icon},
|
||||
geometry::{Alignment2D, Offset, Point, Rect},
|
||||
use crate::{
|
||||
trezorhal::secbool::{secbool, sectrue},
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Pad},
|
||||
constant::screen,
|
||||
display,
|
||||
display::{Font, Icon},
|
||||
geometry::{Alignment2D, Offset, Point, Rect},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@ -17,9 +20,9 @@ use super::{
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MenuMsg {
|
||||
Close = 1,
|
||||
Reboot = 2,
|
||||
FactoryReset = 3,
|
||||
Close = 0xAABBCCDD,
|
||||
Reboot = 0x11223344,
|
||||
FactoryReset = 0x55667788,
|
||||
}
|
||||
impl ReturnToC for MenuMsg {
|
||||
fn return_to_c(self) -> u32 {
|
||||
@ -74,17 +77,19 @@ impl Trace for MenuChoice {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MenuChoiceFactory;
|
||||
pub struct MenuChoiceFactory {
|
||||
firmware_present: secbool,
|
||||
}
|
||||
|
||||
impl MenuChoiceFactory {
|
||||
const CHOICES: [(&'static str, &'static str, Icon); CHOICE_LENGTH] = [
|
||||
("Factory", "reset", ICON_TRASH),
|
||||
("Reboot", "Trezor", ICON_REDO),
|
||||
("Exit", "menu", ICON_EXIT),
|
||||
("Reboot", "Trezor", ICON_REDO),
|
||||
];
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new(firmware_present: secbool) -> Self {
|
||||
Self { firmware_present }
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +98,11 @@ impl ChoiceFactory<&'static str> for MenuChoiceFactory {
|
||||
type Item = MenuChoice;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
CHOICE_LENGTH
|
||||
if self.firmware_present == sectrue {
|
||||
CHOICE_LENGTH
|
||||
} else {
|
||||
CHOICE_LENGTH - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||
@ -104,8 +113,8 @@ impl ChoiceFactory<&'static str> for MenuChoiceFactory {
|
||||
);
|
||||
let action = match choice_index {
|
||||
0 => MenuMsg::FactoryReset,
|
||||
1 => MenuMsg::Reboot,
|
||||
2 => MenuMsg::Close,
|
||||
1 => MenuMsg::Close,
|
||||
2 if self.firmware_present == sectrue => MenuMsg::Reboot,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
(choice_item, action)
|
||||
@ -118,8 +127,8 @@ pub struct Menu {
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn new() -> Self {
|
||||
let choices = MenuChoiceFactory::new();
|
||||
pub fn new(firmware_present: secbool) -> Self {
|
||||
let choices = MenuChoiceFactory::new(firmware_present);
|
||||
Self {
|
||||
pad: Pad::with_background(BLD_BG).with_clear(),
|
||||
choice_page: Child::new(
|
||||
@ -141,7 +150,7 @@ impl Component for Menu {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.choice_page.event(ctx, event)
|
||||
self.choice_page.event(ctx, event).map(|evt| evt.0)
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
|
@ -21,11 +21,14 @@ mod menu;
|
||||
mod theme;
|
||||
mod welcome;
|
||||
|
||||
use crate::ui::{
|
||||
constant,
|
||||
constant::HEIGHT,
|
||||
geometry::Point,
|
||||
model_tr::theme::{ICON_ARM_LEFT, ICON_ARM_RIGHT, WHITE},
|
||||
use crate::{
|
||||
trezorhal::secbool::secbool,
|
||||
ui::{
|
||||
constant,
|
||||
constant::HEIGHT,
|
||||
geometry::Point,
|
||||
model_tr::theme::{ICON_ARM_LEFT, ICON_ARM_RIGHT, WHITE},
|
||||
},
|
||||
};
|
||||
use confirm::Confirm;
|
||||
use connect::Connect;
|
||||
@ -192,8 +195,8 @@ extern "C" fn screen_unlock_bootloader_success() {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_menu(_bld_version: *const cty::c_char) -> u32 {
|
||||
run(&mut Menu::new())
|
||||
extern "C" fn screen_menu(firmware_present: secbool) -> u32 {
|
||||
run(&mut Menu::new(firmware_present))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -202,6 +205,7 @@ extern "C" fn screen_intro(
|
||||
vendor_str: *const cty::c_char,
|
||||
vendor_str_len: u8,
|
||||
version: *const cty::c_char,
|
||||
fw_ok: bool,
|
||||
) -> u32 {
|
||||
let vendor = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) });
|
||||
let version = unwrap!(unsafe { from_c_str(version) });
|
||||
@ -217,7 +221,7 @@ extern "C" fn screen_intro(
|
||||
unwrap!(version_str.push_str("\nby "));
|
||||
unwrap!(version_str.push_str(vendor));
|
||||
|
||||
let mut frame = Intro::new(title_str.as_str(), version_str.as_str());
|
||||
let mut frame = Intro::new(title_str.as_str(), version_str.as_str(), fw_ok);
|
||||
run(&mut frame)
|
||||
}
|
||||
|
||||
@ -301,7 +305,7 @@ extern "C" fn screen_wipe_success() {
|
||||
let title = Label::centered("Trezor Reset", theme::TEXT_BOLD).vertically_centered();
|
||||
|
||||
let content =
|
||||
Label::centered("Reconnect\nthe device", theme::TEXT_NORMAL).vertically_centered();
|
||||
Label::centered("Please reconnect\nthe device", theme::TEXT_NORMAL).vertically_centered();
|
||||
|
||||
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, true);
|
||||
show(&mut frame);
|
||||
@ -339,15 +343,24 @@ extern "C" fn screen_install_fail() {
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_install_success(
|
||||
reboot_msg: *const cty::c_char,
|
||||
restart_seconds: u8,
|
||||
_initial_setup: bool,
|
||||
complete_draw: bool,
|
||||
) {
|
||||
let msg = unwrap!(unsafe { from_c_str(reboot_msg) });
|
||||
let mut reboot_msg = BootloaderString::new();
|
||||
|
||||
if restart_seconds >= 1 {
|
||||
unwrap!(reboot_msg.push_str("Restarting in "));
|
||||
// in practice, restart_seconds is 5 or less so this is fine
|
||||
let seconds_char = b'0' + restart_seconds % 10;
|
||||
unwrap!(reboot_msg.push(seconds_char as char));
|
||||
} else {
|
||||
unwrap!(reboot_msg.push_str("Reconnect the device"));
|
||||
}
|
||||
|
||||
let title = Label::centered("Firmware installed", theme::TEXT_BOLD).vertically_centered();
|
||||
|
||||
let content = Label::centered(msg, theme::TEXT_NORMAL).vertically_centered();
|
||||
let content = Label::centered(reboot_msg.as_str(), theme::TEXT_NORMAL).vertically_centered();
|
||||
|
||||
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, complete_draw);
|
||||
show(&mut frame);
|
||||
|
@ -204,7 +204,7 @@ where
|
||||
};
|
||||
|
||||
let button_event = self.buttons.event(ctx, event);
|
||||
if let Some(ButtonControllerMsg::Triggered(button)) = button_event {
|
||||
if let Some(ButtonControllerMsg::Triggered(button, _)) = button_event {
|
||||
if self.is_in_subpage() {
|
||||
match button {
|
||||
ButtonPos::Left => {
|
||||
|
@ -349,6 +349,7 @@ pub struct ButtonDetails<T> {
|
||||
with_arms: bool,
|
||||
fixed_width: Option<i16>,
|
||||
offset: Offset,
|
||||
pub send_long_press: bool,
|
||||
}
|
||||
|
||||
impl<T> ButtonDetails<T>
|
||||
@ -364,6 +365,7 @@ where
|
||||
with_arms: false,
|
||||
fixed_width: None,
|
||||
offset: Offset::zero(),
|
||||
send_long_press: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,6 +378,7 @@ where
|
||||
with_arms: false,
|
||||
fixed_width: None,
|
||||
offset: Offset::zero(),
|
||||
send_long_press: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
strutil::StringType,
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
component::{base::Event, Component, EventCtx, Pad, TimerToken},
|
||||
event::{ButtonEvent, PhysicalButton},
|
||||
@ -39,8 +39,13 @@ enum ButtonState {
|
||||
}
|
||||
|
||||
pub enum ButtonControllerMsg {
|
||||
/// Button was pressed down.
|
||||
Pressed(ButtonPos),
|
||||
Triggered(ButtonPos),
|
||||
/// Which button was triggered, and whether it was pressed for a longer
|
||||
/// time before releasing.
|
||||
Triggered(ButtonPos, bool),
|
||||
/// Button was pressed and held for longer time (not released yet).
|
||||
LongPressed(ButtonPos),
|
||||
}
|
||||
|
||||
/// Defines what kind of button should be currently used.
|
||||
@ -105,6 +110,16 @@ where
|
||||
{
|
||||
pos: ButtonPos,
|
||||
button_type: ButtonType<T>,
|
||||
/// Holds the timestamp of when the button was pressed.
|
||||
pressed_since: Option<Instant>,
|
||||
/// How long the button should be pressed to send `long_press=true` in
|
||||
/// `ButtonControllerMsg::Triggered`
|
||||
long_press_ms: u32,
|
||||
/// Timer for sending `ButtonControllerMsg::LongPressed`
|
||||
long_pressed_timer: Option<TimerToken>,
|
||||
/// Whether it should even send `ButtonControllerMsg::LongPressed` events
|
||||
/// (optional)
|
||||
send_long_press: bool,
|
||||
}
|
||||
|
||||
impl<T> ButtonContainer<T>
|
||||
@ -114,9 +129,17 @@ where
|
||||
/// Supplying `None` as `btn_details` marks the button inactive
|
||||
/// (it can be later activated in `set()`).
|
||||
pub fn new(pos: ButtonPos, btn_details: Option<ButtonDetails<T>>) -> Self {
|
||||
const DEFAULT_LONG_PRESS_MS: u32 = 1000;
|
||||
let send_long_press = btn_details
|
||||
.as_ref()
|
||||
.map_or(false, |btn| btn.send_long_press);
|
||||
Self {
|
||||
pos,
|
||||
button_type: ButtonType::from_button_details(pos, btn_details),
|
||||
pressed_since: None,
|
||||
long_press_ms: DEFAULT_LONG_PRESS_MS,
|
||||
long_pressed_timer: None,
|
||||
send_long_press,
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +147,9 @@ where
|
||||
///
|
||||
/// Passing `None` as `btn_details` will mark the button as inactive.
|
||||
pub fn set(&mut self, btn_details: Option<ButtonDetails<T>>, button_area: Rect) {
|
||||
self.send_long_press = btn_details
|
||||
.as_ref()
|
||||
.map_or(false, |btn| btn.send_long_press);
|
||||
self.button_type = ButtonType::from_button_details(self.pos, btn_details);
|
||||
self.button_type.place(button_area);
|
||||
}
|
||||
@ -151,7 +177,15 @@ where
|
||||
/// hold.
|
||||
pub fn maybe_trigger(&mut self, ctx: &mut EventCtx) -> Option<ButtonControllerMsg> {
|
||||
match self.button_type {
|
||||
ButtonType::Button(_) => Some(ButtonControllerMsg::Triggered(self.pos)),
|
||||
ButtonType::Button(_) => {
|
||||
// Finding out whether the button was long-pressed
|
||||
let long_press = self.pressed_since.map_or(false, |since| {
|
||||
Instant::now().saturating_duration_since(since).to_millis() > self.long_press_ms
|
||||
});
|
||||
self.pressed_since = None;
|
||||
self.long_pressed_timer = None;
|
||||
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
|
||||
}
|
||||
_ => {
|
||||
self.hold_ended(ctx);
|
||||
None
|
||||
@ -169,6 +203,27 @@ where
|
||||
false
|
||||
}
|
||||
|
||||
/// Saving the timestamp of when the button was pressed.
|
||||
/// Also requesting a timer for long-press if wanted.
|
||||
pub fn got_pressed(&mut self, ctx: &mut EventCtx) {
|
||||
self.pressed_since = Some(Instant::now());
|
||||
if self.send_long_press {
|
||||
self.long_pressed_timer =
|
||||
Some(ctx.request_timer(Duration::from_millis(self.long_press_ms)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the pressed information.
|
||||
pub fn reset(&mut self) {
|
||||
self.pressed_since = None;
|
||||
self.long_pressed_timer = None;
|
||||
}
|
||||
|
||||
/// Whether token matches what we have
|
||||
pub fn is_timer_token(&self, token: TimerToken) -> bool {
|
||||
self.long_pressed_timer == Some(token)
|
||||
}
|
||||
|
||||
/// Registering hold event.
|
||||
pub fn hold_started(&mut self, ctx: &mut EventCtx) {
|
||||
if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
|
||||
@ -283,18 +338,63 @@ where
|
||||
if self.left_btn.htc_got_triggered(ctx, event) {
|
||||
self.state = ButtonState::HTCNeedsRelease(PhysicalButton::Left);
|
||||
self.set_pressed(ctx, false, false, false);
|
||||
return Some(ButtonControllerMsg::Triggered(ButtonPos::Left));
|
||||
return Some(ButtonControllerMsg::Triggered(ButtonPos::Left, true));
|
||||
} else if self.middle_btn.htc_got_triggered(ctx, event) {
|
||||
self.state = ButtonState::Nothing;
|
||||
self.set_pressed(ctx, false, false, false);
|
||||
return Some(ButtonControllerMsg::Triggered(ButtonPos::Middle));
|
||||
return Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, true));
|
||||
} else if self.right_btn.htc_got_triggered(ctx, event) {
|
||||
self.state = ButtonState::HTCNeedsRelease(PhysicalButton::Right);
|
||||
self.set_pressed(ctx, false, false, false);
|
||||
return Some(ButtonControllerMsg::Triggered(ButtonPos::Right));
|
||||
return Some(ButtonControllerMsg::Triggered(ButtonPos::Right, true));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn reset_button_presses(&mut self) {
|
||||
self.left_btn.reset();
|
||||
self.middle_btn.reset();
|
||||
self.right_btn.reset();
|
||||
}
|
||||
|
||||
fn got_pressed(&mut self, ctx: &mut EventCtx, pos: ButtonPos) {
|
||||
// Only one (virtual) button can be pressed at the same time
|
||||
self.reset_button_presses();
|
||||
match pos {
|
||||
ButtonPos::Left => {
|
||||
self.left_btn.got_pressed(ctx);
|
||||
}
|
||||
ButtonPos::Middle => {
|
||||
self.middle_btn.got_pressed(ctx);
|
||||
}
|
||||
ButtonPos::Right => {
|
||||
self.right_btn.got_pressed(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_long_press_timer_token(&mut self, token: TimerToken) -> Option<ButtonPos> {
|
||||
if self.left_btn.is_timer_token(token) {
|
||||
return Some(ButtonPos::Left);
|
||||
}
|
||||
if self.middle_btn.is_timer_token(token) {
|
||||
return Some(ButtonPos::Middle);
|
||||
}
|
||||
if self.right_btn.is_timer_token(token) {
|
||||
return Some(ButtonPos::Right);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Resetting the state of the controller.
|
||||
pub fn reset_state(&mut self, ctx: &mut EventCtx) {
|
||||
self.state = ButtonState::Nothing;
|
||||
self.reset_button_presses();
|
||||
self.set_pressed(ctx, false, false, false);
|
||||
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
|
||||
ignore_btn_delay.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for ButtonController<T>
|
||||
@ -325,11 +425,13 @@ where
|
||||
match which {
|
||||
// ▼ *
|
||||
PhysicalButton::Left => {
|
||||
self.got_pressed(ctx, ButtonPos::Left);
|
||||
self.left_btn.hold_started(ctx);
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Left))
|
||||
}
|
||||
// * ▼
|
||||
PhysicalButton::Right => {
|
||||
self.got_pressed(ctx, ButtonPos::Right);
|
||||
self.right_btn.hold_started(ctx);
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Right))
|
||||
}
|
||||
@ -369,6 +471,7 @@ where
|
||||
return None;
|
||||
}
|
||||
}
|
||||
self.got_pressed(ctx, ButtonPos::Middle);
|
||||
self.middle_hold_started(ctx);
|
||||
(
|
||||
// ↓ ↓
|
||||
@ -399,7 +502,7 @@ where
|
||||
// ▲ * | * ▲
|
||||
ButtonEvent::ButtonReleased(b) if b != which_up => {
|
||||
// _ _
|
||||
// Both button needs to be clickable now
|
||||
// Both buttons need to be clickable now
|
||||
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
|
||||
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
|
||||
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
|
||||
@ -447,6 +550,9 @@ where
|
||||
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
|
||||
ignore_btn_delay.handle_timer_token(token);
|
||||
}
|
||||
if let Some(pos) = self.handle_long_press_timer_token(token) {
|
||||
return Some(ButtonControllerMsg::LongPressed(pos));
|
||||
}
|
||||
self.handle_htc_expiration(ctx, event)
|
||||
}
|
||||
_ => None,
|
||||
@ -544,6 +650,13 @@ impl IgnoreButtonDelay {
|
||||
self.right_clickable_timer = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.left_clickable = true;
|
||||
self.right_clickable = true;
|
||||
self.left_clickable_timer = None;
|
||||
self.right_clickable_timer = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Component allowing for automatically moving through items (e.g. Choice
|
||||
|
@ -256,7 +256,7 @@ where
|
||||
|
||||
// Do something when a button was triggered
|
||||
// and we have some action connected with it
|
||||
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
|
||||
if let Some(ButtonControllerMsg::Triggered(pos, _)) = button_event {
|
||||
// When there is a previous or next screen in the current flow,
|
||||
// handle that first and in case it triggers, then do not continue
|
||||
if self.event_consumed_by_current_choice(ctx, pos) {
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
super::constant, common::display_center, theme, ButtonController, ButtonControllerMsg,
|
||||
ButtonLayout,
|
||||
ButtonLayout, ButtonPos, CancelConfirmMsg,
|
||||
};
|
||||
|
||||
const AREA: Rect = constant::screen();
|
||||
@ -144,7 +144,7 @@ where
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
Self::event_usb(self, ctx, event);
|
||||
// HTC press of any button will lock the device
|
||||
if let Some(ButtonControllerMsg::Triggered(_)) = self.invisible_buttons.event(ctx, event) {
|
||||
if let Some(ButtonControllerMsg::Triggered(..)) = self.invisible_buttons.event(ctx, event) {
|
||||
return Some(());
|
||||
}
|
||||
None
|
||||
@ -202,7 +202,7 @@ where
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
// Press of any button will unlock the device
|
||||
if let Some(ButtonControllerMsg::Triggered(_)) = self.invisible_buttons.event(ctx, event) {
|
||||
if let Some(ButtonControllerMsg::Triggered(..)) = self.invisible_buttons.event(ctx, event) {
|
||||
return Some(());
|
||||
}
|
||||
None
|
||||
@ -220,6 +220,70 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfirmHomescreen<T, F>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
title: Child<Label<T>>,
|
||||
buffer_func: F,
|
||||
buttons: Child<ButtonController<T>>,
|
||||
}
|
||||
|
||||
impl<T, F> ConfirmHomescreen<T, F>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
{
|
||||
pub fn new(title: T, buffer_func: F) -> Self {
|
||||
let btn_layout = ButtonLayout::cancel_none_text("CHANGE".into());
|
||||
ConfirmHomescreen {
|
||||
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
|
||||
buffer_func,
|
||||
buttons: Child::new(ButtonController::new(btn_layout)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, F> Component for ConfirmHomescreen<T, F>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
F: Fn() -> &'a [u8],
|
||||
{
|
||||
type Msg = CancelConfirmMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
let (title_content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
|
||||
let title_height = theme::TEXT_BOLD.text_font.line_height();
|
||||
let (title_area, _) = title_content_area.split_top(title_height);
|
||||
self.title.place(title_area);
|
||||
self.buttons.place(button_area);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
// Left button cancels, right confirms
|
||||
if let Some(ButtonControllerMsg::Triggered(pos, _)) = self.buttons.event(ctx, event) {
|
||||
match pos {
|
||||
ButtonPos::Left => return Some(CancelConfirmMsg::Cancelled),
|
||||
ButtonPos::Right => return Some(CancelConfirmMsg::Confirmed),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
// Drawing the image full-screen first and then other things on top
|
||||
let toif_data = unwrap!(Toif::new((self.buffer_func)()));
|
||||
toif_data.draw(TOP_CENTER, Alignment2D::TOP_CENTER, theme::FG, theme::BG);
|
||||
// Need to make all the title background black, so the title text is well
|
||||
// visible
|
||||
let title_area = self.title.inner().area();
|
||||
rect_fill(title_area, theme::BG);
|
||||
self.title.paint();
|
||||
self.buttons.paint();
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG-ONLY SECTION BELOW
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
@ -243,3 +307,14 @@ where
|
||||
t.child("label", &self.label);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, F> crate::trace::Trace for ConfirmHomescreen<T, F>
|
||||
where
|
||||
T: StringType,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("ConfirmHomescreen");
|
||||
t.child("title", &self.title);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,12 @@ pub trait Choice<T: StringType> {
|
||||
fn btn_layout(&self) -> ButtonLayout<T> {
|
||||
ButtonLayout::default_three_icons()
|
||||
}
|
||||
|
||||
/// Whether it is possible to do the middle action event without
|
||||
/// releasing the button - after long-press duration is reached.
|
||||
fn trigger_middle_without_release(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface for a specific component efficiently giving
|
||||
@ -130,7 +136,7 @@ where
|
||||
/// Need to update the initial button layout.
|
||||
pub fn with_initial_page_counter(mut self, page_counter: usize) -> Self {
|
||||
self.page_counter = page_counter;
|
||||
let initial_btn_layout = self.get_current_choice().0.btn_layout();
|
||||
let initial_btn_layout = self.get_current_item().btn_layout();
|
||||
self.buttons = Child::new(
|
||||
ButtonController::new(initial_btn_layout)
|
||||
.with_ignore_btn_delay(constant::IGNORE_OTHER_BTN_MS),
|
||||
@ -235,7 +241,7 @@ where
|
||||
}
|
||||
|
||||
// Getting the remaining left and right areas.
|
||||
let center_width = self.get_current_choice().0.width_center();
|
||||
let center_width = self.get_current_item().width_center();
|
||||
let (left_area, _center_area, right_area) = center_row_area.split_center(center_width);
|
||||
|
||||
// Possibly drawing on the left side.
|
||||
@ -277,14 +283,23 @@ where
|
||||
}
|
||||
|
||||
/// Getting the choice on the current index
|
||||
pub fn get_current_choice(&self) -> (<F as ChoiceFactory<T>>::Item, A) {
|
||||
fn get_current_choice(&self) -> (<F as ChoiceFactory<T>>::Item, A) {
|
||||
self.choices.get(self.page_counter)
|
||||
}
|
||||
|
||||
/// Getting the current item
|
||||
pub fn get_current_item(&self) -> <F as ChoiceFactory<T>>::Item {
|
||||
self.get_current_choice().0
|
||||
}
|
||||
|
||||
/// Getting the current action
|
||||
pub fn get_current_action(&self) -> A {
|
||||
self.get_current_choice().1
|
||||
}
|
||||
|
||||
/// Display the current choice in the middle.
|
||||
fn show_current_choice(&mut self, area: Rect) {
|
||||
self.get_current_choice()
|
||||
.0
|
||||
self.get_current_item()
|
||||
.paint_center(area, self.inverse_selected_item);
|
||||
|
||||
// Color inversion is just one-time thing.
|
||||
@ -406,7 +421,7 @@ where
|
||||
/// If defined in the current choice, setting their text,
|
||||
/// whether they are long-pressed, and painting them.
|
||||
fn set_buttons(&mut self, ctx: &mut EventCtx) {
|
||||
let btn_layout = self.get_current_choice().0.btn_layout();
|
||||
let btn_layout = self.get_current_item().btn_layout();
|
||||
self.buttons.mutate(ctx, |ctx, buttons| {
|
||||
buttons.set(btn_layout);
|
||||
// When user holds one of the buttons, highlighting it.
|
||||
@ -474,7 +489,7 @@ where
|
||||
F: ChoiceFactory<T, Action = A>,
|
||||
T: StringType + Clone,
|
||||
{
|
||||
type Msg = A;
|
||||
type Msg = (A, bool);
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
|
||||
@ -516,7 +531,7 @@ where
|
||||
// Stopping the automatic movement when the released button is the same as the
|
||||
// direction we were moving, or when the pressed button is the
|
||||
// opposite one (user does middle-click).
|
||||
if matches!(button_event, Some(ButtonControllerMsg::Triggered(pos)) if pos == moving_direction)
|
||||
if matches!(button_event, Some(ButtonControllerMsg::Triggered(pos, _)) if pos == moving_direction)
|
||||
|| matches!(button_event, Some(ButtonControllerMsg::Pressed(pos)) if pos != moving_direction)
|
||||
{
|
||||
self.holding_mover.stop_moving();
|
||||
@ -534,7 +549,7 @@ where
|
||||
}
|
||||
|
||||
// There was a legitimate button event - doing some action
|
||||
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
|
||||
if let Some(ButtonControllerMsg::Triggered(pos, long_press)) = button_event {
|
||||
match pos {
|
||||
ButtonPos::Left => {
|
||||
// Clicked BACK. Decrease the page counter.
|
||||
@ -547,12 +562,24 @@ where
|
||||
self.move_right(ctx);
|
||||
}
|
||||
ButtonPos::Middle => {
|
||||
// Clicked SELECT. Send current choice index
|
||||
// Clicked SELECT. Send current choice index with information about long-press
|
||||
self.clear_and_repaint(ctx);
|
||||
return Some(self.get_current_choice().1);
|
||||
return Some((self.get_current_action(), long_press));
|
||||
}
|
||||
}
|
||||
};
|
||||
// The middle button was pressed for longer time - sending the Event with long
|
||||
// press. Also resetting the functional and visual state of the buttons.
|
||||
// Only doing this when the item is configured to do so
|
||||
if let Some(ButtonControllerMsg::LongPressed(ButtonPos::Middle)) = button_event {
|
||||
if self.get_current_item().trigger_middle_without_release() {
|
||||
self.buttons.mutate(ctx, |ctx, buttons| {
|
||||
buttons.reset_state(ctx);
|
||||
});
|
||||
self.clear_and_repaint(ctx);
|
||||
return Some((self.get_current_action(), true));
|
||||
}
|
||||
};
|
||||
// The middle button was pressed, highlighting the current choice by color
|
||||
// inversion.
|
||||
if let Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)) = button_event {
|
||||
|
@ -19,6 +19,7 @@ pub struct ChoiceItem<T: StringType> {
|
||||
icon: Option<Icon>,
|
||||
btn_layout: ButtonLayout<T>,
|
||||
font: Font,
|
||||
middle_action_without_release: bool,
|
||||
}
|
||||
|
||||
impl<T: StringType> ChoiceItem<T> {
|
||||
@ -28,6 +29,7 @@ impl<T: StringType> ChoiceItem<T> {
|
||||
icon: None,
|
||||
btn_layout,
|
||||
font: theme::FONT_CHOICE_ITEMS,
|
||||
middle_action_without_release: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +45,15 @@ impl<T: StringType> ChoiceItem<T> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows for middle action without release.
|
||||
pub fn with_middle_action_without_release(mut self) -> Self {
|
||||
self.middle_action_without_release = true;
|
||||
if let Some(middle) = self.btn_layout.btn_middle.as_mut() {
|
||||
middle.send_long_press = true;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Setting left button.
|
||||
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<T>>) {
|
||||
self.btn_layout.btn_left = btn_left;
|
||||
@ -117,6 +128,11 @@ where
|
||||
fn btn_layout(&self) -> ButtonLayout<T> {
|
||||
self.btn_layout.clone()
|
||||
}
|
||||
|
||||
/// Whether to do middle action without release
|
||||
fn trigger_middle_without_release(&self) -> bool {
|
||||
self.middle_action_without_release
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_rounded_highlight(area: Rect, size: Offset, inverse: bool) {
|
||||
|
@ -78,7 +78,7 @@ where
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.choice_page.event(ctx, event)
|
||||
self.choice_page.event(ctx, event).map(|evt| evt.0)
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
|
@ -43,50 +43,63 @@ const DIGITS_INDEX: usize = 5;
|
||||
const SPECIAL_INDEX: usize = 6;
|
||||
const SPACE_INDEX: usize = 7;
|
||||
|
||||
// Menu text, action, icon data, middle button with CONFIRM
|
||||
const MENU: [(&str, PassphraseAction, Option<Icon>, bool); MENU_LENGTH] = [
|
||||
("SHOW", PassphraseAction::Show, Some(theme::ICON_EYE), true),
|
||||
/// Menu text, action, icon data, middle button with CONFIRM, without_release
|
||||
const MENU: [(&str, PassphraseAction, Option<Icon>, bool, bool); MENU_LENGTH] = [
|
||||
(
|
||||
"SHOW",
|
||||
PassphraseAction::Show,
|
||||
Some(theme::ICON_EYE),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"CANCEL_OR_DELETE", // will be chosen dynamically
|
||||
PassphraseAction::CancelOrDelete,
|
||||
None,
|
||||
true,
|
||||
true, // without_release
|
||||
),
|
||||
(
|
||||
"ENTER",
|
||||
PassphraseAction::Enter,
|
||||
Some(theme::ICON_TICK),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"abc",
|
||||
PassphraseAction::Category(ChoiceCategory::LowercaseLetter),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"ABC",
|
||||
PassphraseAction::Category(ChoiceCategory::UppercaseLetter),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"123",
|
||||
PassphraseAction::Category(ChoiceCategory::Digit),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"#$!",
|
||||
PassphraseAction::Category(ChoiceCategory::SpecialSymbol),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(
|
||||
"SPACE",
|
||||
PassphraseAction::Character(' '),
|
||||
Some(theme::ICON_SPACE),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
@ -164,7 +177,7 @@ impl ChoiceFactoryPassphrase {
|
||||
choice_index: usize,
|
||||
) -> (ChoiceItem<T>, PassphraseAction) {
|
||||
// More options for CANCEL/DELETE button
|
||||
let (mut text, action, mut icon, show_confirm) = MENU[choice_index];
|
||||
let (mut text, action, mut icon, show_confirm, without_release) = MENU[choice_index];
|
||||
if matches!(action, PassphraseAction::CancelOrDelete) {
|
||||
if self.is_empty {
|
||||
text = "CANCEL";
|
||||
@ -183,6 +196,11 @@ impl ChoiceFactoryPassphrase {
|
||||
menu_item.set_middle_btn(Some(confirm_btn));
|
||||
}
|
||||
|
||||
// Making middle button create LongPress events
|
||||
if without_release {
|
||||
menu_item = menu_item.with_middle_action_without_release();
|
||||
}
|
||||
|
||||
if let Some(icon) = icon {
|
||||
menu_item = menu_item.with_icon(icon);
|
||||
}
|
||||
@ -291,6 +309,10 @@ where
|
||||
self.textbox.delete_last(ctx);
|
||||
}
|
||||
|
||||
fn delete_all_digits(&mut self, ctx: &mut EventCtx) {
|
||||
self.textbox.clear(ctx);
|
||||
}
|
||||
|
||||
/// Displaying the MENU
|
||||
fn show_menu_page(&mut self, ctx: &mut EventCtx) {
|
||||
let menu_choices = ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, self.is_empty());
|
||||
@ -359,13 +381,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(action) = self.choice_page.event(ctx, event) {
|
||||
if let Some((action, long_press)) = self.choice_page.event(ctx, event) {
|
||||
match action {
|
||||
PassphraseAction::CancelOrDelete => {
|
||||
if self.is_empty() {
|
||||
return Some(CancelConfirmMsg::Cancelled);
|
||||
} else {
|
||||
self.delete_last_digit(ctx);
|
||||
// Deleting all when long-pressed
|
||||
if long_press {
|
||||
self.delete_all_digits(ctx);
|
||||
} else {
|
||||
self.delete_last_digit(ctx);
|
||||
}
|
||||
self.update_passphrase_dots(ctx);
|
||||
if self.is_empty() {
|
||||
// Allowing for DELETE/CANCEL change
|
||||
|
@ -27,20 +27,22 @@ const EMPTY_PIN_STR: &str = "_";
|
||||
|
||||
const CHOICE_LENGTH: usize = 13;
|
||||
const NUMBER_START_INDEX: usize = 3;
|
||||
const CHOICES: [(&str, PinAction, Option<Icon>); CHOICE_LENGTH] = [
|
||||
("DELETE", PinAction::Delete, Some(theme::ICON_DELETE)),
|
||||
("SHOW", PinAction::Show, Some(theme::ICON_EYE)),
|
||||
("ENTER", PinAction::Enter, Some(theme::ICON_TICK)),
|
||||
("0", PinAction::Digit('0'), None),
|
||||
("1", PinAction::Digit('1'), None),
|
||||
("2", PinAction::Digit('2'), None),
|
||||
("3", PinAction::Digit('3'), None),
|
||||
("4", PinAction::Digit('4'), None),
|
||||
("5", PinAction::Digit('5'), None),
|
||||
("6", PinAction::Digit('6'), None),
|
||||
("7", PinAction::Digit('7'), None),
|
||||
("8", PinAction::Digit('8'), None),
|
||||
("9", PinAction::Digit('9'), None),
|
||||
/// Text, action, icon, without_release
|
||||
const CHOICES: [(&str, PinAction, Option<Icon>, bool); CHOICE_LENGTH] = [
|
||||
// DELETE should be triggerable without release (after long-press)
|
||||
("DELETE", PinAction::Delete, Some(theme::ICON_DELETE), true),
|
||||
("SHOW", PinAction::Show, Some(theme::ICON_EYE), false),
|
||||
("ENTER", PinAction::Enter, Some(theme::ICON_TICK), false),
|
||||
("0", PinAction::Digit('0'), None, false),
|
||||
("1", PinAction::Digit('1'), None, false),
|
||||
("2", PinAction::Digit('2'), None, false),
|
||||
("3", PinAction::Digit('3'), None, false),
|
||||
("4", PinAction::Digit('4'), None, false),
|
||||
("5", PinAction::Digit('5'), None, false),
|
||||
("6", PinAction::Digit('6'), None, false),
|
||||
("7", PinAction::Digit('7'), None, false),
|
||||
("8", PinAction::Digit('8'), None, false),
|
||||
("9", PinAction::Digit('9'), None, false),
|
||||
];
|
||||
|
||||
fn get_random_digit_position() -> usize {
|
||||
@ -54,7 +56,7 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
||||
type Item = ChoiceItem<T>;
|
||||
|
||||
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
|
||||
let (choice_str, action, icon) = CHOICES[choice_index];
|
||||
let (choice_str, action, icon, without_release) = CHOICES[choice_index];
|
||||
|
||||
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
|
||||
|
||||
@ -64,6 +66,11 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
||||
choice_item.set_middle_btn(Some(confirm_btn));
|
||||
}
|
||||
|
||||
// Making middle button create LongPress events
|
||||
if without_release {
|
||||
choice_item = choice_item.with_middle_action_without_release();
|
||||
}
|
||||
|
||||
// Adding icons for appropriate items
|
||||
if let Some(icon) = icon {
|
||||
choice_item = choice_item.with_icon(icon);
|
||||
@ -240,29 +247,34 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
match self.choice_page.event(ctx, event) {
|
||||
Some(PinAction::Delete) => {
|
||||
self.textbox.delete_last(ctx);
|
||||
self.update(ctx);
|
||||
None
|
||||
if let Some((action, long_press)) = self.choice_page.event(ctx, event) {
|
||||
match action {
|
||||
PinAction::Delete => {
|
||||
// Deleting all when long-pressed
|
||||
if long_press {
|
||||
self.textbox.clear(ctx);
|
||||
} else {
|
||||
self.textbox.delete_last(ctx);
|
||||
}
|
||||
self.update(ctx);
|
||||
}
|
||||
PinAction::Show => {
|
||||
self.show_real_pin = true;
|
||||
self.update(ctx);
|
||||
}
|
||||
PinAction::Enter => return Some(CancelConfirmMsg::Confirmed),
|
||||
PinAction::Digit(ch) if !self.is_full() => {
|
||||
self.textbox.append(ctx, ch);
|
||||
// Choosing random digit to be shown next
|
||||
self.choice_page
|
||||
.set_page_counter(ctx, get_random_digit_position(), true);
|
||||
self.show_last_digit = true;
|
||||
self.update(ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(PinAction::Show) => {
|
||||
self.show_real_pin = true;
|
||||
self.update(ctx);
|
||||
None
|
||||
}
|
||||
Some(PinAction::Enter) => Some(CancelConfirmMsg::Confirmed),
|
||||
Some(PinAction::Digit(ch)) if !self.is_full() => {
|
||||
self.textbox.append(ctx, ch);
|
||||
// Choosing random digit to be shown next
|
||||
self.choice_page
|
||||
.set_page_counter(ctx, get_random_digit_position(), true);
|
||||
self.show_last_digit = true;
|
||||
self.update(ctx);
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
|
@ -112,7 +112,7 @@ where
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.choice_page.event(ctx, event)
|
||||
self.choice_page.event(ctx, event).map(|evt| evt.0)
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
|
@ -95,7 +95,8 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryWordlist {
|
||||
if choice_index == DELETE_INDEX {
|
||||
return (
|
||||
ChoiceItem::new("DELETE", ButtonLayout::arrow_armed_arrow("CONFIRM".into()))
|
||||
.with_icon(theme::ICON_DELETE),
|
||||
.with_icon(theme::ICON_DELETE)
|
||||
.with_middle_action_without_release(),
|
||||
WordlistAction::Delete,
|
||||
);
|
||||
}
|
||||
@ -156,19 +157,49 @@ where
|
||||
ChoiceFactoryWordlist::new(self.wordlist_type, self.textbox.content())
|
||||
}
|
||||
|
||||
fn get_last_textbox_letter(&self) -> Option<char> {
|
||||
self.textbox.content().chars().last()
|
||||
}
|
||||
|
||||
fn get_new_page_counter(&self, new_choices: &ChoiceFactoryWordlist) -> usize {
|
||||
// Starting at the random position in case of letters and at the beginning in
|
||||
// case of words.
|
||||
if self.offer_words {
|
||||
INITIAL_PAGE_COUNTER
|
||||
} else {
|
||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory<T>>::count(new_choices);
|
||||
// There should be always DELETE and at least one letter
|
||||
assert!(choices_count > 1);
|
||||
if choices_count == 2 {
|
||||
// In case there is only DELETE and one letter, starting on that letter
|
||||
// (regardless of the last letter in the textbox)
|
||||
return INITIAL_PAGE_COUNTER;
|
||||
}
|
||||
// We do not want to end up at the same letter as the last one in the textbox
|
||||
loop {
|
||||
let random_position = get_random_position(choices_count);
|
||||
let current_action =
|
||||
<ChoiceFactoryWordlist as ChoiceFactory<T>>::get(new_choices, random_position)
|
||||
.1;
|
||||
if let WordlistAction::Letter(current_letter) = current_action {
|
||||
if let Some(last_letter) = self.get_last_textbox_letter() {
|
||||
if current_letter == last_letter {
|
||||
// Randomly trying again when the last and current letter match
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
break random_position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the whole page.
|
||||
fn update(&mut self, ctx: &mut EventCtx) {
|
||||
self.update_chosen_letters(ctx);
|
||||
let new_choices = self.get_current_choices();
|
||||
self.offer_words = new_choices.offer_words;
|
||||
// Starting at the random position in case of letters and at the beginning in
|
||||
// case of words
|
||||
let new_page_counter = if self.offer_words {
|
||||
INITIAL_PAGE_COUNTER
|
||||
} else {
|
||||
let choices_count = <ChoiceFactoryWordlist as ChoiceFactory<T>>::count(&new_choices);
|
||||
get_random_position(choices_count)
|
||||
};
|
||||
let new_page_counter = self.get_new_page_counter(&new_choices);
|
||||
// Not using carousel in case of words, as that looks weird in case
|
||||
// there is only one word to choose from.
|
||||
self.choice_page
|
||||
@ -201,19 +232,25 @@ where
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
match self.choice_page.event(ctx, event) {
|
||||
Some(WordlistAction::Delete) => {
|
||||
self.textbox.delete_last(ctx);
|
||||
self.update(ctx);
|
||||
if let Some((action, long_press)) = self.choice_page.event(ctx, event) {
|
||||
match action {
|
||||
WordlistAction::Delete => {
|
||||
// Deleting all when long-pressed
|
||||
if long_press {
|
||||
self.textbox.clear(ctx);
|
||||
} else {
|
||||
self.textbox.delete_last(ctx);
|
||||
}
|
||||
self.update(ctx);
|
||||
}
|
||||
WordlistAction::Letter(letter) => {
|
||||
self.textbox.append(ctx, letter);
|
||||
self.update(ctx);
|
||||
}
|
||||
WordlistAction::Word(word) => {
|
||||
return Some(word);
|
||||
}
|
||||
}
|
||||
Some(WordlistAction::Letter(letter)) => {
|
||||
self.textbox.append(ctx, letter);
|
||||
self.update(ctx);
|
||||
}
|
||||
Some(WordlistAction::Word(word)) => {
|
||||
return Some(word);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ pub use flow::Flow;
|
||||
pub use flow_pages::{FlowPages, Page};
|
||||
pub use frame::{Frame, ScrollableContent, ScrollableFrame};
|
||||
#[cfg(feature = "micropython")]
|
||||
pub use homescreen::{Homescreen, Lockscreen};
|
||||
pub use homescreen::{ConfirmHomescreen, Homescreen, Lockscreen};
|
||||
pub use input_methods::{
|
||||
number_input::NumberInput,
|
||||
passphrase::PassphraseEntry,
|
||||
|
@ -182,7 +182,7 @@ where
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
ctx.set_page_count(self.page_count());
|
||||
if let Some(ButtonControllerMsg::Triggered(pos)) = self.buttons.event(ctx, event) {
|
||||
if let Some(ButtonControllerMsg::Triggered(pos, _)) = self.buttons.event(ctx, event) {
|
||||
match pos {
|
||||
ButtonPos::Left => {
|
||||
if self.has_previous_page() {
|
||||
|
@ -5,8 +5,10 @@ use crate::ui::{
|
||||
geometry::{Alignment2D, Offset, Point, Rect},
|
||||
};
|
||||
|
||||
const MESSAGE_AREA_START: i16 = 26;
|
||||
const FOOTER_AREA_START: i16 = 40;
|
||||
const MESSAGE_AREA_START: i16 = 24 + 11;
|
||||
const MESSAGE_AREA_START_2L: i16 = 24 + 7;
|
||||
const FOOTER_AREA_START: i16 = MESSAGE_AREA_START + 10;
|
||||
const FOOTER_AREA_START_2L: i16 = MESSAGE_AREA_START_2L + 10;
|
||||
const ICON_TOP: i16 = 12;
|
||||
|
||||
pub struct ResultScreen<'a> {
|
||||
@ -53,15 +55,36 @@ impl<'a> Component for ResultScreen<'a> {
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.bg.place(bounds);
|
||||
|
||||
self.message_top.place(Rect::new(
|
||||
Point::new(0, MESSAGE_AREA_START),
|
||||
Point::new(WIDTH, FOOTER_AREA_START),
|
||||
));
|
||||
|
||||
let bottom_area = Rect::new(Point::new(0, FOOTER_AREA_START), Point::new(WIDTH, HEIGHT));
|
||||
|
||||
self.small_pad.place(bottom_area);
|
||||
self.message_bottom.place(bottom_area);
|
||||
let h = self.message_bottom.inner().text_height(WIDTH);
|
||||
|
||||
if h > 8 {
|
||||
self.message_top.place(Rect::new(
|
||||
Point::new(0, MESSAGE_AREA_START_2L),
|
||||
Point::new(WIDTH, FOOTER_AREA_START_2L),
|
||||
));
|
||||
|
||||
let bottom_area = Rect::new(
|
||||
Point::new(0, FOOTER_AREA_START_2L),
|
||||
Point::new(WIDTH, FOOTER_AREA_START_2L + h),
|
||||
);
|
||||
self.message_bottom.place(bottom_area);
|
||||
} else {
|
||||
self.message_top.place(Rect::new(
|
||||
Point::new(0, MESSAGE_AREA_START),
|
||||
Point::new(WIDTH, FOOTER_AREA_START),
|
||||
));
|
||||
|
||||
let bottom_area = Rect::new(
|
||||
Point::new(0, FOOTER_AREA_START),
|
||||
Point::new(WIDTH, FOOTER_AREA_START + h),
|
||||
);
|
||||
self.message_bottom.place(bottom_area);
|
||||
}
|
||||
|
||||
self.small_pad.place(bottom_area);
|
||||
|
||||
bounds
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ where
|
||||
self.text.event(ctx, event);
|
||||
self.headline.event(ctx, event);
|
||||
|
||||
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Right)) =
|
||||
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Right, _)) =
|
||||
self.buttons.event(ctx, event)
|
||||
{
|
||||
button_confirmed = true;
|
||||
|
@ -58,7 +58,7 @@ where
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let button_event = self.buttons.event(ctx, event);
|
||||
|
||||
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
|
||||
if let Some(ButtonControllerMsg::Triggered(pos, _)) = button_event {
|
||||
match pos {
|
||||
ButtonPos::Left => {
|
||||
return Some(CancelInfoConfirmMsg::Cancelled);
|
||||
|
@ -6,8 +6,15 @@ use crate::{
|
||||
error::Error,
|
||||
maybe_trace::MaybeTrace,
|
||||
micropython::{
|
||||
buffer::StrBuffer, gc::Gc, iter::IterBuf, list::List, map::Map, module::Module, obj::Obj,
|
||||
qstr::Qstr, util,
|
||||
buffer::{get_buffer, StrBuffer},
|
||||
gc::Gc,
|
||||
iter::IterBuf,
|
||||
list::List,
|
||||
map::Map,
|
||||
module::Module,
|
||||
obj::Obj,
|
||||
qstr::Qstr,
|
||||
util,
|
||||
},
|
||||
strutil::StringType,
|
||||
ui::{
|
||||
@ -38,9 +45,10 @@ use crate::{
|
||||
use super::{
|
||||
component::{
|
||||
AddressDetails, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, CancelConfirmMsg,
|
||||
CancelInfoConfirmMsg, CoinJoinProgress, Flow, FlowPages, Frame, Homescreen, Lockscreen,
|
||||
NumberInput, Page, PassphraseEntry, PinEntry, Progress, ScrollableContent, ScrollableFrame,
|
||||
ShareWords, ShowMore, SimpleChoice, WelcomeScreen, WordlistEntry, WordlistType,
|
||||
CancelInfoConfirmMsg, CoinJoinProgress, ConfirmHomescreen, Flow, FlowPages, Frame,
|
||||
Homescreen, Lockscreen, NumberInput, Page, PassphraseEntry, PinEntry, Progress,
|
||||
ScrollableContent, ScrollableFrame, ShareWords, ShowMore, SimpleChoice, WelcomeScreen,
|
||||
WordlistEntry, WordlistType,
|
||||
},
|
||||
constant, theme,
|
||||
};
|
||||
@ -242,6 +250,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, F> ComponentMsgObj for ConfirmHomescreen<T, F>
|
||||
where
|
||||
T: StringType + Clone,
|
||||
F: Fn() -> &'a [u8],
|
||||
{
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
match msg {
|
||||
CancelConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()),
|
||||
CancelConfirmMsg::Cancelled => Ok(CANCELLED.as_obj()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to create and call a `ButtonPage` dialog based on paginable content
|
||||
/// (e.g. `Paragraphs` or `FormattedText`).
|
||||
/// Has optional title (supply empty `StrBuffer` for that) and hold-to-confirm
|
||||
@ -392,6 +413,24 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let data: Obj = kwargs.get(Qstr::MP_QSTR_image)?;
|
||||
|
||||
// Layout needs to hold the Obj to play nice with GC. Obj is resolved to &[u8]
|
||||
// in every paint pass.
|
||||
// SAFETY: We expect no existing mutable reference. Resulting reference is
|
||||
// discarded before returning to micropython.
|
||||
let buffer_func = move || unsafe { unwrap!(get_buffer(data)) };
|
||||
|
||||
let obj = LayoutObj::new(ConfirmHomescreen::new(title, buffer_func))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
}
|
||||
|
||||
extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
@ -1587,6 +1626,14 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// """Confirm action."""
|
||||
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
|
||||
|
||||
/// def confirm_homescreen(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// image: bytes,
|
||||
/// ) -> object:
|
||||
/// """Confirm homescreen."""
|
||||
Qstr::MP_QSTR_confirm_homescreen => obj_fn_kw!(0, new_confirm_homescreen).as_obj(),
|
||||
|
||||
/// def confirm_blob(
|
||||
/// *,
|
||||
/// title: str,
|
||||
|
@ -43,8 +43,8 @@ pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO_DATA
|
||||
.with_line_spacing(2)
|
||||
.with_ellipsis_icon(ICON_NEXT_PAGE, -2);
|
||||
|
||||
// Chunks for this model, with accounting for some wider characters in MONO font
|
||||
pub const MONO_CHUNKS: Chunks = Chunks::new(4, 4).with_wider_chars("mMwW");
|
||||
// Chunks for this model
|
||||
pub const MONO_CHUNKS: Chunks = Chunks::new(4, 4);
|
||||
|
||||
/// Convert Python-side numeric id to a `TextStyle`.
|
||||
pub fn textstyle_number(num: i32) -> &'static TextStyle {
|
||||
|
@ -2,11 +2,11 @@ use crate::ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label, Pad},
|
||||
constant::screen,
|
||||
display::Icon,
|
||||
geometry::{Insets, Point, Rect},
|
||||
geometry::{Alignment, Insets, Point, Rect},
|
||||
model_tt::{
|
||||
bootloader::theme::{
|
||||
button_bld, button_bld_menu, BLD_BG, BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING,
|
||||
CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_TITLE, TITLE_AREA,
|
||||
CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_TITLE, TEXT_WARNING, TITLE_AREA,
|
||||
},
|
||||
component::{Button, ButtonMsg::Clicked},
|
||||
constant::WIDTH,
|
||||
@ -26,10 +26,11 @@ pub struct Intro<'a> {
|
||||
menu: Child<Button<&'static str>>,
|
||||
host: Child<Button<&'static str>>,
|
||||
text: Child<Label<&'a str>>,
|
||||
warn: Option<Child<Label<&'a str>>>,
|
||||
}
|
||||
|
||||
impl<'a> Intro<'a> {
|
||||
pub fn new(title: &'a str, content: &'a str) -> Self {
|
||||
pub fn new(title: &'a str, content: &'a str, fw_ok: bool) -> Self {
|
||||
Self {
|
||||
bg: Pad::with_background(BLD_BG).with_clear(),
|
||||
title: Child::new(Label::left_aligned(title, TEXT_TITLE).vertically_centered()),
|
||||
@ -40,6 +41,10 @@ impl<'a> Intro<'a> {
|
||||
),
|
||||
host: Child::new(Button::with_text("INSTALL FIRMWARE").styled(button_bld())),
|
||||
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
|
||||
warn: (!fw_ok).then_some(Child::new(
|
||||
Label::new("FIRMWARE CORRUPTED", Alignment::Start, TEXT_WARNING)
|
||||
.vertically_centered(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,10 +61,25 @@ impl<'a> Component for Intro<'a> {
|
||||
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START + BUTTON_HEIGHT),
|
||||
));
|
||||
self.text.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START - CONTENT_PADDING),
|
||||
));
|
||||
if self.warn.is_some() {
|
||||
self.warn.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING),
|
||||
Point::new(
|
||||
WIDTH - CONTENT_PADDING,
|
||||
TITLE_AREA.y1 + CONTENT_PADDING + 30,
|
||||
),
|
||||
));
|
||||
self.text.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING + 30),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START - CONTENT_PADDING),
|
||||
));
|
||||
} else {
|
||||
self.text.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START - CONTENT_PADDING),
|
||||
));
|
||||
}
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
@ -77,6 +97,7 @@ impl<'a> Component for Intro<'a> {
|
||||
self.bg.paint();
|
||||
self.title.paint();
|
||||
self.text.paint();
|
||||
self.warn.paint();
|
||||
self.host.paint();
|
||||
self.menu.paint();
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
use crate::ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label, Pad},
|
||||
constant::{screen, WIDTH},
|
||||
display::Icon,
|
||||
geometry::{Insets, Point, Rect},
|
||||
model_tt::{
|
||||
bootloader::theme::{
|
||||
button_bld, button_bld_menu, BLD_BG, BUTTON_HEIGHT, CONTENT_PADDING,
|
||||
CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, FIRE24, REFRESH24, TEXT_TITLE,
|
||||
TITLE_AREA, X32,
|
||||
use crate::{
|
||||
trezorhal::secbool::{secbool, sectrue},
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label, Pad},
|
||||
constant::{screen, WIDTH},
|
||||
display::Icon,
|
||||
geometry::{Insets, Point, Rect},
|
||||
model_tt::{
|
||||
bootloader::theme::{
|
||||
button_bld, button_bld_menu, BLD_BG, BUTTON_HEIGHT, CONTENT_PADDING,
|
||||
CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, FIRE24, REFRESH24, TEXT_TITLE,
|
||||
TITLE_AREA, X32,
|
||||
},
|
||||
component::{Button, ButtonMsg::Clicked, IconText},
|
||||
},
|
||||
component::{Button, ButtonMsg::Clicked, IconText},
|
||||
},
|
||||
};
|
||||
|
||||
@ -19,9 +22,9 @@ const BUTTON_SPACING: i16 = 8;
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, ToPrimitive)]
|
||||
pub enum MenuMsg {
|
||||
Close = 1,
|
||||
Reboot = 2,
|
||||
FactoryReset = 3,
|
||||
Close = 0xAABBCCDD,
|
||||
Reboot = 0x11223344,
|
||||
FactoryReset = 0x55667788,
|
||||
}
|
||||
|
||||
pub struct Menu {
|
||||
@ -33,7 +36,7 @@ pub struct Menu {
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(firmware_present: secbool) -> Self {
|
||||
let content_reboot = IconText::new("REBOOT TREZOR", Icon::new(REFRESH24));
|
||||
let content_reset = IconText::new("FACTORY RESET", Icon::new(FIRE24));
|
||||
|
||||
@ -45,7 +48,11 @@ impl Menu {
|
||||
.styled(button_bld_menu())
|
||||
.with_expanded_touch_area(Insets::uniform(CORNER_BUTTON_TOUCH_EXPANSION)),
|
||||
),
|
||||
reboot: Child::new(Button::with_icon_and_text(content_reboot).styled(button_bld())),
|
||||
reboot: Child::new(
|
||||
Button::with_icon_and_text(content_reboot)
|
||||
.styled(button_bld())
|
||||
.initially_enabled(sectrue == firmware_present),
|
||||
),
|
||||
reset: Child::new(Button::with_icon_and_text(content_reset).styled(button_bld())),
|
||||
};
|
||||
instance.bg.clear();
|
||||
|
@ -35,7 +35,7 @@ pub mod menu;
|
||||
pub mod theme;
|
||||
pub mod welcome;
|
||||
|
||||
use crate::ui::model_tt::theme::BLACK;
|
||||
use crate::{trezorhal::secbool::secbool, ui::model_tt::theme::BLACK};
|
||||
use confirm::Confirm;
|
||||
use intro::Intro;
|
||||
use menu::Menu;
|
||||
@ -220,8 +220,8 @@ extern "C" fn screen_wipe_confirm() -> u32 {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_menu() -> u32 {
|
||||
run(&mut Menu::new())
|
||||
extern "C" fn screen_menu(firmware_present: secbool) -> u32 {
|
||||
run(&mut Menu::new(firmware_present))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -230,6 +230,7 @@ extern "C" fn screen_intro(
|
||||
vendor_str: *const cty::c_char,
|
||||
vendor_str_len: u8,
|
||||
version: *const cty::c_char,
|
||||
fw_ok: bool,
|
||||
) -> u32 {
|
||||
let vendor = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) });
|
||||
let version = unwrap!(unsafe { from_c_str(version) });
|
||||
@ -245,7 +246,7 @@ extern "C" fn screen_intro(
|
||||
unwrap!(version_str.push_str("\nby "));
|
||||
unwrap!(version_str.push_str(vendor));
|
||||
|
||||
let mut frame = Intro::new(title_str.as_str(), version_str.as_str());
|
||||
let mut frame = Intro::new(title_str.as_str(), version_str.as_str(), fw_ok);
|
||||
|
||||
run(&mut frame)
|
||||
}
|
||||
@ -315,7 +316,7 @@ extern "C" fn screen_wipe_success() {
|
||||
&RESULT_WIPE,
|
||||
Icon::new(CHECK40),
|
||||
"Trezor reset\nsuccessfully",
|
||||
RECONNECT_MESSAGE,
|
||||
Label::centered(RECONNECT_MESSAGE, RESULT_WIPE.title_style()).vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
@ -327,7 +328,7 @@ extern "C" fn screen_wipe_fail() {
|
||||
&RESULT_WIPE,
|
||||
Icon::new(WARNING40),
|
||||
"Trezor reset was\nnot successful",
|
||||
RECONNECT_MESSAGE,
|
||||
Label::centered(RECONNECT_MESSAGE, RESULT_WIPE.title_style()).vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
@ -357,29 +358,29 @@ extern "C" fn screen_install_fail() {
|
||||
&RESULT_FW_INSTALL,
|
||||
Icon::new(WARNING40),
|
||||
"Firmware installation was not successful",
|
||||
RECONNECT_MESSAGE,
|
||||
Label::centered(RECONNECT_MESSAGE, RESULT_FW_INSTALL.title_style()).vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_install_success_bld(msg: &'static str, complete_draw: bool) {
|
||||
fn screen_install_success_bld(msg: &str, complete_draw: bool) {
|
||||
let mut frame = ResultScreen::new(
|
||||
&RESULT_FW_INSTALL,
|
||||
Icon::new(CHECK40),
|
||||
"Firmware installed\nsuccessfully",
|
||||
msg,
|
||||
Label::centered(msg, RESULT_FW_INSTALL.title_style()).vertically_centered(),
|
||||
complete_draw,
|
||||
);
|
||||
show(&mut frame, complete_draw);
|
||||
}
|
||||
|
||||
fn screen_install_success_initial(msg: &'static str, complete_draw: bool) {
|
||||
fn screen_install_success_initial(msg: &str, complete_draw: bool) {
|
||||
let mut frame = ResultScreen::new(
|
||||
&RESULT_INITIAL,
|
||||
Icon::new(CHECK40),
|
||||
"Firmware installed\nsuccessfully",
|
||||
msg,
|
||||
Label::centered(msg, RESULT_INITIAL.title_style()).vertically_centered(),
|
||||
complete_draw,
|
||||
);
|
||||
show(&mut frame, complete_draw);
|
||||
@ -387,15 +388,25 @@ fn screen_install_success_initial(msg: &'static str, complete_draw: bool) {
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_install_success(
|
||||
reboot_msg: *const cty::c_char,
|
||||
restart_seconds: u8,
|
||||
initial_setup: bool,
|
||||
complete_draw: bool,
|
||||
) {
|
||||
let msg = unwrap!(unsafe { from_c_str(reboot_msg) });
|
||||
if initial_setup {
|
||||
screen_install_success_initial(msg, complete_draw)
|
||||
let mut reboot_msg = BootloaderString::new();
|
||||
|
||||
if restart_seconds >= 1 {
|
||||
unwrap!(reboot_msg.push_str("RESTARTING IN "));
|
||||
// in practice, restart_seconds is 5 or less so this is fine
|
||||
let seconds_char = b'0' + restart_seconds % 10;
|
||||
unwrap!(reboot_msg.push(seconds_char as char));
|
||||
} else {
|
||||
screen_install_success_bld(msg, complete_draw)
|
||||
unwrap!(reboot_msg.push_str(RECONNECT_MESSAGE));
|
||||
}
|
||||
|
||||
if initial_setup {
|
||||
screen_install_success_initial(reboot_msg.as_str(), complete_draw)
|
||||
} else {
|
||||
screen_install_success_bld(reboot_msg.as_str(), complete_draw)
|
||||
}
|
||||
display::refresh();
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ pub const BLD_FG: Color = WHITE;
|
||||
pub const BLD_WIPE_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
|
||||
pub const BLD_WIPE_TEXT_COLOR: Color = WHITE;
|
||||
|
||||
pub const BLD_WARN_COLOR: Color = Color::rgb(0xFF, 0x00, 0x00);
|
||||
|
||||
pub const BLD_WIPE_BTN_COLOR: Color = WHITE;
|
||||
pub const BLD_WIPE_BTN_COLOR_ACTIVE: Color = Color::rgb(0xFA, 0xCF, 0xCF);
|
||||
|
||||
@ -238,6 +240,13 @@ pub const TEXT_TITLE: TextStyle = TextStyle::new(
|
||||
);
|
||||
|
||||
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG);
|
||||
pub const TEXT_WARNING: TextStyle = TextStyle::new(
|
||||
Font::BOLD,
|
||||
BLD_WARN_COLOR,
|
||||
BLD_BG,
|
||||
BLD_WARN_COLOR,
|
||||
BLD_WARN_COLOR,
|
||||
);
|
||||
pub const TEXT_FINGERPRINT: TextStyle =
|
||||
TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG)
|
||||
.with_line_breaking(BreakWordsNoHyphen);
|
||||
|
@ -30,7 +30,10 @@ impl<T: AsRef<str>> ErrorScreen<'_, T> {
|
||||
pub fn new(title: T, message: T, footer: T) -> Self {
|
||||
let title = Label::centered(title, STYLE.title_style());
|
||||
let message = Label::centered(message, STYLE.message_style());
|
||||
let footer = ResultFooter::new(footer, STYLE);
|
||||
let footer = ResultFooter::new(
|
||||
Label::centered(footer, STYLE.title_style()).vertically_centered(),
|
||||
STYLE,
|
||||
);
|
||||
|
||||
Self {
|
||||
bg: Pad::with_background(FATAL_ERROR_COLOR).with_clear(),
|
||||
|
@ -48,10 +48,10 @@ pub struct ResultFooter<'a, T> {
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<str>> ResultFooter<'a, T> {
|
||||
pub fn new(text: T, style: &'a ResultStyle) -> Self {
|
||||
pub fn new(text: Label<T>, style: &'a ResultStyle) -> Self {
|
||||
Self {
|
||||
style,
|
||||
text: Label::centered(text, style.title_style()).vertically_centered(),
|
||||
text,
|
||||
area: Rect::zero(),
|
||||
}
|
||||
}
|
||||
@ -101,7 +101,7 @@ pub struct ResultScreen<'a, T> {
|
||||
style: &'a ResultStyle,
|
||||
icon: Icon,
|
||||
message: Child<Label<T>>,
|
||||
footer: Child<ResultFooter<'a, T>>,
|
||||
footer: Child<ResultFooter<'a, &'a str>>,
|
||||
}
|
||||
|
||||
impl<'a, T: StringType> ResultScreen<'a, T> {
|
||||
@ -109,7 +109,7 @@ impl<'a, T: StringType> ResultScreen<'a, T> {
|
||||
style: &'a ResultStyle,
|
||||
icon: Icon,
|
||||
message: T,
|
||||
footer: T,
|
||||
footer: Label<&'a str>,
|
||||
complete_draw: bool,
|
||||
) -> Self {
|
||||
let mut instance = Self {
|
||||
@ -130,13 +130,13 @@ impl<'a, T: StringType> ResultScreen<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StringType> Component for ResultScreen<'_, T> {
|
||||
impl<'a, T: StringType> Component for ResultScreen<'a, T> {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, _bounds: Rect) -> Rect {
|
||||
self.bg.place(screen());
|
||||
|
||||
let (main_area, footer_area) = ResultFooter::<T>::split_bounds();
|
||||
let (main_area, footer_area) = ResultFooter::<&'a str>::split_bounds();
|
||||
|
||||
self.footer_pad.place(footer_area);
|
||||
self.footer.place(footer_area);
|
||||
|
@ -76,6 +76,7 @@ void show_pin_too_many_screen(void);
|
||||
|
||||
void hal_delay(uint32_t ms);
|
||||
uint32_t hal_ticks_ms();
|
||||
void hal_delay_us(uint16_t delay_us);
|
||||
|
||||
void collect_hw_entropy(void);
|
||||
#define HW_ENTROPY_LEN (12 + 32)
|
||||
|
@ -175,8 +175,10 @@ static optiga_result optiga_i2c_write(const uint8_t *data, uint16_t data_size) {
|
||||
}
|
||||
if (HAL_OK == i2c_transmit(OPTIGA_I2C_INSTANCE, OPTIGA_ADDRESS,
|
||||
(uint8_t *)data, data_size, I2C_TIMEOUT)) {
|
||||
hal_delay_us(1000);
|
||||
return OPTIGA_SUCCESS;
|
||||
}
|
||||
hal_delay_us(1000);
|
||||
}
|
||||
return OPTIGA_ERR_I2C_WRITE;
|
||||
}
|
||||
|
@ -46,6 +46,8 @@
|
||||
#define COLOR_FATAL_ERROR COLOR_BLACK
|
||||
#endif
|
||||
|
||||
uint32_t systick_val_copy = 0;
|
||||
|
||||
// from util.s
|
||||
extern void shutdown_privileged(void);
|
||||
|
||||
@ -154,6 +156,17 @@ void __assert_func(const char *file, int line, const char *func,
|
||||
|
||||
void hal_delay(uint32_t ms) { HAL_Delay(ms); }
|
||||
uint32_t hal_ticks_ms() { return HAL_GetTick(); }
|
||||
void hal_delay_us(uint16_t delay_us) {
|
||||
uint32_t val = svc_get_systick_val();
|
||||
uint32_t t = hal_ticks_ms() * 1000 +
|
||||
(((SystemCoreClock / 1000) - val) / (SystemCoreClock / 1000000));
|
||||
uint32_t t2 = t;
|
||||
do {
|
||||
val = svc_get_systick_val();
|
||||
t2 = hal_ticks_ms() * 1000 +
|
||||
(((SystemCoreClock / 1000) - val) / (SystemCoreClock / 1000000));
|
||||
} while ((t2 - t) < delay_us);
|
||||
}
|
||||
|
||||
// reference RM0090 section 35.12.1 Figure 413
|
||||
#define USB_OTG_HS_DATA_FIFO_RAM (USB_OTG_HS_PERIPH_BASE + 0x20000U)
|
||||
|
@ -171,9 +171,19 @@ HAL_StatusTypeDef i2c_transmit(uint16_t idx, uint8_t addr, uint8_t *data,
|
||||
uint16_t len, uint32_t timeout) {
|
||||
return HAL_I2C_Master_Transmit(&i2c_handle[idx], addr, data, len, timeout);
|
||||
}
|
||||
|
||||
HAL_StatusTypeDef i2c_receive(uint16_t idx, uint8_t addr, uint8_t *data,
|
||||
uint16_t len, uint32_t timeout) {
|
||||
return HAL_I2C_Master_Receive(&i2c_handle[idx], addr, data, len, timeout);
|
||||
HAL_StatusTypeDef ret =
|
||||
HAL_I2C_Master_Receive(&i2c_handle[idx], addr, data, len, timeout);
|
||||
#ifdef USE_OPTIGA
|
||||
if (idx == OPTIGA_I2C_INSTANCE) {
|
||||
// apply GUARD_TIME as specified by the OPTIGA datasheet
|
||||
// (only applies to the I2C bus to which the OPTIGA is connected)
|
||||
hal_delay_us(50);
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
HAL_StatusTypeDef i2c_mem_write(uint16_t idx, uint8_t addr, uint16_t mem_addr,
|
||||
|
@ -11,6 +11,9 @@ void optiga_hal_init(void) {
|
||||
GPIO_InitStructure.Alternate = 0;
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_9;
|
||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
|
||||
// perform reset on every initialization
|
||||
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_9, GPIO_PIN_RESET);
|
||||
hal_delay(10);
|
||||
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_9, GPIO_PIN_SET);
|
||||
// warm reset startup time min 15ms
|
||||
hal_delay(20);
|
||||
|
@ -6,11 +6,15 @@
|
||||
#define SVC_SHUTDOWN 4
|
||||
#define SVC_REBOOT_TO_BOOTLOADER 5
|
||||
#define SVC_REBOOT_COPY_IMAGE_HEADER 6
|
||||
#define SVC_GET_SYSTICK_VAL 7
|
||||
|
||||
#include <string.h>
|
||||
#include "common.h"
|
||||
#include "image.h"
|
||||
|
||||
// from common.c
|
||||
extern uint32_t systick_val_copy;
|
||||
|
||||
// from util.s
|
||||
extern void shutdown_privileged(void);
|
||||
extern void reboot_to_bootloader(void);
|
||||
@ -87,3 +91,13 @@ static inline void svc_reboot_copy_image_header(const uint8_t *image_address) {
|
||||
reboot_to_bootloader();
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t svc_get_systick_val(void) {
|
||||
if (is_mode_unprivileged() && !is_mode_handler()) {
|
||||
__asm__ __volatile__("svc %0" ::"i"(SVC_GET_SYSTICK_VAL) : "memory");
|
||||
return systick_val_copy;
|
||||
} else {
|
||||
systick_val_copy = SysTick->VAL;
|
||||
return systick_val_copy;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include "rand.h"
|
||||
|
||||
static const uint8_t DEVICE_CERT_CHAIN[] = {
|
||||
0x30, 0x82, 0x01, 0x90, 0x30, 0x82, 0x01, 0x37, 0xa0, 0x03, 0x02, 0x01,
|
||||
0x30, 0x82, 0x01, 0x91, 0x30, 0x82, 0x01, 0x37, 0xa0, 0x03, 0x02, 0x01,
|
||||
0x02, 0x02, 0x04, 0x4e, 0xe2, 0xa5, 0x0f, 0x30, 0x0a, 0x06, 0x08, 0x2a,
|
||||
0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x4f, 0x31, 0x0b, 0x30,
|
||||
0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, 0x5a, 0x31, 0x1e,
|
||||
@ -49,18 +49,18 @@ static const uint8_t DEVICE_CERT_CHAIN[] = {
|
||||
0xa3, 0x41, 0x30, 0x3f, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
|
||||
0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x00, 0x80, 0x30, 0x0c, 0x06, 0x03,
|
||||
0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1f,
|
||||
0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x0a,
|
||||
0x80, 0xa4, 0x22, 0xc1, 0x6e, 0x36, 0x94, 0x24, 0xd3, 0xb8, 0x12, 0x67,
|
||||
0xb5, 0xaa, 0xe6, 0x46, 0x74, 0x74, 0xc8, 0x30, 0x0a, 0x06, 0x08, 0x2a,
|
||||
0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x47, 0x00, 0x30, 0x44,
|
||||
0x02, 0x20, 0x2f, 0xba, 0xc7, 0xe7, 0x99, 0x5b, 0x0e, 0x00, 0xc2, 0xc2,
|
||||
0xa7, 0xcb, 0xd8, 0xdd, 0x1d, 0x4e, 0xce, 0xf5, 0x58, 0x75, 0xa2, 0x65,
|
||||
0xc6, 0x86, 0xd4, 0xa8, 0xd2, 0x80, 0x68, 0x63, 0x5b, 0xf8, 0x02, 0x20,
|
||||
0x1e, 0xee, 0x62, 0x10, 0x0d, 0x0c, 0x97, 0x5a, 0x96, 0x34, 0x6f, 0x14,
|
||||
0xef, 0xe2, 0x6b, 0xc9, 0x3b, 0x00, 0xba, 0x30, 0x28, 0x9c, 0xe3, 0x7c,
|
||||
0xef, 0x17, 0x89, 0xbc, 0xc0, 0x68, 0xba, 0xb9, 0x30, 0x82, 0x01, 0xe0,
|
||||
0x30, 0x82, 0x01, 0x85, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x05, 0x00,
|
||||
0x94, 0x4e, 0x25, 0x7c, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce,
|
||||
0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x67,
|
||||
0xc5, 0xd4, 0xe7, 0xf0, 0x8f, 0x91, 0xb6, 0xe7, 0x48, 0xdf, 0x42, 0xbf,
|
||||
0x9f, 0x74, 0x1f, 0x43, 0xd2, 0x73, 0x75, 0x30, 0x0a, 0x06, 0x08, 0x2a,
|
||||
0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45,
|
||||
0x02, 0x21, 0x00, 0x8e, 0xd1, 0x92, 0xc5, 0xe5, 0xcd, 0xf4, 0xb3, 0x43,
|
||||
0x07, 0x20, 0x15, 0x2a, 0x4d, 0xbe, 0x18, 0x7e, 0xaf, 0xa9, 0x45, 0x1e,
|
||||
0x01, 0x29, 0x53, 0xa7, 0xec, 0x17, 0x16, 0x4e, 0xe7, 0x49, 0xd0, 0x02,
|
||||
0x20, 0x09, 0xd4, 0xdd, 0x11, 0x49, 0xd9, 0xa0, 0x28, 0xc1, 0xcc, 0x5d,
|
||||
0x6c, 0xca, 0x01, 0xd2, 0xe6, 0x19, 0x47, 0xa4, 0xad, 0xb3, 0x0d, 0xac,
|
||||
0xe4, 0x30, 0xc8, 0x36, 0x8b, 0x34, 0x46, 0x6e, 0x79, 0x30, 0x82, 0x01,
|
||||
0xde, 0x30, 0x82, 0x01, 0x84, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04,
|
||||
0x48, 0xf1, 0xf6, 0xc5, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce,
|
||||
0x3d, 0x04, 0x03, 0x02, 0x30, 0x54, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
|
||||
0x55, 0x04, 0x06, 0x13, 0x02, 0x43, 0x5a, 0x31, 0x1e, 0x30, 0x1c, 0x06,
|
||||
0x03, 0x55, 0x04, 0x0a, 0x0c, 0x15, 0x54, 0x72, 0x65, 0x7a, 0x6f, 0x72,
|
||||
@ -80,25 +80,25 @@ static const uint8_t DEVICE_CERT_CHAIN[] = {
|
||||
0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x43,
|
||||
0x41, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d,
|
||||
0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
|
||||
0x03, 0x42, 0x00, 0x04, 0x9b, 0xf0, 0x76, 0x0c, 0xd7, 0x55, 0x5e, 0xfc,
|
||||
0x5b, 0x01, 0xe6, 0x4c, 0xe9, 0x46, 0x06, 0xcd, 0xee, 0xb4, 0x8a, 0x4f,
|
||||
0x91, 0x96, 0xf4, 0x54, 0xbc, 0x2a, 0x01, 0x41, 0xb3, 0x31, 0x88, 0x2d,
|
||||
0x06, 0x8b, 0x4b, 0x6e, 0x63, 0x79, 0x13, 0xdd, 0x22, 0x06, 0x54, 0xe2,
|
||||
0x8f, 0xde, 0x3c, 0x44, 0x21, 0x41, 0xf9, 0x53, 0xb3, 0xe3, 0x6a, 0xd9,
|
||||
0xa5, 0x75, 0x19, 0x00, 0x71, 0x19, 0xd9, 0xc9, 0xa3, 0x47, 0x30, 0x45,
|
||||
0x03, 0x42, 0x00, 0x04, 0xba, 0x60, 0x84, 0xcb, 0x9f, 0xba, 0x7c, 0x86,
|
||||
0xd5, 0xd5, 0xa8, 0x61, 0x08, 0xa9, 0x1d, 0x55, 0xa2, 0x70, 0x56, 0xda,
|
||||
0x4e, 0xab, 0xbe, 0xdd, 0xe8, 0x8a, 0x95, 0xe1, 0xca, 0xe8, 0xbc, 0xe3,
|
||||
0x62, 0x08, 0x89, 0x16, 0x7a, 0xaf, 0x7f, 0x2d, 0xb1, 0x66, 0x99, 0x8f,
|
||||
0x95, 0x09, 0x84, 0xaa, 0x19, 0x5e, 0x86, 0x8f, 0x96, 0xe2, 0x28, 0x03,
|
||||
0xc3, 0xcd, 0x99, 0x1b, 0xe3, 0x1d, 0x39, 0xe7, 0xa3, 0x47, 0x30, 0x45,
|
||||
0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
|
||||
0x03, 0x02, 0x02, 0x04, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
|
||||
0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
|
||||
0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
|
||||
0x14, 0x28, 0xb2, 0x02, 0xf8, 0xf9, 0xc7, 0x8a, 0x74, 0xe8, 0xc1, 0x52,
|
||||
0xbb, 0xfb, 0x43, 0x3d, 0x99, 0xd0, 0xca, 0x03, 0xef, 0x30, 0x0a, 0x06,
|
||||
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00,
|
||||
0x30, 0x46, 0x02, 0x21, 0x00, 0x9b, 0x60, 0xfb, 0x5f, 0xc7, 0xee, 0x07,
|
||||
0x00, 0x31, 0x2d, 0x40, 0x2d, 0xcb, 0xe0, 0x77, 0xe4, 0x81, 0x82, 0xbb,
|
||||
0x94, 0x4b, 0xb1, 0xb3, 0x30, 0x79, 0xdd, 0xa0, 0xb6, 0xca, 0x9a, 0xb5,
|
||||
0xb7, 0x02, 0x21, 0x00, 0x81, 0x5d, 0xd9, 0x76, 0xab, 0xec, 0xb9, 0xb3,
|
||||
0xa8, 0x0f, 0x5b, 0x86, 0xb4, 0x49, 0x4f, 0xef, 0x94, 0xf2, 0xe0, 0xc1,
|
||||
0xf2, 0xc4, 0x5d, 0xe6, 0xec, 0x5f, 0x89, 0x5c, 0xa5, 0x6f, 0x04, 0x8b};
|
||||
0x14, 0xc8, 0xb5, 0xb2, 0x43, 0xb3, 0x30, 0x43, 0xcc, 0x08, 0xb4, 0xdc,
|
||||
0x3a, 0x72, 0xa1, 0xde, 0xcd, 0xcf, 0xd7, 0xea, 0xdb, 0x30, 0x0a, 0x06,
|
||||
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00,
|
||||
0x30, 0x45, 0x02, 0x20, 0x02, 0xc1, 0x02, 0x0a, 0xb3, 0xc6, 0x4e, 0xd9,
|
||||
0xe6, 0x58, 0xfd, 0xf8, 0x70, 0x93, 0x72, 0xc9, 0xe0, 0x53, 0x82, 0xde,
|
||||
0x4e, 0x58, 0x75, 0x80, 0xc8, 0xba, 0xc4, 0x2f, 0x43, 0x78, 0x4a, 0xd9,
|
||||
0x02, 0x21, 0x00, 0x99, 0x00, 0x98, 0x1c, 0xbc, 0x68, 0xae, 0xb0, 0x6d,
|
||||
0x3e, 0xa9, 0x11, 0x94, 0x8d, 0x63, 0x11, 0xd6, 0xf6, 0x94, 0x40, 0x3a,
|
||||
0xbb, 0xbb, 0x65, 0x9e, 0x5a, 0xf5, 0x2b, 0xf3, 0x2e, 0x33, 0xc4};
|
||||
|
||||
int optiga_sign(uint8_t index, const uint8_t *digest, size_t digest_size,
|
||||
uint8_t *signature, size_t max_sig_size, size_t *sig_size) {
|
||||
|
26
core/embed/trezorhal/unix/optiga_hal.c
Normal file
26
core/embed/trezorhal/unix/optiga_hal.c
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
void optiga_hal_init(void) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
void optiga_reset(void) {
|
||||
// nothing to do
|
||||
}
|
@ -29,6 +29,15 @@ def confirm_action(
|
||||
"""Confirm action."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tr/layout.rs
|
||||
def confirm_homescreen(
|
||||
*,
|
||||
title: str,
|
||||
image: bytes,
|
||||
) -> object:
|
||||
"""Confirm homescreen."""
|
||||
|
||||
|
||||
# rust/src/ui/model_tr/layout.rs
|
||||
def confirm_blob(
|
||||
*,
|
||||
|
@ -98,3 +98,4 @@ MODEL: str
|
||||
INTERNAL_MODEL: str
|
||||
EMULATOR: bool
|
||||
BITCOIN_ONLY: bool
|
||||
UI_LAYOUT: str
|
||||
|
@ -39,6 +39,8 @@ def generate(env):
|
||||
is_t2b1 = env["TREZOR_MODEL"] == "R"
|
||||
backlight = env["backlight"]
|
||||
optiga = env["optiga"]
|
||||
layout_tt = env["ui_layout"] == "UI_LAYOUT_TT"
|
||||
layout_tr = env["ui_layout"] == "UI_LAYOUT_TR"
|
||||
interim = f"{target[:-4]}.i" # replace .mpy with .i
|
||||
sed_scripts = " ".join(
|
||||
[
|
||||
@ -46,6 +48,8 @@ def generate(env):
|
||||
rf"-e 's/utils\.BITCOIN_ONLY/{btc_only}/g'",
|
||||
rf"-e 's/utils\.USE_BACKLIGHT/{backlight}/g'",
|
||||
rf"-e 's/utils\.USE_OPTIGA/{optiga}/g'",
|
||||
rf"-e 's/utils\.UI_LAYOUT == \"TT\"/{layout_tt}/g'",
|
||||
rf"-e 's/utils\.UI_LAYOUT == \"TR\"/{layout_tr}/g'",
|
||||
r"-e 's/if TYPE_CHECKING/if False/'",
|
||||
r"-e 's/import typing/# \0/'",
|
||||
r"-e '/from typing import (/,/^\s*)/ {s/^/# /}'",
|
||||
|
@ -173,18 +173,18 @@ trezor.ui.layouts.tr.recovery
|
||||
import trezor.ui.layouts.tr.recovery
|
||||
trezor.ui.layouts.tr.reset
|
||||
import trezor.ui.layouts.tr.reset
|
||||
trezor.ui.layouts.tt_v2
|
||||
import trezor.ui.layouts.tt_v2
|
||||
trezor.ui.layouts.tt_v2.fido
|
||||
import trezor.ui.layouts.tt_v2.fido
|
||||
trezor.ui.layouts.tt_v2.homescreen
|
||||
import trezor.ui.layouts.tt_v2.homescreen
|
||||
trezor.ui.layouts.tt_v2.progress
|
||||
import trezor.ui.layouts.tt_v2.progress
|
||||
trezor.ui.layouts.tt_v2.recovery
|
||||
import trezor.ui.layouts.tt_v2.recovery
|
||||
trezor.ui.layouts.tt_v2.reset
|
||||
import trezor.ui.layouts.tt_v2.reset
|
||||
trezor.ui.layouts.tt
|
||||
import trezor.ui.layouts.tt
|
||||
trezor.ui.layouts.tt.fido
|
||||
import trezor.ui.layouts.tt.fido
|
||||
trezor.ui.layouts.tt.homescreen
|
||||
import trezor.ui.layouts.tt.homescreen
|
||||
trezor.ui.layouts.tt.progress
|
||||
import trezor.ui.layouts.tt.progress
|
||||
trezor.ui.layouts.tt.recovery
|
||||
import trezor.ui.layouts.tt.recovery
|
||||
trezor.ui.layouts.tt.reset
|
||||
import trezor.ui.layouts.tt.reset
|
||||
trezor.ui.style
|
||||
import trezor.ui.style
|
||||
trezor.utils
|
||||
|
@ -80,6 +80,7 @@ def get_features() -> Features:
|
||||
Capability.Crypto,
|
||||
Capability.Shamir,
|
||||
Capability.ShamirGroups,
|
||||
Capability.PassphraseEntry,
|
||||
]
|
||||
else:
|
||||
f.capabilities = [
|
||||
|
@ -26,8 +26,13 @@ async def get_address(msg: BinanceGetAddress, keychain: Keychain) -> BinanceAddr
|
||||
pubkey = node.public_key()
|
||||
address = address_from_public_key(pubkey, HRP)
|
||||
if msg.show_display:
|
||||
from . import PATTERN, SLIP44_ID
|
||||
|
||||
await show_address(
|
||||
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
|
||||
address,
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name("BNB", address_n, PATTERN, SLIP44_ID),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return BinanceAddress(address=address)
|
||||
|
@ -24,7 +24,13 @@ async def get_public_key(
|
||||
pubkey = node.public_key()
|
||||
|
||||
if msg.show_display:
|
||||
from . import PATTERN, SLIP44_ID
|
||||
|
||||
path = paths.address_n_to_str(msg.address_n)
|
||||
await show_pubkey(hexlify(pubkey).decode(), path=path)
|
||||
await show_pubkey(
|
||||
hexlify(pubkey).decode(),
|
||||
account=paths.get_account_name("BNB", msg.address_n, PATTERN, SLIP44_ID),
|
||||
path=path,
|
||||
)
|
||||
|
||||
return BinancePublicKey(public_key=pubkey)
|
||||
|
@ -372,3 +372,46 @@ def address_n_to_str(address_n: Iterable[int]) -> str:
|
||||
|
||||
def unharden(item: int) -> int:
|
||||
return item ^ (item & HARDENED)
|
||||
|
||||
|
||||
def get_account_name(
|
||||
coin: str, address_n: Bip32Path, pattern: str | Sequence[str], slip44_id: int
|
||||
) -> str | None:
|
||||
account_num = _get_account_num(address_n, pattern, slip44_id)
|
||||
if account_num is None:
|
||||
return None
|
||||
return f"{coin} #{account_num}"
|
||||
|
||||
|
||||
def _get_account_num(
|
||||
address_n: Bip32Path, pattern: str | Sequence[str], slip44_id: int
|
||||
) -> int | None:
|
||||
if isinstance(pattern, str):
|
||||
pattern = [pattern]
|
||||
|
||||
# Trying all possible patterns - at least one should match
|
||||
for patt in pattern:
|
||||
try:
|
||||
return _get_account_num_single(address_n, patt, slip44_id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# This function should not raise
|
||||
return None
|
||||
|
||||
|
||||
def _get_account_num_single(address_n: Bip32Path, pattern: str, slip44_id: int) -> int:
|
||||
# Validating address_n is compatible with pattern
|
||||
if not PathSchema.parse(pattern, slip44_id).match(address_n):
|
||||
raise ValueError
|
||||
|
||||
account_pos = pattern.find("/account")
|
||||
if account_pos >= 0:
|
||||
i = pattern.count("/", 0, account_pos)
|
||||
num = address_n[i]
|
||||
if is_hardened(num):
|
||||
return unharden(num) + 1
|
||||
else:
|
||||
return num + 1
|
||||
else:
|
||||
raise ValueError
|
||||
|
@ -26,6 +26,9 @@ async def get_public_key(msg: EosGetPublicKey, keychain: Keychain) -> EosPublicK
|
||||
wif = public_key_to_wif(public_key)
|
||||
|
||||
if msg.show_display:
|
||||
from . import PATTERN, SLIP44_ID
|
||||
|
||||
path = paths.address_n_to_str(msg.address_n)
|
||||
await require_get_public_key(wif, path)
|
||||
account = paths.get_account_name("EOS", msg.address_n, PATTERN, SLIP44_ID)
|
||||
await require_get_public_key(wif, path, account)
|
||||
return EosPublicKey(wif_public_key=wif, raw_public_key=public_key)
|
||||
|
@ -1,7 +1,9 @@
|
||||
async def require_get_public_key(public_key: str, path: str) -> None:
|
||||
async def require_get_public_key(
|
||||
public_key: str, path: str, account: str | None
|
||||
) -> None:
|
||||
from trezor.ui.layouts import show_pubkey
|
||||
|
||||
await show_pubkey(public_key, path=path)
|
||||
await show_pubkey(public_key, path=path, account=account)
|
||||
|
||||
|
||||
async def require_sign_tx(num_actions: int) -> None:
|
||||
|
@ -32,8 +32,14 @@ async def get_address(
|
||||
address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network)
|
||||
|
||||
if msg.show_display:
|
||||
slip44_id = address_n[1] # it depends on the network (ETH vs ETC...)
|
||||
await show_address(
|
||||
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
|
||||
address,
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name(
|
||||
"ETH", address_n, PATTERNS_ADDRESS, slip44_id
|
||||
),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return EthereumAddress(address=address)
|
||||
|
@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
||||
import trezorui2
|
||||
from trezor import utils
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.ui.layouts import confirm_action, confirm_homescreen, confirm_single
|
||||
from trezor.ui.layouts import confirm_action
|
||||
from trezor.wire import DataError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -151,6 +151,8 @@ async def apply_settings(msg: ApplySettings) -> Success:
|
||||
|
||||
|
||||
async def _require_confirm_change_homescreen(homescreen: bytes) -> None:
|
||||
from trezor.ui.layouts import confirm_homescreen
|
||||
|
||||
if homescreen == b"":
|
||||
await confirm_action(
|
||||
"set_homescreen",
|
||||
@ -159,12 +161,12 @@ async def _require_confirm_change_homescreen(homescreen: bytes) -> None:
|
||||
br_code=BRT_PROTECT_CALL,
|
||||
)
|
||||
else:
|
||||
await confirm_homescreen(
|
||||
homescreen,
|
||||
)
|
||||
await confirm_homescreen(homescreen)
|
||||
|
||||
|
||||
async def _require_confirm_change_label(label: str) -> None:
|
||||
from trezor.ui.layouts import confirm_single
|
||||
|
||||
await confirm_single(
|
||||
"set_label",
|
||||
"Device name",
|
||||
|
@ -22,10 +22,11 @@ async def get_address(msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddres
|
||||
account = msg.account # local_cache_attribute
|
||||
minor = msg.minor # local_cache_attribute
|
||||
payment_id = msg.payment_id # local_cache_attribute
|
||||
address_n = msg.address_n # local_cache_attribute
|
||||
|
||||
await paths.validate_path(keychain, msg.address_n)
|
||||
await paths.validate_path(keychain, address_n)
|
||||
|
||||
creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
|
||||
creds = misc.get_creds(keychain, address_n, msg.network_type)
|
||||
addr = creds.address
|
||||
|
||||
have_subaddress = (
|
||||
@ -65,10 +66,13 @@ async def get_address(msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddres
|
||||
)
|
||||
|
||||
if msg.show_display:
|
||||
from . import PATTERN, SLIP44_ID
|
||||
|
||||
await show_address(
|
||||
addr,
|
||||
address_qr="monero:" + addr,
|
||||
path=paths.address_n_to_str(msg.address_n),
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name("XMR", msg.address_n, PATTERN, SLIP44_ID),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
|
@ -15,7 +15,7 @@ async def get_address(msg: NEMGetAddress, keychain: Keychain) -> NEMAddress:
|
||||
from trezor.messages import NEMAddress
|
||||
from trezor.ui.layouts import show_address
|
||||
|
||||
from apps.common.paths import address_n_to_str, validate_path
|
||||
from apps.common import paths
|
||||
|
||||
from .helpers import check_path, get_network_str
|
||||
from .validators import validate_network
|
||||
@ -24,16 +24,19 @@ async def get_address(msg: NEMGetAddress, keychain: Keychain) -> NEMAddress:
|
||||
network = msg.network # local_cache_attribute
|
||||
|
||||
validate_network(network)
|
||||
await validate_path(keychain, address_n, check_path(address_n, network))
|
||||
await paths.validate_path(keychain, address_n, check_path(address_n, network))
|
||||
|
||||
node = keychain.derive(address_n)
|
||||
address = node.nem_address(network)
|
||||
|
||||
if msg.show_display:
|
||||
from . import PATTERNS, SLIP44_ID
|
||||
|
||||
await show_address(
|
||||
address,
|
||||
case_sensitive=False,
|
||||
path=address_n_to_str(address_n),
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name("NEM", msg.address_n, PATTERNS, SLIP44_ID),
|
||||
network=get_network_str(network),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
@ -18,16 +18,21 @@ async def get_address(msg: RippleGetAddress, keychain: Keychain) -> RippleAddres
|
||||
|
||||
from .helpers import address_from_public_key
|
||||
|
||||
await paths.validate_path(keychain, msg.address_n)
|
||||
address_n = msg.address_n # local_cache_attribute
|
||||
|
||||
node = keychain.derive(msg.address_n)
|
||||
await paths.validate_path(keychain, address_n)
|
||||
|
||||
node = keychain.derive(address_n)
|
||||
pubkey = node.public_key()
|
||||
address = address_from_public_key(pubkey)
|
||||
|
||||
if msg.show_display:
|
||||
from . import PATTERN, SLIP44_ID
|
||||
|
||||
await show_address(
|
||||
address,
|
||||
path=paths.address_n_to_str(msg.address_n),
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name("XRP", msg.address_n, PATTERN, SLIP44_ID),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
|
@ -17,16 +17,23 @@ async def get_address(msg: StellarGetAddress, keychain: Keychain) -> StellarAddr
|
||||
|
||||
from . import helpers
|
||||
|
||||
await paths.validate_path(keychain, msg.address_n)
|
||||
address_n = msg.address_n # local_cache_attribute
|
||||
|
||||
node = keychain.derive(msg.address_n)
|
||||
await paths.validate_path(keychain, address_n)
|
||||
|
||||
node = keychain.derive(address_n)
|
||||
pubkey = seed.remove_ed25519_prefix(node.public_key())
|
||||
address = helpers.address_from_public_key(pubkey)
|
||||
|
||||
if msg.show_display:
|
||||
path = paths.address_n_to_str(msg.address_n)
|
||||
from . import PATTERN, SLIP44_ID
|
||||
|
||||
await show_address(
|
||||
address, case_sensitive=False, path=path, chunkify=bool(msg.chunkify)
|
||||
address,
|
||||
case_sensitive=False,
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name("XLM", msg.address_n, PATTERN, SLIP44_ID),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
return StellarAddress(address=address)
|
||||
|
@ -20,18 +20,23 @@ async def get_address(msg: TezosGetAddress, keychain: Keychain) -> TezosAddress:
|
||||
|
||||
from . import helpers
|
||||
|
||||
await paths.validate_path(keychain, msg.address_n)
|
||||
address_n = msg.address_n # local_cache_attribute
|
||||
|
||||
node = keychain.derive(msg.address_n)
|
||||
await paths.validate_path(keychain, address_n)
|
||||
|
||||
node = keychain.derive(address_n)
|
||||
|
||||
pk = seed.remove_ed25519_prefix(node.public_key())
|
||||
pkh = hashlib.blake2b(pk, outlen=helpers.PUBLIC_KEY_HASH_SIZE).digest()
|
||||
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
|
||||
|
||||
if msg.show_display:
|
||||
from . import PATTERNS, SLIP44_ID
|
||||
|
||||
await show_address(
|
||||
address,
|
||||
path=paths.address_n_to_str(msg.address_n),
|
||||
path=paths.address_n_to_str(address_n),
|
||||
account=paths.get_account_name("XTZ", address_n, PATTERNS, SLIP44_ID),
|
||||
chunkify=bool(msg.chunkify),
|
||||
)
|
||||
|
||||
|
@ -26,6 +26,10 @@ async def get_public_key(msg: TezosGetPublicKey, keychain: Keychain) -> TezosPub
|
||||
pk_prefixed = helpers.base58_encode_check(pk, helpers.TEZOS_PUBLICKEY_PREFIX)
|
||||
|
||||
if msg.show_display:
|
||||
await show_pubkey(pk_prefixed)
|
||||
from . import PATTERNS, SLIP44_ID
|
||||
|
||||
account = paths.get_account_name("XTZ", msg.address_n, PATTERNS, SLIP44_ID)
|
||||
path = paths.address_n_to_str(msg.address_n)
|
||||
await show_pubkey(pk_prefixed, account=account, path=path)
|
||||
|
||||
return TezosPublicKey(public_key=pk_prefixed)
|
||||
|
@ -4,9 +4,9 @@ from .common import * # noqa: F401,F403
|
||||
|
||||
# NOTE: using any import magic probably causes mypy not to check equivalence of
|
||||
# layout type signatures across models
|
||||
if utils.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
||||
if utils.UI_LAYOUT == "TR":
|
||||
from .tr import * # noqa: F401,F403
|
||||
elif utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
from .tt_v2 import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TT":
|
||||
from .tt import * # noqa: F401,F403
|
||||
else:
|
||||
raise ValueError("Unknown Trezor model")
|
||||
raise ValueError("Unknown layout")
|
||||
|
@ -1,6 +1,6 @@
|
||||
from trezor import utils
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
from .tt_v2.fido import * # noqa: F401,F403
|
||||
elif utils.INTERNAL_MODEL in ("T2B1",):
|
||||
if utils.UI_LAYOUT == "TT":
|
||||
from .tt.fido import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.fido import * # noqa: F401,F403
|
||||
|
@ -1,6 +1,6 @@
|
||||
from trezor import utils
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
from .tt_v2.homescreen import * # noqa: F401,F403
|
||||
elif utils.INTERNAL_MODEL in ("T2B1",):
|
||||
if utils.UI_LAYOUT == "TT":
|
||||
from .tt.homescreen import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.homescreen import * # noqa: F401,F403
|
||||
|
@ -1,6 +1,6 @@
|
||||
from trezor import utils
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
from .tt_v2.progress import * # noqa: F401,F403
|
||||
elif utils.INTERNAL_MODEL in ("T2B1",):
|
||||
if utils.UI_LAYOUT == "TT":
|
||||
from .tt.progress import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.progress import * # noqa: F401,F403
|
||||
|
@ -1,6 +1,6 @@
|
||||
from trezor import utils
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
from .tt_v2.recovery import * # noqa: F401,F403
|
||||
elif utils.INTERNAL_MODEL in ("T2B1",):
|
||||
if utils.UI_LAYOUT == "TT":
|
||||
from .tt.recovery import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.recovery import * # noqa: F401,F403
|
||||
|
@ -1,6 +1,6 @@
|
||||
from trezor import utils
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
from .tt_v2.reset import * # noqa: F401,F403
|
||||
elif utils.INTERNAL_MODEL in ("T2B1",):
|
||||
if utils.UI_LAYOUT == "TT":
|
||||
from .tt.reset import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.reset import * # noqa: F401,F403
|
||||
|
@ -443,12 +443,17 @@ async def confirm_path_warning(
|
||||
async def confirm_homescreen(
|
||||
image: bytes,
|
||||
) -> None:
|
||||
# TODO: show homescreen preview?
|
||||
await confirm_action(
|
||||
"set_homescreen",
|
||||
"Set homescreen",
|
||||
description="Do you really want to set new homescreen image?",
|
||||
br_code=ButtonRequestType.ProtectCall,
|
||||
await raise_if_not_confirmed(
|
||||
interact(
|
||||
RustLayout(
|
||||
trezorui2.confirm_homescreen(
|
||||
title="CHANGE HOMESCREEN?",
|
||||
image=image,
|
||||
)
|
||||
),
|
||||
"set_homesreen",
|
||||
ButtonRequestType.ProtectCall,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@ from trezorutils import ( # noqa: F401
|
||||
INTERNAL_MODEL,
|
||||
MODEL,
|
||||
SCM_REVISION,
|
||||
UI_LAYOUT,
|
||||
USE_BACKLIGHT,
|
||||
USE_OPTIGA,
|
||||
USE_SD_CARD,
|
||||
|
@ -649,6 +649,15 @@ class DebugLink:
|
||||
physical_button=messages.DebugPhysicalButton.MIDDLE_BTN, wait=wait
|
||||
)
|
||||
|
||||
def press_middle_htc(
|
||||
self, hold_ms: int, extra_ms: int = 200
|
||||
) -> Optional[LayoutContent]:
|
||||
return self.press_htc(
|
||||
button=messages.DebugPhysicalButton.MIDDLE_BTN,
|
||||
hold_ms=hold_ms,
|
||||
extra_ms=extra_ms,
|
||||
)
|
||||
|
||||
@overload
|
||||
def press_right(self) -> None:
|
||||
...
|
||||
@ -664,10 +673,19 @@ class DebugLink:
|
||||
|
||||
def press_right_htc(
|
||||
self, hold_ms: int, extra_ms: int = 200
|
||||
) -> Optional[LayoutContent]:
|
||||
return self.press_htc(
|
||||
button=messages.DebugPhysicalButton.RIGHT_BTN,
|
||||
hold_ms=hold_ms,
|
||||
extra_ms=extra_ms,
|
||||
)
|
||||
|
||||
def press_htc(
|
||||
self, button: messages.DebugPhysicalButton, hold_ms: int, extra_ms: int = 200
|
||||
) -> Optional[LayoutContent]:
|
||||
hold_ms = hold_ms + extra_ms # safety margin
|
||||
result = self.input(
|
||||
physical_button=messages.DebugPhysicalButton.RIGHT_BTN,
|
||||
physical_button=button,
|
||||
hold_ms=hold_ms,
|
||||
)
|
||||
# sleeping little longer for UI to update
|
||||
|
@ -70,6 +70,7 @@ def navigate_to_action_and_press(
|
||||
wanted_action: str,
|
||||
all_actions: list[str],
|
||||
is_carousel: bool = True,
|
||||
hold_ms: int = 0,
|
||||
) -> None:
|
||||
"""Navigate to the button with certain action and press it"""
|
||||
# Orient
|
||||
@ -99,8 +100,11 @@ def navigate_to_action_and_press(
|
||||
is_carousel=is_carousel,
|
||||
)
|
||||
|
||||
# Press
|
||||
debug.press_middle(wait=True)
|
||||
# Press or hold
|
||||
if hold_ms:
|
||||
debug.press_middle_htc(1000)
|
||||
else:
|
||||
debug.press_middle(wait=True)
|
||||
|
||||
|
||||
def _get_action_index(wanted_action: str, all_actions: list[str]) -> int:
|
||||
|
@ -169,6 +169,18 @@ def _delete_pin(debug: "DebugLink", digits_to_delete: int, check: bool = True) -
|
||||
assert before[:-digits_to_delete] == after
|
||||
|
||||
|
||||
def _delete_all(debug: "DebugLink", check: bool = True) -> None:
|
||||
"""Navigate to "DELETE" and hold it until all digits are deleted"""
|
||||
if debug.model == "T":
|
||||
debug.click_hold(buttons.pin_passphrase_grid(9), hold_ms=1500)
|
||||
elif debug.model == "R":
|
||||
navigate_to_action_and_press(debug, "DELETE", TR_PIN_ACTIONS, hold_ms=1000)
|
||||
|
||||
if check:
|
||||
after = debug.read_layout().pin()
|
||||
assert after == ""
|
||||
|
||||
|
||||
def _cancel_pin(debug: "DebugLink") -> None:
|
||||
"""Navigate to "CANCEL" and press it"""
|
||||
# It is the same button as DELETE
|
||||
@ -224,6 +236,17 @@ def test_pin_long_delete(device_handler: "BackgroundDeviceHandler"):
|
||||
_input_see_confirm(debug, PIN24[-10:])
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_pin_delete_hold(device_handler: "BackgroundDeviceHandler"):
|
||||
with prepare(device_handler) as debug:
|
||||
_input_pin(debug, PIN4)
|
||||
_see_pin(debug)
|
||||
|
||||
_delete_all(debug)
|
||||
|
||||
_input_see_confirm(debug, PIN4)
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN60[:50])
|
||||
def test_pin_longer_than_max(device_handler: "BackgroundDeviceHandler"):
|
||||
with prepare(device_handler) as debug:
|
||||
|
@ -20,26 +20,41 @@ from trezorlib.binance import get_address
|
||||
from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ...input_flows import InputFlowShowAddressQRCode
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.altcoin,
|
||||
pytest.mark.binance,
|
||||
pytest.mark.skip_t1, # T1 support is not planned
|
||||
pytest.mark.setup_client(
|
||||
mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin"
|
||||
),
|
||||
]
|
||||
|
||||
BINANCE_ADDRESS_TEST_VECTORS = [
|
||||
("m/44h/714h/0h/0/0", "bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu"),
|
||||
("m/44h/714h/0h/0/1", "bnb1egswqkszzfc2uq78zjslc6u2uky4pw46x4rstd"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.binance
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.setup_client(
|
||||
mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin"
|
||||
)
|
||||
@pytest.mark.parametrize("chunkify", (True, False))
|
||||
@pytest.mark.parametrize("path, expected_address", BINANCE_ADDRESS_TEST_VECTORS)
|
||||
def test_binance_get_address(
|
||||
client: Client, chunkify: bool, path: str, expected_address: str
|
||||
def test_binance_get_address(client: Client, path: str, expected_address: str):
|
||||
# data from https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/crypto.test.js#L50
|
||||
|
||||
address = get_address(client, parse_path(path), show_display=True)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, expected_address", BINANCE_ADDRESS_TEST_VECTORS)
|
||||
def test_binance_get_address_chunkify_details(
|
||||
client: Client, path: str, expected_address: str
|
||||
):
|
||||
# data from https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/crypto.test.js#L50
|
||||
|
||||
address = get_address(
|
||||
client, parse_path(path), show_display=True, chunkify=chunkify
|
||||
)
|
||||
assert address == expected_address
|
||||
with client:
|
||||
IF = InputFlowShowAddressQRCode(client)
|
||||
client.set_input_flow(IF.get())
|
||||
address = get_address(
|
||||
client, parse_path(path), show_display=True, chunkify=True
|
||||
)
|
||||
assert address == expected_address
|
||||
|
@ -21,15 +21,27 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ...common import parametrize_using_common_fixtures
|
||||
from ...input_flows import InputFlowShowAddressQRCode
|
||||
|
||||
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
|
||||
|
||||
|
||||
@parametrize_using_common_fixtures("ethereum/getaddress.json")
|
||||
@pytest.mark.parametrize("chunkify", (True, False))
|
||||
def test_getaddress(client: Client, chunkify: bool, parameters, result):
|
||||
def test_getaddress(client: Client, parameters, result):
|
||||
address_n = parse_path(parameters["path"])
|
||||
assert (
|
||||
ethereum.get_address(client, address_n, show_display=True, chunkify=chunkify)
|
||||
== result["address"]
|
||||
ethereum.get_address(client, address_n, show_display=True) == result["address"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip_t1("No input flow for T1")
|
||||
@parametrize_using_common_fixtures("ethereum/getaddress.json")
|
||||
def test_getaddress_chunkify_details(client: Client, parameters, result):
|
||||
with client:
|
||||
IF = InputFlowShowAddressQRCode(client)
|
||||
client.set_input_flow(IF.get())
|
||||
address_n = parse_path(parameters["path"])
|
||||
assert (
|
||||
ethereum.get_address(client, address_n, show_display=True, chunkify=True)
|
||||
== result["address"]
|
||||
)
|
||||
|
@ -21,29 +21,45 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ...common import MNEMONIC12
|
||||
from ...input_flows import InputFlowShowAddressQRCode
|
||||
|
||||
TEST_VECTORS = [
|
||||
(
|
||||
"m/44h/128h/0h",
|
||||
b"4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM",
|
||||
),
|
||||
(
|
||||
"m/44h/128h/1h",
|
||||
b"44iAazhoAkv5a5RqLNVyh82a1n3ceNggmN4Ho7bUBJ14WkEVR8uFTe9f7v5rNnJ2kEbVXxfXiRzsD5Jtc6NvBi4D6WNHPie",
|
||||
),
|
||||
(
|
||||
"m/44h/128h/2h",
|
||||
b"47ejhmbZ4wHUhXaqA4b7PN667oPMkokf4ZkNdWrMSPy9TNaLVr7vLqVUQHh2MnmaAEiyrvLsX8xUf99q3j1iAeMV8YvSFcH",
|
||||
),
|
||||
]
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.altcoin,
|
||||
pytest.mark.monero,
|
||||
pytest.mark.skip_t1,
|
||||
pytest.mark.setup_client(mnemonic=MNEMONIC12),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.monero
|
||||
@pytest.mark.skip_t1
|
||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||
@pytest.mark.parametrize("chunkify", (True, False))
|
||||
def test_monero_getaddress(client: Client, chunkify: bool):
|
||||
assert (
|
||||
monero.get_address(
|
||||
client, parse_path("m/44h/128h/0h"), show_display=True, chunkify=chunkify
|
||||
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
|
||||
def test_monero_getaddress(client: Client, path: str, expected_address: bytes):
|
||||
address = monero.get_address(client, parse_path(path), show_display=True)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
|
||||
def test_monero_getaddress_chunkify_details(
|
||||
client: Client, path: str, expected_address: bytes
|
||||
):
|
||||
with client:
|
||||
IF = InputFlowShowAddressQRCode(client)
|
||||
client.set_input_flow(IF.get())
|
||||
address = monero.get_address(
|
||||
client, parse_path(path), show_display=True, chunkify=True
|
||||
)
|
||||
== b"4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM"
|
||||
)
|
||||
assert (
|
||||
monero.get_address(
|
||||
client, parse_path("m/44h/128h/1h"), show_display=True, chunkify=chunkify
|
||||
)
|
||||
== b"44iAazhoAkv5a5RqLNVyh82a1n3ceNggmN4Ho7bUBJ14WkEVR8uFTe9f7v5rNnJ2kEbVXxfXiRzsD5Jtc6NvBi4D6WNHPie"
|
||||
)
|
||||
assert (
|
||||
monero.get_address(
|
||||
client, parse_path("m/44h/128h/2h"), show_display=True, chunkify=chunkify
|
||||
)
|
||||
== b"47ejhmbZ4wHUhXaqA4b7PN667oPMkokf4ZkNdWrMSPy9TNaLVr7vLqVUQHh2MnmaAEiyrvLsX8xUf99q3j1iAeMV8YvSFcH"
|
||||
)
|
||||
assert address == expected_address
|
||||
|
0
tests/device_tests/ripple/__init__.py
Normal file
0
tests/device_tests/ripple/__init__.py
Normal file
@ -20,6 +20,8 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
from trezorlib.ripple import get_address
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ...input_flows import InputFlowShowAddressQRCode
|
||||
|
||||
CUSTOM_MNEMONIC = (
|
||||
"armed bundle pudding lazy strategy impulse where identify "
|
||||
"submit weekend physical antenna flight social acoustic absurd "
|
||||
@ -32,15 +34,31 @@ pytestmark = [
|
||||
pytest.mark.skip_t1, # T1 support is not planned
|
||||
]
|
||||
|
||||
# data from https://iancoleman.io/bip39/
|
||||
TEST_VECTORS = [
|
||||
("m/44h/144h/0h/0/0", "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H"),
|
||||
("m/44h/144h/0h/0/1", "rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws"),
|
||||
("m/44h/144h/1h/0/0", "rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX"),
|
||||
]
|
||||
|
||||
def test_ripple_get_address(client: Client):
|
||||
# data from https://iancoleman.io/bip39/
|
||||
address = get_address(client, parse_path("m/44h/144h/0h/0/0"))
|
||||
assert address == "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H"
|
||||
address = get_address(client, parse_path("m/44h/144h/0h/0/1"))
|
||||
assert address == "rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws"
|
||||
address = get_address(client, parse_path("m/44h/144h/1h/0/0"))
|
||||
assert address == "rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX"
|
||||
|
||||
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
|
||||
def test_ripple_get_address(client: Client, path: str, expected_address: str):
|
||||
address = get_address(client, parse_path(path), show_display=True)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
|
||||
def test_ripple_get_address_chunkify_details(
|
||||
client: Client, path: str, expected_address: str
|
||||
):
|
||||
with client:
|
||||
IF = InputFlowShowAddressQRCode(client)
|
||||
client.set_input_flow(IF.get())
|
||||
address = get_address(
|
||||
client, parse_path(path), show_display=True, chunkify=True
|
||||
)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.setup_client(mnemonic=CUSTOM_MNEMONIC)
|
||||
|
@ -59,6 +59,12 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ...common import parametrize_using_common_fixtures
|
||||
from ...input_flows import InputFlowShowAddressQRCode
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.altcoin,
|
||||
pytest.mark.stellar,
|
||||
]
|
||||
|
||||
|
||||
def parameters_to_proto(parameters):
|
||||
@ -80,8 +86,6 @@ def parameters_to_proto(parameters):
|
||||
return tx, operations
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.stellar
|
||||
@parametrize_using_common_fixtures("stellar/sign_tx.json")
|
||||
def test_sign_tx(client: Client, parameters, result):
|
||||
tx, operations = parameters_to_proto(parameters)
|
||||
@ -92,8 +96,6 @@ def test_sign_tx(client: Client, parameters, result):
|
||||
assert b64encode(response.signature).decode() == result["signature"]
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.stellar
|
||||
@parametrize_using_common_fixtures("stellar/sign_tx.json")
|
||||
@pytest.mark.skipif(not stellar.HAVE_STELLAR_SDK, reason="requires Stellar SDK")
|
||||
def test_xdr(parameters, result):
|
||||
@ -110,13 +112,21 @@ def test_xdr(parameters, result):
|
||||
assert expected == actual
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.stellar
|
||||
@pytest.mark.parametrize("chunkify", (True, False))
|
||||
@parametrize_using_common_fixtures("stellar/get_address.json")
|
||||
def test_get_address(client: Client, chunkify: bool, parameters, result):
|
||||
def test_get_address(client: Client, parameters, result):
|
||||
address_n = parse_path(parameters["path"])
|
||||
address = stellar.get_address(
|
||||
client, address_n, show_display=True, chunkify=chunkify
|
||||
)
|
||||
address = stellar.get_address(client, address_n, show_display=True)
|
||||
assert address == result["address"]
|
||||
|
||||
|
||||
@pytest.mark.skip_t1("No input flow for T1")
|
||||
@parametrize_using_common_fixtures("stellar/get_address.json")
|
||||
def test_get_address_chunkify_details(client: Client, parameters, result):
|
||||
with client:
|
||||
IF = InputFlowShowAddressQRCode(client)
|
||||
client.set_input_flow(IF.get())
|
||||
address_n = parse_path(parameters["path"])
|
||||
address = stellar.get_address(
|
||||
client, address_n, show_display=True, chunkify=True
|
||||
)
|
||||
assert address == result["address"]
|
||||
|
@ -12,7 +12,7 @@ from ..common import compact_size
|
||||
pytestmark = [pytest.mark.skip_t1, pytest.mark.skip_t2]
|
||||
|
||||
ROOT_PUBLIC_KEY = bytes.fromhex(
|
||||
"04626d58aca84f0fcb52ea63f0eb08de1067b8d406574a715d5e7928f4b67f113a00fb5c5918e74d2327311946c446b242c20fe7347482999bdc1e229b94e27d96"
|
||||
"047f77368dea2d4d61e989f474a56723c3212dacf8a808d8795595ef38441427c4389bc454f02089d7f08b873005e4c28d432468997871c0bf286fd3861e21e96a"
|
||||
)
|
||||
|
||||
|
||||
|
@ -69,11 +69,11 @@ def test_busy_expiry(client: Client):
|
||||
_assert_busy(client, True)
|
||||
|
||||
# Hasn't expired yet.
|
||||
time.sleep(1.4)
|
||||
time.sleep(0.1)
|
||||
_assert_busy(client, True)
|
||||
|
||||
# Wait for it to expire. Add 400ms tolerance to account for CI slowness.
|
||||
time.sleep(0.5)
|
||||
# Wait for it to expire. Add some tolerance to account for CI/hardware slowness.
|
||||
time.sleep(4.0)
|
||||
|
||||
# Check that the device is no longer busy.
|
||||
# Also needs to come back to Homescreen (for UI tests).
|
||||
|
@ -20,16 +20,34 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
from trezorlib.tezos import get_address
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ...input_flows import InputFlowShowAddressQRCode
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.tezos
|
||||
@pytest.mark.skip_t1
|
||||
@pytest.mark.parametrize("chunkify", (True, False))
|
||||
def test_tezos_get_address(client: Client, chunkify: bool):
|
||||
path = parse_path("m/44h/1729h/0h")
|
||||
address = get_address(client, path, show_display=True, chunkify=chunkify)
|
||||
assert address == "tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9"
|
||||
pytestmark = [
|
||||
pytest.mark.altcoin,
|
||||
pytest.mark.tezos,
|
||||
pytest.mark.skip_t1,
|
||||
]
|
||||
|
||||
path = parse_path("m/44h/1729h/1h")
|
||||
address = get_address(client, path, show_display=True, chunkify=chunkify)
|
||||
assert address == "tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1"
|
||||
TEST_VECTORS = [
|
||||
("m/44h/1729h/0h", "tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9"),
|
||||
("m/44h/1729h/1h", "tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
|
||||
def test_tezos_get_address(client: Client, path: str, expected_address: str):
|
||||
address = get_address(client, parse_path(path), show_display=True)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, expected_address", TEST_VECTORS)
|
||||
def test_tezos_get_address_chunkify_details(
|
||||
client: Client, path: str, expected_address: str
|
||||
):
|
||||
with client:
|
||||
IF = InputFlowShowAddressQRCode(client)
|
||||
client.set_input_flow(IF.get())
|
||||
address = get_address(
|
||||
client, parse_path(path), show_display=True, chunkify=True
|
||||
)
|
||||
assert address == expected_address
|
||||
|
@ -273,7 +273,15 @@ class InputFlowShowAddressQRCode(InputFlowBase):
|
||||
self.debug.press_yes()
|
||||
|
||||
def input_flow_tr(self) -> BRGeneratorType:
|
||||
yield
|
||||
# Find out the page-length of the address
|
||||
br = yield
|
||||
if br.pages is not None:
|
||||
address_swipes = br.pages - 1
|
||||
else:
|
||||
address_swipes = 0
|
||||
for _ in range(address_swipes):
|
||||
self.debug.press_right()
|
||||
|
||||
# Go into details
|
||||
self.debug.press_right()
|
||||
# Go through details and back
|
||||
@ -281,6 +289,8 @@ class InputFlowShowAddressQRCode(InputFlowBase):
|
||||
self.debug.press_left()
|
||||
self.debug.press_left()
|
||||
# Confirm
|
||||
for _ in range(address_swipes):
|
||||
self.debug.press_right()
|
||||
self.debug.press_middle()
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user