From 1607db20d2fa1de6e459dfaf6ee6958c1f0daf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ioan=20Biz=C4=83u?= Date: Wed, 2 Apr 2025 16:49:26 +0200 Subject: [PATCH] feat(core/ui): layouts for device pairing --- core/embed/rust/librust_qstr.h | 4 ++ .../rust/src/ui/api/firmware_micropython.rs | 38 +++++++++++++++++++ .../rust/src/ui/layout_bolt/ui_firmware.rs | 10 +++++ .../rust/src/ui/layout_caesar/ui_firmware.rs | 10 +++++ .../rust/src/ui/layout_delizia/ui_firmware.rs | 10 +++++ .../rust/src/ui/layout_eckhart/ui_firmware.rs | 34 ++++++++++++++++- core/embed/rust/src/ui/ui_firmware.rs | 6 +++ core/mocks/generated/trezorui_api.pyi | 16 ++++++++ core/src/apps/homescreen/__init__.py | 15 ++++++++ 9 files changed, 142 insertions(+), 1 deletion(-) diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index c411ff0c49..d0488e79d6 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -182,6 +182,7 @@ static void _librust_qstrs(void) { MP_QSTR_case_sensitive; MP_QSTR_check_homescreen_format; MP_QSTR_chunkify; + MP_QSTR_code; MP_QSTR_coinjoin__access_account; MP_QSTR_coinjoin__do_not_disconnect; MP_QSTR_coinjoin__max_mining_fee; @@ -224,6 +225,7 @@ static void _librust_qstrs(void) { MP_QSTR_deinit; MP_QSTR_description; MP_QSTR_details_title; + MP_QSTR_device_name; MP_QSTR_device_name__change_template; MP_QSTR_device_name__title; MP_QSTR_disable_animation; @@ -656,6 +658,8 @@ static void _librust_qstrs(void) { MP_QSTR_show_instructions; MP_QSTR_show_lockscreen; MP_QSTR_show_mismatch; + MP_QSTR_show_pairing_code; + MP_QSTR_show_pairing_device_name; MP_QSTR_show_progress; MP_QSTR_show_progress_coinjoin; MP_QSTR_show_remaining_shares; diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs index db80595e13..80c3390003 100644 --- a/core/embed/rust/src/ui/api/firmware_micropython.rs +++ b/core/embed/rust/src/ui/api/firmware_micropython.rs @@ -815,6 +815,30 @@ extern "C" fn new_show_device_menu(n_args: usize, args: *const Obj, kwargs: *mut unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_pairing_device_name( + n_args: usize, + args: *const Obj, + kwargs: *mut Map, +) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let device_name: TString = kwargs.get(Qstr::MP_QSTR_device_name)?.try_into()?; + let layout = ModelUI::show_pairing_device_name(device_name)?; + let layout_obj = LayoutObj::new_root(layout)?; + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + +extern "C" fn new_show_pairing_code(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let code: TString = kwargs.get(Qstr::MP_QSTR_code)?.try_into()?; + let layout = ModelUI::show_pairing_code(code)?; + let layout_obj = LayoutObj::new_root(layout)?; + Ok(layout_obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; @@ -1582,6 +1606,20 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// """Idle homescreen.""" Qstr::MP_QSTR_show_device_menu => obj_fn_kw!(0, new_show_device_menu).as_obj(), + /// def show_pairing_device_name( + /// *, + /// device_name: str, + /// ) -> LayoutObj[UiResult]: + /// """Pairing device: first screen (device name).""" + Qstr::MP_QSTR_show_pairing_device_name => obj_fn_kw!(0, new_show_pairing_device_name).as_obj(), + + /// def show_pairing_code( + /// *, + /// code: str, + /// ) -> LayoutObj[UiResult]: + /// """Pairing device: second screen (pairing code).""" + Qstr::MP_QSTR_show_pairing_code => obj_fn_kw!(0, new_show_pairing_code).as_obj(), + /// def show_info( /// *, /// title: str, diff --git a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs index 862a4352d3..b6035c422b 100644 --- a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs @@ -874,6 +874,16 @@ impl FirmwareUI for UIBolt { Err::, Error>(Error::ValueError(c"show_device_menu not supported")) } + fn show_pairing_device_name( + device_name: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError(c"show_pairing_device_name not supported")) + } + + fn show_pairing_code(code: TString<'static>) -> Result { + Err::, Error>(Error::ValueError(c"show_pairing_code not supported")) + } + fn show_info( title: TString<'static>, description: TString<'static>, diff --git a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs index fce8dbec1f..38f21e8270 100644 --- a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs @@ -1040,6 +1040,16 @@ impl FirmwareUI for UICaesar { Err::, Error>(Error::ValueError(c"show_device_menu not supported")) } + fn show_pairing_device_name( + device_name: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError(c"show_pairing_device_name not supported")) + } + + fn show_pairing_code(code: TString<'static>) -> Result { + Err::, Error>(Error::ValueError(c"show_pairing_code not supported")) + } + fn show_info( title: TString<'static>, description: TString<'static>, diff --git a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs index ff0fc79142..693880564d 100644 --- a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs @@ -892,6 +892,16 @@ impl FirmwareUI for UIDelizia { Err::, Error>(Error::ValueError(c"show_device_menu not supported")) } + fn show_pairing_device_name( + device_name: TString<'static>, + ) -> Result { + Err::, Error>(Error::ValueError(c"show_pairing_device_name not supported")) + } + + fn show_pairing_code(code: TString<'static>) -> Result { + Err::, Error>(Error::ValueError(c"show_pairing_code not supported")) + } + fn show_info( title: TString<'static>, description: TString<'static>, diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs index b15b2603c3..a62577b080 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -17,7 +17,7 @@ use crate::{ }, Empty, FormattedText, }, - geometry::{LinearPlacement, Offset}, + geometry::{Alignment, LinearPlacement, Offset}, layout::{ obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, util::{ConfirmValueParams, RecoveryType, StrOrBytes}, @@ -788,6 +788,38 @@ impl FirmwareUI for UIEckhart { Ok(layout) } + fn show_pairing_device_name( + device_name: TString<'static>, + ) -> Result { + let font = fonts::FONT_SATOSHI_REGULAR_38; + let text_style = theme::firmware::TEXT_REGULAR; + let mut ops = OpTextLayout::new(text_style); + ops = ops.color(theme::GREEN); + ops = ops.text(device_name, font); + ops = ops.color(text_style.text_color); + let text: TString = " is your Trezor's name.".into(); + ops = ops.text(text, font); + let screen = TextScreen::new(FormattedText::new(ops)) + .with_header(Header::new("Pair with new device".into()).with_close_button()) + .with_action_bar(ActionBar::new_single(Button::with_text("Continue on host".into()))); + let layout = RootComponent::new(screen); + Ok(layout) + } + + fn show_pairing_code(code: TString<'static>) -> Result { + let text: TString<'static> = "Pairing code match?".into(); + let mut ops = OpTextLayout::new(theme::firmware::TEXT_REGULAR); + ops = ops.text(text, fonts::FONT_SATOSHI_REGULAR_38); + ops = ops.newline().newline().newline(); + ops = ops.alignment(Alignment::Center); + ops = ops.text(code, fonts::FONT_SATOSHI_EXTRALIGHT_72); + let screen = TextScreen::new(FormattedText::new(ops)) + .with_header(Header::new("Bluetooth pairing".into())) + .with_action_bar(ActionBar::new_cancel_confirm()); + let layout = RootComponent::new(screen); + Ok(layout) + } + fn show_info( title: TString<'static>, description: TString<'static>, diff --git a/core/embed/rust/src/ui/ui_firmware.rs b/core/embed/rust/src/ui/ui_firmware.rs index 7253ea8e7c..cb3c8e1dc4 100644 --- a/core/embed/rust/src/ui/ui_firmware.rs +++ b/core/embed/rust/src/ui/ui_firmware.rs @@ -307,6 +307,12 @@ pub trait FirmwareUI { paired_devices: Vec, 1>, ) -> Result; + fn show_pairing_device_name( + device_name: TString<'static>, + ) -> Result; + + fn show_pairing_code(code: TString<'static>) -> Result; + fn show_info( title: TString<'static>, description: TString<'static>, diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index f4570562dd..fb2ea02913 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -541,6 +541,22 @@ def show_device_menu( """Idle homescreen.""" +# rust/src/ui/api/firmware_micropython.rs +def show_pairing_device_name( + *, + device_name: str, +) -> LayoutObj[UiResult]: + """Pairing device: first screen (device name).""" + + +# rust/src/ui/api/firmware_micropython.rs +def show_pairing_code( + *, + code: str, +) -> LayoutObj[UiResult]: + """Pairing device: second screen (pairing code).""" + + # rust/src/ui/api/firmware_micropython.rs def show_info( *, diff --git a/core/src/apps/homescreen/__init__.py b/core/src/apps/homescreen/__init__.py index 6c8357cce3..2ff40323f4 100644 --- a/core/src/apps/homescreen/__init__.py +++ b/core/src/apps/homescreen/__init__.py @@ -66,6 +66,21 @@ async def homescreen() -> None: menu_result = await raise_if_not_confirmed(show_device_menu(failed_backup=failed_backup, battery_percentage=battery_percentage, paired_devices=paired_devices), "device_menu") print(menu_result) + if menu_result == "DevicePair": + from trezor import ui + import trezorui_api + await raise_if_not_confirmed( + trezorui_api.show_pairing_device_name( + device_name="My Trez", + ), + "device_name" + ) + await raise_if_not_confirmed( + trezorui_api.show_pairing_code( + code="123456", + ), + "pairing_code" + ) else: lock_device() finally: