diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs index b6711d4a44..81254ab120 100644 --- a/core/embed/rust/src/ui/api/firmware_micropython.rs +++ b/core/embed/rust/src/ui/api/firmware_micropython.rs @@ -1621,14 +1621,16 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// *, /// device_name: str, /// ) -> LayoutObj[UiResult]: - /// """Pairing device: first screen (device name).""" + /// """Pairing device: first screen (device name). + /// Returns if BLEEvent::PairingRequest is received.""" 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).""" + /// """Pairing device: second screen (pairing code). + /// Returns on BLEEvent::{PairingCanceled, Disconnected}.""" Qstr::MP_QSTR_show_pairing_code => obj_fn_kw!(0, new_show_pairing_code).as_obj(), /// def show_info( diff --git a/core/embed/rust/src/ui/component/ble.rs b/core/embed/rust/src/ui/component/ble.rs new file mode 100644 index 0000000000..aa4bfbea84 --- /dev/null +++ b/core/embed/rust/src/ui/component/ble.rs @@ -0,0 +1,83 @@ +use crate::ui::{ + component::{Component, Event, EventCtx}, + event::BLEEvent, +}; + +pub struct BLEHandler { + inner: T, + waiting_for_pairing: bool, +} + +pub enum BLEHandlerMsg { + Content(T), + PairingCode(u32), + Cancelled, +} + +impl BLEHandler { + pub fn new(inner: T, waiting_for_pairing: bool) -> Self { + Self { + inner, + waiting_for_pairing, + } + } +} + +impl Component for BLEHandler +where + T: Component, +{ + type Msg = BLEHandlerMsg; + + fn place(&mut self, bounds: crate::ui::geometry::Rect) -> crate::ui::geometry::Rect { + self.inner.place(bounds) + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + match (event, self.waiting_for_pairing) { + (Event::BLE(BLEEvent::PairingRequest(num)), true) => { + return Some(BLEHandlerMsg::PairingCode(num)) + } + (Event::BLE(BLEEvent::PairingCanceled), false) + | (Event::BLE(BLEEvent::Disconnected), false) => return Some(BLEHandlerMsg::Cancelled), + _ => {} + } + self.inner.event(ctx, event).map(BLEHandlerMsg::Content) + } + + fn render<'s>(&'s self, target: &mut impl crate::ui::shape::Renderer<'s>) { + self.inner.render(target) + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for BLEHandler +where + T: crate::trace::Trace, +{ + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + self.inner.trace(t) + } +} + +#[cfg(feature = "micropython")] +mod micropython { + use super::*; + use crate::{ + error::Error, + micropython::obj::Obj, + ui::layout::{obj::ComponentMsgObj, result::CANCELLED}, + }; + impl ComponentMsgObj for BLEHandler + where + T: ComponentMsgObj, + { + fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { + match msg { + BLEHandlerMsg::Content(msg) => self.inner.msg_try_into_obj(msg), + BLEHandlerMsg::PairingCode(num) => num.try_into(), + BLEHandlerMsg::Cancelled => Ok(CANCELLED.as_obj()), + } + } + } +} diff --git a/core/embed/rust/src/ui/component/mod.rs b/core/embed/rust/src/ui/component/mod.rs index 2af6799156..418618be52 100644 --- a/core/embed/rust/src/ui/component/mod.rs +++ b/core/embed/rust/src/ui/component/mod.rs @@ -2,6 +2,8 @@ pub mod bar; pub mod base; +#[cfg(feature = "ble")] +mod ble; pub mod border; pub mod button_request; #[cfg(all( @@ -32,6 +34,8 @@ pub mod timeout; pub use bar::Bar; pub use base::{Child, Component, ComponentExt, Event, EventCtx, FlowMsg, Never, Timer}; +#[cfg(feature = "ble")] +pub use ble::BLEHandler; pub use border::Border; pub use button_request::{ButtonRequestExt, SendButtonRequest}; #[cfg(all( diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs index 412553fb45..463b549511 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs @@ -180,6 +180,10 @@ impl<'a> DeviceMenuScreen<'a> { let device = screen.add_device_menu(device_name, about); let settings = screen.add_settings_menu(security, device); + let is_connected = !paired_devices.is_empty(); // TODO this is mostly bad + let connected_subtext: Option> = + is_connected.then_some("1 device connected".into()); + let mut paired_device_indices: Vec = Vec::new(); for (i, device) in paired_devices.iter().enumerate() { unwrap!(paired_device_indices @@ -187,9 +191,10 @@ impl<'a> DeviceMenuScreen<'a> { } let devices = screen.add_paired_devices_menu(paired_devices, paired_device_indices); - let pair_and_connect = screen.add_pair_and_connect_menu(devices); + let pair_and_connect = screen.add_pair_and_connect_menu(devices, connected_subtext); - let root = screen.add_root_menu(failed_backup, pair_and_connect, settings); + let root = + screen.add_root_menu(failed_backup, pair_and_connect, settings, connected_subtext); screen.set_active_subscreen(root); @@ -219,17 +224,18 @@ impl<'a> DeviceMenuScreen<'a> { self.add_subscreen(Subscreen::Submenu(submenu_index)) } - fn add_pair_and_connect_menu(&mut self, manage_devices_index: usize) -> usize { + fn add_pair_and_connect_menu( + &mut self, + manage_devices_index: usize, + connected_subtext: Option>, + ) -> usize { let mut items: Vec = Vec::new(); unwrap!(items.push( MenuItem::new( "Manage paired devices".into(), Some(Action::GoTo(manage_devices_index)), ) - .with_subtext(Some(( - "1 device connected".into(), - Some(Button::SUBTEXT_STYLE_GREEN) - ))) + .with_subtext(connected_subtext.map(|t| (t, Some(Button::SUBTEXT_STYLE_GREEN)))) )); unwrap!(items.push(MenuItem::new( "Pair new device".into(), @@ -293,6 +299,7 @@ impl<'a> DeviceMenuScreen<'a> { failed_backup: bool, pair_and_connect_index: usize, settings_index: usize, + connected_subtext: Option>, ) -> usize { let mut items: Vec = Vec::new(); if failed_backup { @@ -310,10 +317,7 @@ impl<'a> DeviceMenuScreen<'a> { "Pair & connect".into(), Some(Action::GoTo(pair_and_connect_index)), ) - .with_subtext(Some(( - "1 device connected".into(), - Some(Button::SUBTEXT_STYLE_GREEN) - ))) + .with_subtext(connected_subtext.map(|t| (t, Some(Button::SUBTEXT_STYLE_GREEN)))) )); unwrap!(items.push(MenuItem::new( "Settings".into(), 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 35f31d514c..d4d3df3168 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -15,7 +15,7 @@ use crate::{ Paragraphs, VecExt, }, }, - Empty, FormattedText, + BLEHandler, Empty, FormattedText, }, geometry::{Alignment, LinearPlacement, Offset}, layout::{ @@ -814,6 +814,7 @@ impl FirmwareUI for UIEckhart { .with_action_bar(ActionBar::new_single(Button::with_text( "Continue on host".into(), ))); + let screen = BLEHandler::new(screen, true); let layout = RootComponent::new(screen); Ok(layout) } @@ -828,6 +829,7 @@ impl FirmwareUI for UIEckhart { let screen = TextScreen::new(FormattedText::new(ops)) .with_header(Header::new("Bluetooth pairing".into())) .with_action_bar(ActionBar::new_cancel_confirm()); + let screen = BLEHandler::new(screen, false); let layout = RootComponent::new(screen); Ok(layout) } diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index 9b61436244..145c245cdc 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -549,7 +549,8 @@ def show_pairing_device_name( *, device_name: str, ) -> LayoutObj[UiResult]: - """Pairing device: first screen (device name).""" + """Pairing device: first screen (device name). + Returns if BLEEvent::PairingRequest is received.""" # rust/src/ui/api/firmware_micropython.rs @@ -557,7 +558,8 @@ def show_pairing_code( *, code: str, ) -> LayoutObj[UiResult]: - """Pairing device: second screen (pairing code).""" + """Pairing device: second screen (pairing code). + Returns on BLEEvent::{PairingCanceled, Disconnected}.""" # rust/src/ui/api/firmware_micropython.rs