diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs index b092656aba..259d2be092 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs @@ -1,4 +1,5 @@ use crate::{ + strutil::TString, translations::TR, ui::{ component::{Component, Event, EventCtx, Timeout}, @@ -128,6 +129,14 @@ impl ActionBar { Self::new(Mode::PaginateOnly, None, None, None) } + pub fn new_text_only(text: TString<'static>) -> Self { + Self::new_single( + Button::with_text(text) + .styled(theme::button_always_disabled()) + .initially_enabled(false), + ) + } + pub fn with_left_short(mut self, short: bool) -> Self { if let Mode::Double { ref mut left_short } = self.mode { *left_short = short; diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs index 19106c0233..dc690d79e9 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs @@ -545,6 +545,22 @@ pub const fn button_keyboard_next() -> ButtonStyleSheet { } } +// Things that look like button but don't do anything. +pub const fn button_always_disabled() -> ButtonStyleSheet { + let style = &ButtonStyle { + font: fonts::FONT_SATOSHI_MEDIUM_26, + text_color: GREY_LIGHT, + button_color: BG, + icon_color: GREY_LIGHT, + background_color: BG, + }; + ButtonStyleSheet { + normal: style, + active: style, + disabled: style, + } +} + pub const fn input_mnemonic() -> ButtonStyleSheet { ButtonStyleSheet { normal: &ButtonStyle { 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 c024a6b662..c2f77720c7 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -1003,9 +1003,7 @@ impl FirmwareUI for UIEckhart { 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(), - ))); + .with_action_bar(ActionBar::new_text_only("Continue on host".into())); #[cfg(feature = "ble")] let screen = crate::ui::component::BLEHandler::new(screen, true); let layout = RootComponent::new(screen); diff --git a/core/embed/upymod/qstrdefsport.h b/core/embed/upymod/qstrdefsport.h index 238ccc72e2..f9b9cbcad4 100644 --- a/core/embed/upymod/qstrdefsport.h +++ b/core/embed/upymod/qstrdefsport.h @@ -124,6 +124,7 @@ Q(apps.management.apply_flags) Q(apps.management.apply_settings) Q(apps.management.authenticate_device) Q(apps.management.backup_device) +Q(apps.management.ble) Q(apps.management.ble.pair_new_device) Q(apps.management.ble.unpair) Q(apps.management.change_language) @@ -168,6 +169,7 @@ Q(benchmark) Q(benchmarks) Q(bitcoin) Q(bitcoinlike) +Q(ble) Q(bolt) Q(boot) Q(cache) diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py index 4d84cbde8a..97ecbdddd0 100644 --- a/core/src/apps/homescreen/device_menu.py +++ b/core/src/apps/homescreen/device_menu.py @@ -1,6 +1,7 @@ import storage.device +import trezorble as ble import trezorui_api -from trezor import utils +from trezor import log, utils from trezor.ui.layouts import raise_if_not_confirmed @@ -8,10 +9,14 @@ async def handle_device_menu() -> None: # MOCK DATA failed_backup = True battery_percentage = 22 - paired_devices = ["Trezor Suite"] + paired_devices = ["Trezor Suite"] if ble.is_connected() else [] # ### firmware_version = ".".join(map(str, utils.VERSION)) device_name = storage.device.get_label() or "Trezor" + log.debug( + __name__, + f"device menu, BLE state: {ble.connection_flags()} (peers: {ble.peer_count()})", + ) menu_result = await raise_if_not_confirmed( trezorui_api.show_device_menu( @@ -27,5 +32,11 @@ async def handle_device_menu() -> None: from apps.management.ble.pair_new_device import pair_new_device await pair_new_device() + elif menu_result == "DeviceDisconnect": + from trezor.messages import BleUnpair + + from apps.management.ble.unpair import unpair + + await unpair(BleUnpair(all=False)) else: raise RuntimeError(f"Unknown menu {menu_result}") diff --git a/core/src/apps/management/ble/__init__.py b/core/src/apps/management/ble/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/src/apps/management/ble/pair_new_device.py b/core/src/apps/management/ble/pair_new_device.py index d80daf0524..474047c3ce 100644 --- a/core/src/apps/management/ble/pair_new_device.py +++ b/core/src/apps/management/ble/pair_new_device.py @@ -1,17 +1,43 @@ +import trezorble as ble import trezorui_api -from trezor.ui.layouts import interact +from storage import device as storage_device +from trezor import utils +from trezor.ui.layouts import CONFIRMED, raise_if_not_confirmed +from trezor.wire import ActionCancelled + + +def _end_pairing() -> None: + if ble.peer_count() > 0: + ble.start_advertising(True, storage_device.get_label()) + else: + ble.stop_advertising() async def pair_new_device() -> None: - label = "Trezor T3W1" - await interact( - trezorui_api.show_pairing_device_name(device_name=label), - None, - raise_on_cancel=None, # for UI testing - ) + label = storage_device.get_label() or utils.MODEL_FULL_NAME + ble.start_advertising(False, label) + try: + code = await raise_if_not_confirmed( + trezorui_api.show_pairing_device_name( + device_name=label, + ), + None, + ) + if not isinstance(code, int): + raise ActionCancelled - code = 12345 - await interact( - trezorui_api.show_pairing_code(code=f"{code:0>6}"), - None, - ) + try: + result = await raise_if_not_confirmed( + trezorui_api.show_pairing_code( + code=f"{code:0>6}", + ), + None, + ) + except Exception: + ble.reject_pairing() + raise + else: + if result is CONFIRMED: + ble.allow_pairing(code) + finally: + _end_pairing()