refactor(core/eckhart): simplify device menu handling
What changed, and why it matters
This is a code cleanup (refactor) for the device menu on a specific Trezor hardware wallet model. It changes how menu actions are represented internally (from numbers to named strings) and simplifies how the menu decides which submenu to show next. There is no indication this fixes a security bug or introduces a vulnerability.
No security action required; review as normal code-quality refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors DeviceMenuMsg handling in the Eckhart layout. It replaces integer-based enum discriminants with Qstr-based string identifiers, removes the shared result_arg field in favor of an explicit UnpairDevice(u8) variant, and centralizes the ‘next menu’ logic into next_menu_id(). The Python side adds explicit handlers for Close and RefreshMenu and removes the previous special-case handling of RefreshMenu. No security-relevant behavior changes are visible in the diff.
Changed components
core/embed/rust/src/ui/layout/device_menu_result.rscore/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rscore/embed/rust/src/ui/layout_eckhart/component_msg_obj.rscore/embed/rust/src/ui/api/firmware_micropython.rscore/mocks/generated/trezorui_api.pyicore/src/apps/homescreen/device_menu.pyInspect captured patch +163 / −149
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 550281e6..bd7fdcea 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -25,6 +25,7 @@ static void _librust_qstrs(void) {
MP_QSTR_CANCELLED;
MP_QSTR_CONFIRMED;
MP_QSTR_CheckBackup;
+ MP_QSTR_Close;
MP_QSTR_DIM;
MP_QSTR_DONE;
MP_QSTR_DeviceMenuResult;
diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs
index 697bf393..368fde29 100644
--- a/core/embed/rust/src/ui/api/firmware_micropython.rs
+++ b/core/embed/rust/src/ui/api/firmware_micropython.rs
@@ -1987,8 +1987,8 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// led_enabled: bool | None,
/// about_items: Sequence[tuple[str | None, StrOrBytes | None, bool | None]],
/// production_year: str | None,
- /// ) -> LayoutContext[UiResult | tuple[int, int | None, int]]:
- /// """Show the device menu. Result is either CANCELLED or a tuple (action, action_arg, parent_menu_id)."""
+ /// ) -> LayoutContext[tuple[str, int | None, int]]:
+ /// """Show the device menu. Result is a tuple (action, action_arg, parent_menu_id)."""
Qstr::MP_QSTR_show_device_menu => obj_fn_kw!(0, new_show_device_menu).as_obj(),
/// def show_pairing_device_name(
@@ -2212,28 +2212,29 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// class DeviceMenuResult:
/// """Result of a device menu operation."""
- /// ReviewFailedBackup: ClassVar[int]
- /// DisconnectDevice: ClassVar[int]
- /// PairDevice: ClassVar[int]
- /// UnpairDevice: ClassVar[int]
- /// UnpairAllDevices: ClassVar[int]
- /// ToggleBluetooth: ClassVar[int]
- /// SetOrChangePin: ClassVar[int]
- /// RemovePin: ClassVar[int]
- /// SetAutoLockBattery: ClassVar[int]
- /// SetAutoLockUSB: ClassVar[int]
- /// SetOrChangeWipeCode: ClassVar[int]
- /// RemoveWipeCode: ClassVar[int]
- /// CheckBackup: ClassVar[int]
- /// SetDeviceName: ClassVar[int]
- /// SetBrightness: ClassVar[int]
- /// ToggleTapToWake: ClassVar[int]
- /// ToggleHaptics: ClassVar[int]
- /// ToggleLed: ClassVar[int]
- /// WipeDevice: ClassVar[int]
- /// Reboot: ClassVar[int]
- /// RebootToBootloader: ClassVar[int]
- /// TurnOff: ClassVar[int]
- /// RefreshMenu: ClassVar[int]
+ /// Close: ClassVar[str]
+ /// ReviewFailedBackup: ClassVar[str]
+ /// DisconnectDevice: ClassVar[str]
+ /// PairDevice: ClassVar[str]
+ /// UnpairDevice: ClassVar[str]
+ /// UnpairAllDevices: ClassVar[str]
+ /// ToggleBluetooth: ClassVar[str]
+ /// SetOrChangePin: ClassVar[str]
+ /// RemovePin: ClassVar[str]
+ /// SetAutoLockBattery: ClassVar[str]
+ /// SetAutoLockUSB: ClassVar[str]
+ /// SetOrChangeWipeCode: ClassVar[str]
+ /// RemoveWipeCode: ClassVar[str]
+ /// CheckBackup: ClassVar[str]
+ /// SetDeviceName: ClassVar[str]
+ /// SetBrightness: ClassVar[str]
+ /// ToggleTapToWake: ClassVar[str]
+ /// ToggleHaptics: ClassVar[str]
+ /// ToggleLed: ClassVar[str]
+ /// WipeDevice: ClassVar[str]
+ /// Reboot: ClassVar[str]
+ /// RebootToBootloader: ClassVar[str]
+ /// TurnOff: ClassVar[str]
+ /// RefreshMenu: ClassVar[str]
Qstr::MP_QSTR_DeviceMenuResult => DEVICE_MENU_RESULT.as_obj(),
};
diff --git a/core/embed/rust/src/ui/layout/device_menu_result.rs b/core/embed/rust/src/ui/layout/device_menu_result.rs
index ebf095ba..29d016ff 100644
--- a/core/embed/rust/src/ui/layout/device_menu_result.rs
+++ b/core/embed/rust/src/ui/layout/device_menu_result.rs
@@ -5,19 +5,16 @@ use crate::{
},
};
-use num_traits::ToPrimitive;
-
-#[repr(u8)]
-#[derive(Copy, Clone, ToPrimitive)]
+#[derive(Copy, Clone)]
pub enum DeviceMenuMsg {
- Close = 0,
+ Close,
// Root menu
ReviewFailedBackup,
// "Pair & Connect"
PairDevice, // pair a new device
DisconnectDevice, // disconnect a device
- UnpairDevice, // unpair a device, its index is in result_arg
+ UnpairDevice(u8), // unpair a device
UnpairAllDevices,
// Power
@@ -46,14 +43,45 @@ pub enum DeviceMenuMsg {
WipeDevice,
// Misc
- RefreshMenu, // menu id is in result_arg
+ RefreshMenu,
}
impl DeviceMenuMsg {
- pub fn as_obj(&self) -> Obj {
- assert!(!matches!(self, DeviceMenuMsg::Close));
- let n = unwrap!(self.to_u8());
- n.into()
+ pub fn id_to_obj(&self) -> Obj {
+ match self {
+ Self::Close => Qstr::MP_QSTR_Close,
+ Self::ReviewFailedBackup => Qstr::MP_QSTR_ReviewFailedBackup,
+ Self::PairDevice => Qstr::MP_QSTR_PairDevice,
+ Self::DisconnectDevice => Qstr::MP_QSTR_DisconnectDevice,
+ Self::UnpairDevice(_) => Qstr::MP_QSTR_UnpairDevice,
+ Self::UnpairAllDevices => Qstr::MP_QSTR_UnpairAllDevices,
+ Self::TurnOff => Qstr::MP_QSTR_TurnOff,
+ Self::Reboot => Qstr::MP_QSTR_Reboot,
+ Self::RebootToBootloader => Qstr::MP_QSTR_RebootToBootloader,
+ Self::ToggleBluetooth => Qstr::MP_QSTR_ToggleBluetooth,
+ Self::SetOrChangePin => Qstr::MP_QSTR_SetOrChangePin,
+ Self::RemovePin => Qstr::MP_QSTR_RemovePin,
+ Self::SetAutoLockBattery => Qstr::MP_QSTR_SetAutoLockBattery,
+ Self::SetAutoLockUSB => Qstr::MP_QSTR_SetAutoLockUSB,
+ Self::SetOrChangeWipeCode => Qstr::MP_QSTR_SetOrChangeWipeCode,
+ Self::RemoveWipeCode => Qstr::MP_QSTR_RemoveWipeCode,
+ Self::CheckBackup => Qstr::MP_QSTR_CheckBackup,
+ Self::SetDeviceName => Qstr::MP_QSTR_SetDeviceName,
+ Self::SetBrightness => Qstr::MP_QSTR_SetBrightness,
+ Self::ToggleTapToWake => Qstr::MP_QSTR_ToggleTapToWake,
+ Self::ToggleHaptics => Qstr::MP_QSTR_ToggleHaptics,
+ Self::ToggleLed => Qstr::MP_QSTR_ToggleLed,
+ Self::WipeDevice => Qstr::MP_QSTR_WipeDevice,
+ Self::RefreshMenu => Qstr::MP_QSTR_RefreshMenu,
+ }
+ .to_obj()
+ }
+
+ pub fn args_to_obj(&self) -> Obj {
+ match self {
+ Self::UnpairDevice(id) => (*id).into(),
+ _ => Obj::const_none(),
+ }
}
}
@@ -71,33 +99,34 @@ unsafe extern "C" fn device_menu_result_attr(_self_in: Obj, attr: ffi::qstr, des
return Err(Error::TypeError);
}
let attr = Qstr::from_u16(attr as _);
- let value = match attr {
- Qstr::MP_QSTR_ReviewFailedBackup => DeviceMenuMsg::ReviewFailedBackup.as_obj(),
- Qstr::MP_QSTR_PairDevice => DeviceMenuMsg::PairDevice.as_obj(),
- Qstr::MP_QSTR_DisconnectDevice => DeviceMenuMsg::DisconnectDevice.as_obj(),
- Qstr::MP_QSTR_UnpairDevice => DeviceMenuMsg::UnpairDevice.as_obj(),
- Qstr::MP_QSTR_UnpairAllDevices => DeviceMenuMsg::UnpairAllDevices.as_obj(),
- Qstr::MP_QSTR_ToggleBluetooth => DeviceMenuMsg::ToggleBluetooth.as_obj(),
- Qstr::MP_QSTR_SetOrChangePin => DeviceMenuMsg::SetOrChangePin.as_obj(),
- Qstr::MP_QSTR_RemovePin => DeviceMenuMsg::RemovePin.as_obj(),
- Qstr::MP_QSTR_SetAutoLockBattery => DeviceMenuMsg::SetAutoLockBattery.as_obj(),
- Qstr::MP_QSTR_SetAutoLockUSB => DeviceMenuMsg::SetAutoLockUSB.as_obj(),
- Qstr::MP_QSTR_SetOrChangeWipeCode => DeviceMenuMsg::SetOrChangeWipeCode.as_obj(),
- Qstr::MP_QSTR_RemoveWipeCode => DeviceMenuMsg::RemoveWipeCode.as_obj(),
- Qstr::MP_QSTR_CheckBackup => DeviceMenuMsg::CheckBackup.as_obj(),
- Qstr::MP_QSTR_SetDeviceName => DeviceMenuMsg::SetDeviceName.as_obj(),
- Qstr::MP_QSTR_SetBrightness => DeviceMenuMsg::SetBrightness.as_obj(),
- Qstr::MP_QSTR_ToggleTapToWake => DeviceMenuMsg::ToggleTapToWake.as_obj(),
- Qstr::MP_QSTR_ToggleHaptics => DeviceMenuMsg::ToggleHaptics.as_obj(),
- Qstr::MP_QSTR_ToggleLed => DeviceMenuMsg::ToggleLed.as_obj(),
- Qstr::MP_QSTR_WipeDevice => DeviceMenuMsg::WipeDevice.as_obj(),
- Qstr::MP_QSTR_TurnOff => DeviceMenuMsg::TurnOff.as_obj(),
- Qstr::MP_QSTR_Reboot => DeviceMenuMsg::Reboot.as_obj(),
- Qstr::MP_QSTR_RebootToBootloader => DeviceMenuMsg::RebootToBootloader.as_obj(),
- Qstr::MP_QSTR_RefreshMenu => DeviceMenuMsg::RefreshMenu.as_obj(),
+ let msg = match attr {
+ Qstr::MP_QSTR_Close => Qstr::MP_QSTR_Close,
+ Qstr::MP_QSTR_ReviewFailedBackup => Qstr::MP_QSTR_ReviewFailedBackup,
+ Qstr::MP_QSTR_PairDevice => Qstr::MP_QSTR_PairDevice,
+ Qstr::MP_QSTR_DisconnectDevice => Qstr::MP_QSTR_DisconnectDevice,
+ Qstr::MP_QSTR_UnpairDevice => Qstr::MP_QSTR_UnpairDevice,
+ Qstr::MP_QSTR_UnpairAllDevices => Qstr::MP_QSTR_UnpairAllDevices,
+ Qstr::MP_QSTR_ToggleBluetooth => Qstr::MP_QSTR_ToggleBluetooth,
+ Qstr::MP_QSTR_SetOrChangePin => Qstr::MP_QSTR_SetOrChangePin,
+ Qstr::MP_QSTR_RemovePin => Qstr::MP_QSTR_RemovePin,
+ Qstr::MP_QSTR_SetAutoLockBattery => Qstr::MP_QSTR_SetAutoLockBattery,
+ Qstr::MP_QSTR_SetAutoLockUSB => Qstr::MP_QSTR_SetAutoLockUSB,
+ Qstr::MP_QSTR_SetOrChangeWipeCode => Qstr::MP_QSTR_SetOrChangeWipeCode,
+ Qstr::MP_QSTR_RemoveWipeCode => Qstr::MP_QSTR_RemoveWipeCode,
+ Qstr::MP_QSTR_CheckBackup => Qstr::MP_QSTR_CheckBackup,
+ Qstr::MP_QSTR_SetDeviceName => Qstr::MP_QSTR_SetDeviceName,
+ Qstr::MP_QSTR_SetBrightness => Qstr::MP_QSTR_SetBrightness,
+ Qstr::MP_QSTR_ToggleTapToWake => Qstr::MP_QSTR_ToggleTapToWake,
+ Qstr::MP_QSTR_ToggleHaptics => Qstr::MP_QSTR_ToggleHaptics,
+ Qstr::MP_QSTR_ToggleLed => Qstr::MP_QSTR_ToggleLed,
+ Qstr::MP_QSTR_WipeDevice => Qstr::MP_QSTR_WipeDevice,
+ Qstr::MP_QSTR_TurnOff => Qstr::MP_QSTR_TurnOff,
+ Qstr::MP_QSTR_Reboot => Qstr::MP_QSTR_Reboot,
+ Qstr::MP_QSTR_RebootToBootloader => Qstr::MP_QSTR_RebootToBootloader,
+ Qstr::MP_QSTR_RefreshMenu => Qstr::MP_QSTR_RefreshMenu,
_ => return Err(Error::AttributeError(attr)),
};
- unsafe { dest.write(value) };
+ unsafe { dest.write(msg.to_obj()) };
Ok(())
};
unsafe { util::try_or_raise(block) }
diff --git a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs
index 3528d055..5b927e9e 100644
--- a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs
@@ -15,8 +15,8 @@ use crate::{
};
use super::firmware::{
- AllowedTextContent, ConfirmHomescreen, ConfirmHomescreenMsg, DeviceMenuMsg, DeviceMenuScreen,
- Homescreen, HomescreenMsg, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PinKeyboard,
+ AllowedTextContent, ConfirmHomescreen, ConfirmHomescreenMsg, DeviceMenuScreen, Homescreen,
+ HomescreenMsg, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PinKeyboard,
PinKeyboardMsg, ProgressScreen, SelectWordCountMsg, SelectWordCountScreen, SelectWordMsg,
SelectWordScreen, SetBrightnessScreen, StringInput, StringKeyboard, StringKeyboardMsg,
TextScreen, TextScreenMsg, ValueInput, ValueInputScreen, ValueInputScreenMsg,
@@ -157,16 +157,9 @@ impl ComponentMsgObj for SetBrightnessScreen {
impl ComponentMsgObj for DeviceMenuScreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
- if matches!(msg, DeviceMenuMsg::Close) {
- return Ok(CANCELLED.as_obj());
- }
- let action_obj = msg.to_u8().into();
- let result: Option<u8> = match msg {
- DeviceMenuMsg::UnpairDevice | DeviceMenuMsg::RefreshMenu => self.result_arg,
- _ => None,
- };
- let result_obj = result.into();
- let parent_idx_obj = DeviceMenuScreen::parent(msg).to_u8().into();
- new_tuple(&[action_obj, result_obj, parent_idx_obj])
+ let action_obj = msg.id_to_obj();
+ let result_obj = msg.args_to_obj();
+ let next_menu_obj = self.next_menu_id(msg).to_u8().into();
+ new_tuple(&[action_obj, result_obj, next_menu_obj])
}
}
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 15f2adef..b93e86c8 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
@@ -73,12 +73,14 @@ enum Action {
}
impl DeviceMenuScreen {
- pub fn parent(msg: DeviceMenuMsg) -> DeviceMenuId {
+ /// Which submenu should be reloaded after msg is handled.
+ pub fn next_menu_id(&self, msg: DeviceMenuMsg) -> DeviceMenuId {
match msg {
+ DeviceMenuMsg::Close => DeviceMenuId::Root,
DeviceMenuMsg::ReviewFailedBackup => DeviceMenuId::Root,
DeviceMenuMsg::PairDevice => DeviceMenuId::PairAndConnect,
DeviceMenuMsg::DisconnectDevice => DeviceMenuId::PairAndConnect,
- DeviceMenuMsg::UnpairDevice => DeviceMenuId::PairAndConnect,
+ DeviceMenuMsg::UnpairDevice(_) => DeviceMenuId::PairAndConnect,
DeviceMenuMsg::UnpairAllDevices => DeviceMenuId::PairAndConnect,
DeviceMenuMsg::TurnOff => DeviceMenuId::Power,
DeviceMenuMsg::Reboot => DeviceMenuId::Power,
@@ -97,8 +99,13 @@ impl DeviceMenuScreen {
DeviceMenuMsg::ToggleHaptics => DeviceMenuId::Device,
DeviceMenuMsg::ToggleLed => DeviceMenuId::Device,
DeviceMenuMsg::WipeDevice => DeviceMenuId::Device,
- DeviceMenuMsg::RefreshMenu => DeviceMenuId::Root,
- DeviceMenuMsg::Close => DeviceMenuId::Root,
+ DeviceMenuMsg::RefreshMenu => match self.active_screen.deref() {
+ ActiveScreen::Menu(_, id) => *id,
+ ActiveScreen::Device(_) => DeviceMenuId::PairAndConnect,
+ ActiveScreen::Regulatory(_) | ActiveScreen::About(_) => DeviceMenuId::Device,
+ ActiveScreen::Empty | ActiveScreen::BackupInfo(_) => DeviceMenuId::Root,
+ ActiveScreen::HostInfo(_) => DeviceMenuId::PairAndConnect,
+ },
}
}
}
@@ -274,9 +281,6 @@ pub struct DeviceMenuScreen {
// index of the current subscreen in the list of subscreens
active_subscreen: u8,
- // Integer argument for DeviceMenuMsg::RefreshMenu and DeviceMenuMsg::UnpairDevice
- pub result_arg: Option<u8>,
-
// Production year string for Regulatory screen
production_year: Option<TString<'static>>,
}
@@ -310,7 +314,6 @@ impl DeviceMenuScreen {
submenus: GcBox::new(Vec::new())?,
subscreens: Vec::new(),
submenu_index: [None; MAX_SUBMENUS],
- result_arg: None,
production_year,
};
@@ -545,18 +548,13 @@ impl DeviceMenuScreen {
fn register_auto_lock_menu(&mut self, auto_lock_delay: [TString<'static>; 2]) {
let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
- let battery_delay = MenuItem::new(
- auto_lock_delay[0],
- Some(Action::Return(DeviceMenuMsg::SetAutoLockBattery)),
- )
- .with_subtext(Some((TR::auto_lock__on_battery.into(), None)));
+ let battery_delay =
+ MenuItem::return_msg(auto_lock_delay[0], DeviceMenuMsg::SetAutoLockBattery)
+ .with_subtext(Some((TR::auto_lock__on_battery.into(), None)));
items.add(battery_delay);
- let usb_delay = MenuItem::new(
- auto_lock_delay[1],
- Some(Action::Return(DeviceMenuMsg::SetAutoLockUSB)),
- )
- .with_subtext(Some((TR::auto_lock__on_usb.into(), None)));
+ let usb_delay = MenuItem::return_msg(auto_lock_delay[1], DeviceMenuMsg::SetAutoLockUSB)
+ .with_subtext(Some((TR::auto_lock__on_usb.into(), None)));
items.add(usb_delay);
self.register_submenu(DeviceMenuId::AutoLock, Submenu::new(items));
@@ -1031,28 +1029,19 @@ impl Component for DeviceMenuScreen {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
- let refresh = match event {
- Event::USB(USBEvent::Configured | USBEvent::Deconfigured) => true,
+ // Refresh this layout after reloading connection status
+ match event {
+ Event::USB(USBEvent::Configured | USBEvent::Deconfigured) => {
+ return Some(DeviceMenuMsg::RefreshMenu)
+ }
#[cfg(feature = "ble")]
Event::BLE(
BLEEvent::Connected | BLEEvent::Disconnected | BLEEvent::ConnectionChanged,
- ) => true,
+ ) => return Some(DeviceMenuMsg::RefreshMenu),
- _ => false,
+ _ => (),
};
- if refresh {
- let submenu_idx = match self.active_screen.deref() {
- ActiveScreen::Menu(_, id) => *id,
- ActiveScreen::Device(_) => DeviceMenuId::PairAndConnect,
- ActiveScreen::Regulatory(_) | ActiveScreen::About(_) => DeviceMenuId::Device,
- ActiveScreen::Empty | ActiveScreen::BackupInfo(_) => DeviceMenuId::Root,
- ActiveScreen::HostInfo(_) => DeviceMenuId::PairAndConnect,
- };
-
- self.result_arg = submenu_idx.to_u8();
- return Some(DeviceMenuMsg::RefreshMenu);
- }
// Handle the event for the active menu
let subscreen = &self.subscreens[usize::from(self.active_subscreen)];
@@ -1083,8 +1072,9 @@ impl Component for DeviceMenuScreen {
return None;
}
(1, false) | (2, true) => {
- self.result_arg = Some(device_screen.device_index);
- return Some(DeviceMenuMsg::UnpairDevice);
+ return Some(DeviceMenuMsg::UnpairDevice(
+ device_screen.device_index,
+ ));
}
_ => {}
}
diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi
index 98c73ffe..8b811fbb 100644
--- a/core/mocks/generated/trezorui_api.pyi
+++ b/core/mocks/generated/trezorui_api.pyi
@@ -647,8 +647,8 @@ def show_device_menu(
led_enabled: bool | None,
about_items: Sequence[tuple[str | None, StrOrBytes | None, bool | None]],
production_year: str | None,
-) -> LayoutContext[UiResult | tuple[int, int | None, int]]:
- """Show the device menu. Result is either CANCELLED or a tuple (action, action_arg, parent_menu_id)."""
+) -> LayoutContext[tuple[str, int | None, int]]:
+ """Show the device menu. Result is a tuple (action, action_arg, parent_menu_id)."""
# rust/src/ui/api/firmware_micropython.rs
@@ -896,26 +896,27 @@ class LayoutState:
# rust/src/ui/api/firmware_micropython.rs
class DeviceMenuResult:
"""Result of a device menu operation."""
- ReviewFailedBackup: ClassVar[int]
- DisconnectDevice: ClassVar[int]
- PairDevice: ClassVar[int]
- UnpairDevice: ClassVar[int]
- UnpairAllDevices: ClassVar[int]
- ToggleBluetooth: ClassVar[int]
- SetOrChangePin: ClassVar[int]
- RemovePin: ClassVar[int]
- SetAutoLockBattery: ClassVar[int]
- SetAutoLockUSB: ClassVar[int]
- SetOrChangeWipeCode: ClassVar[int]
- RemoveWipeCode: ClassVar[int]
- CheckBackup: ClassVar[int]
- SetDeviceName: ClassVar[int]
- SetBrightness: ClassVar[int]
- ToggleTapToWake: ClassVar[int]
- ToggleHaptics: ClassVar[int]
- ToggleLed: ClassVar[int]
- WipeDevice: ClassVar[int]
- Reboot: ClassVar[int]
- RebootToBootloader: ClassVar[int]
- TurnOff: ClassVar[int]
- RefreshMenu: ClassVar[int]
+ Close: ClassVar[str]
+ ReviewFailedBackup: ClassVar[str]
+ DisconnectDevice: ClassVar[str]
+ PairDevice: ClassVar[str]
+ UnpairDevice: ClassVar[str]
+ UnpairAllDevices: ClassVar[str]
+ ToggleBluetooth: ClassVar[str]
+ SetOrChangePin: ClassVar[str]
+ RemovePin: ClassVar[str]
+ SetAutoLockBattery: ClassVar[str]
+ SetAutoLockUSB: ClassVar[str]
+ SetOrChangeWipeCode: ClassVar[str]
+ RemoveWipeCode: ClassVar[str]
+ CheckBackup: ClassVar[str]
+ SetDeviceName: ClassVar[str]
+ SetBrightness: ClassVar[str]
+ ToggleTapToWake: ClassVar[str]
+ ToggleHaptics: ClassVar[str]
+ ToggleLed: ClassVar[str]
+ WipeDevice: ClassVar[str]
+ Reboot: ClassVar[str]
+ RebootToBootloader: ClassVar[str]
+ TurnOff: ClassVar[str]
+ RefreshMenu: ClassVar[str]
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index dcb81785..7ad342e0 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -165,21 +165,13 @@ async def handle_device_menu() -> None:
production_year=production_year,
) as layout:
menu_result = await interact(
- layout,
- br_name=None,
- raise_on_cancel=None,
- layout_type=UsbAwareLayout,
+ layout, br_name=None, layout_type=UsbAwareLayout
)
if not isinstance(menu_result, tuple) or len(menu_result) != 3:
raise RuntimeError(f"Unknown menu {menu_result}")
- action, arg, parent_submenu_idx = menu_result
- # special handling
- if action == DeviceMenuResult.RefreshMenu:
- init_submenu_idx = arg
- continue
-
+ action, arg, init_submenu_idx = menu_result
handler = _MENU_HANDLERS.get(action)
if not handler:
raise RuntimeError(f"Unknown menu {menu_result}")
@@ -192,11 +184,12 @@ async def handle_device_menu() -> None:
except ExitDeviceMenu:
break
except (ActionCancelled, PinCancelled):
- # return to the submenu if flow was cancelled
+ # return to the submenu if handler was cancelled / succeeded
continue
- finally:
- # return to submenu on success or cancellation
- init_submenu_idx = parent_submenu_idx
+
+
+async def handle_Close() -> None:
+ raise ExitDeviceMenu # return to homescreen
async def handle_ReviewFailedBackup() -> None:
@@ -493,7 +486,12 @@ async def handle_RebootToBootloader() -> None:
raise RuntimeError
+async def handle_RefreshMenu() -> None:
+ pass
+
+
_MENU_HANDLERS = {
+ DeviceMenuResult.Close: handle_Close,
DeviceMenuResult.ReviewFailedBackup: handle_ReviewFailedBackup,
DeviceMenuResult.DisconnectDevice: handle_DisconnectDevice,
DeviceMenuResult.PairDevice: handle_PairDevice,
@@ -516,4 +514,5 @@ _MENU_HANDLERS = {
DeviceMenuResult.TurnOff: handle_TurnOff,
DeviceMenuResult.Reboot: handle_Reboot,
DeviceMenuResult.RebootToBootloader: handle_RebootToBootloader,
+ DeviceMenuResult.RefreshMenu: handle_RefreshMenu,
}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.