refactor(core): refactor T3W1 device menu
What changed, and why it matters
This commit is a straightforward internal code cleanup for the T3W1 hardware wallet's on-device menu. It changes how menu choices are represented behind the scenes (from unique object tokens to plain integer codes) and reorganizes the Python handler code into smaller functions. There is no indication this fixes a security bug or introduces a new vulnerability.
No security action required. Treat as normal refactoring review; verify existing device-menu functional tests still pass.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors the DeviceMenuResult/DeviceMenuMsg handling in the Trezor Core firmware. Previously, menu actions were returned as distinct SimpleTypeObj instances and sometimes wrapped in tuples; now they are returned as a uniform tuple (action: u8, action_arg: Option
Changed components
core/embed/rust/src/ui/api/firmware_micropython.rscore/embed/rust/src/ui/layout/device_menu_result.rscore/embed/rust/src/ui/layout_eckhart/component_msg_obj.rscore/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rscore/mocks/generated/trezorui_api.pyicore/src/apps/homescreen/device_menu.pyInspect captured patch +534 / −540
diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs
index 070af6f9..94321cd0 100644
--- a/core/embed/rust/src/ui/api/firmware_micropython.rs
+++ b/core/embed/rust/src/ui/api/firmware_micropython.rs
@@ -1974,8 +1974,8 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// haptics_enabled: bool | None,
/// led_enabled: bool | None,
/// about_items: Sequence[tuple[str | None, StrOrBytes | None, bool | None]],
- /// ) -> LayoutObj[UiResult | DeviceMenuResult | tuple[DeviceMenuResult, int]]:
- /// """Show the device menu."""
+ /// ) -> LayoutObj[UiResult | tuple[int, int | None, int]]:
+ /// """Show the device menu. Result is either CANCELLED or 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(
@@ -2186,27 +2186,27 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// class DeviceMenuResult:
/// """Result of a device menu operation."""
- /// ReviewFailedBackup: ClassVar[DeviceMenuResult]
- /// DisconnectDevice: ClassVar[DeviceMenuResult]
- /// PairDevice: ClassVar[DeviceMenuResult]
- /// UnpairDevice: ClassVar[DeviceMenuResult]
- /// UnpairAllDevices: ClassVar[DeviceMenuResult]
- /// ToggleBluetooth: ClassVar[DeviceMenuResult]
- /// SetOrChangePin: ClassVar[DeviceMenuResult]
- /// RemovePin: ClassVar[DeviceMenuResult]
- /// SetAutoLockBattery: ClassVar[DeviceMenuResult]
- /// SetAutoLockUSB: ClassVar[DeviceMenuResult]
- /// SetOrChangeWipeCode: ClassVar[DeviceMenuResult]
- /// RemoveWipeCode: ClassVar[DeviceMenuResult]
- /// CheckBackup: ClassVar[DeviceMenuResult]
- /// SetDeviceName: ClassVar[DeviceMenuResult]
- /// SetBrightness: ClassVar[DeviceMenuResult]
- /// ToggleHaptics: ClassVar[DeviceMenuResult]
- /// ToggleLed: ClassVar[DeviceMenuResult]
- /// WipeDevice: ClassVar[DeviceMenuResult]
- /// Reboot: ClassVar[DeviceMenuResult]
- /// RebootToBootloader: ClassVar[DeviceMenuResult]
- /// TurnOff: ClassVar[DeviceMenuResult]
- /// RefreshMenu: ClassVar[DeviceMenuResult]
+ /// 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]
+ /// ToggleHaptics: ClassVar[int]
+ /// ToggleLed: ClassVar[int]
+ /// WipeDevice: ClassVar[int]
+ /// Reboot: ClassVar[int]
+ /// RebootToBootloader: ClassVar[int]
+ /// TurnOff: ClassVar[int]
+ /// RefreshMenu: ClassVar[int]
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 bac63b18..6b85fcc2 100644
--- a/core/embed/rust/src/ui/layout/device_menu_result.rs
+++ b/core/embed/rust/src/ui/layout/device_menu_result.rs
@@ -1,72 +1,104 @@
-use crate::micropython::{
- macros::{obj_dict, obj_map, obj_type},
- qstr::Qstr,
- simple_type::SimpleTypeObj,
- typ::Type,
+use crate::{
+ error::Error,
+ micropython::{
+ ffi, macros::obj_type, obj::Obj, qstr::Qstr, simple_type::SimpleTypeObj, typ::Type, util,
+ },
};
-static DEVICE_MENU_RESULT_BASE_TYPE: Type = obj_type! { name: Qstr::MP_QSTR_DeviceMenuResult, };
+use num_traits::ToPrimitive;
-// Root menu
-pub static REVIEW_FAILED_BACKUP: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static BACKUP_DEVICE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-// "Pair & Connect"
-pub static PAIR_DEVICE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static DISCONNECT_DEVICE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static UNPAIR_DEVICE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static UNPAIR_ALL_DEVICES: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-// Settings
-pub static TOGGLE_BLUETOOTH: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+#[repr(u8)]
+#[derive(Copy, Clone, ToPrimitive)]
+pub enum DeviceMenuMsg {
+ Close = 0,
+ // Root menu
+ ReviewFailedBackup,
-// Security menu
-pub static SET_OR_CHANGE_PIN: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static REMOVE_PIN: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static SET_AUTO_LOCK_BATTERY: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static SET_AUTO_LOCK_USB: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static SET_OR_CHANGE_WIPE_CODE: SimpleTypeObj =
- SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static REMOVE_WIPE_CODE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static CHECK_BACKUP: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-// Device menu
-pub static SET_DEVICE_NAME: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static SET_BRIGHTNESS: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static TOGGLE_HAPTICS: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static TOGGLE_LED: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static WIPE_DEVICE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-// Power settings
-pub static TURN_OFF: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static REBOOT: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-pub static REBOOT_TO_BOOTLOADER: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
-// Misc
-pub static REFRESH_MENU: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+ // "Pair & Connect"
+ PairDevice, // pair a new device
+ DisconnectDevice, // disconnect a device
+ UnpairDevice, // unpair a device, its index is in result_arg
+ UnpairAllDevices,
+
+ // Power
+ TurnOff,
+ Reboot,
+ RebootToBootloader,
+
+ // Settings menu
+ ToggleBluetooth,
+
+ // Security menu
+ SetOrChangePin,
+ RemovePin,
+ SetAutoLockBattery,
+ SetAutoLockUSB,
+ SetOrChangeWipeCode,
+ RemoveWipeCode,
+ CheckBackup,
+
+ // Device menu
+ SetDeviceName,
+ SetBrightness,
+ ToggleHaptics,
+ ToggleLed,
+ WipeDevice,
+
+ // Misc
+ RefreshMenu, // menu id is in result_arg
+}
+
+impl DeviceMenuMsg {
+ pub fn as_obj(&self) -> Obj {
+ assert!(!matches!(self, DeviceMenuMsg::Close));
+ let n = unwrap!(self.to_u8());
+ n.into()
+ }
+}
// Create a DeviceMenuResult class that contains all result types
static DEVICE_MENU_RESULT_TYPE: Type = obj_type! {
name: Qstr::MP_QSTR_DeviceMenuResult,
- locals: &obj_dict! { obj_map! {
- Qstr::MP_QSTR_ReviewFailedBackup => REVIEW_FAILED_BACKUP.as_obj(),
- Qstr::MP_QSTR_PairDevice => PAIR_DEVICE.as_obj(),
- Qstr::MP_QSTR_DisconnectDevice => DISCONNECT_DEVICE.as_obj(),
- Qstr::MP_QSTR_UnpairDevice => UNPAIR_DEVICE.as_obj(),
- Qstr::MP_QSTR_UnpairAllDevices => UNPAIR_ALL_DEVICES.as_obj(),
- Qstr::MP_QSTR_ToggleBluetooth => TOGGLE_BLUETOOTH.as_obj(),
- Qstr::MP_QSTR_SetOrChangePin => SET_OR_CHANGE_PIN.as_obj(),
- Qstr::MP_QSTR_RemovePin => REMOVE_PIN.as_obj(),
- Qstr::MP_QSTR_SetAutoLockBattery => SET_AUTO_LOCK_BATTERY.as_obj(),
- Qstr::MP_QSTR_SetAutoLockUSB => SET_AUTO_LOCK_USB.as_obj(),
- Qstr::MP_QSTR_SetOrChangeWipeCode => SET_OR_CHANGE_WIPE_CODE.as_obj(),
- Qstr::MP_QSTR_RemoveWipeCode => REMOVE_WIPE_CODE.as_obj(),
- Qstr::MP_QSTR_CheckBackup => CHECK_BACKUP.as_obj(),
- Qstr::MP_QSTR_SetDeviceName => SET_DEVICE_NAME.as_obj(),
- Qstr::MP_QSTR_SetBrightness => SET_BRIGHTNESS.as_obj(),
- Qstr::MP_QSTR_ToggleHaptics => TOGGLE_HAPTICS.as_obj(),
- Qstr::MP_QSTR_ToggleLed => TOGGLE_LED.as_obj(),
- Qstr::MP_QSTR_WipeDevice => WIPE_DEVICE.as_obj(),
- Qstr::MP_QSTR_TurnOff => TURN_OFF.as_obj(),
- Qstr::MP_QSTR_Reboot => REBOOT.as_obj(),
- Qstr::MP_QSTR_RebootToBootloader => REBOOT_TO_BOOTLOADER.as_obj(),
- Qstr::MP_QSTR_RefreshMenu => REFRESH_MENU.as_obj(),
- } },
+ attr_fn: device_menu_result_attr,
};
+unsafe extern "C" fn device_menu_result_attr(_self_in: Obj, attr: ffi::qstr, dest: *mut Obj) {
+ let block = || {
+ let arg = unsafe { dest.read() };
+ if !arg.is_null() {
+ // Null destination would mean a `setattr`.
+ 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_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(),
+ _ => return Err(Error::AttributeError(attr)),
+ };
+ unsafe { dest.write(value) };
+ Ok(())
+ };
+ unsafe { util::try_or_raise(block) }
+}
+
pub static DEVICE_MENU_RESULT: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_TYPE);
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 715e5609..3528d055 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
@@ -1,3 +1,5 @@
+use num_traits::ToPrimitive;
+
#[cfg(not(feature = "clippy"))]
use crate::ui::component::{
text::paragraphs::{ParagraphSource, Paragraphs},
@@ -7,7 +9,6 @@ use crate::{
error::Error,
micropython::{obj::Obj, util::new_tuple},
ui::layout::{
- device_menu_result::*,
obj::ComponentMsgObj,
result::{CANCELLED, CONFIRMED, INFO},
},
@@ -156,43 +157,16 @@ impl ComponentMsgObj for SetBrightnessScreen {
impl ComponentMsgObj for DeviceMenuScreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
- match msg {
- // Root menu
- DeviceMenuMsg::ReviewFailedBackup => Ok(REVIEW_FAILED_BACKUP.as_obj()),
- // "Pair & Connect"
- DeviceMenuMsg::PairDevice => Ok(PAIR_DEVICE.as_obj()),
- DeviceMenuMsg::DisconnectDevice => Ok(DISCONNECT_DEVICE.as_obj()),
- DeviceMenuMsg::UnpairDevice(index) => {
- Ok(new_tuple(&[UNPAIR_DEVICE.as_obj(), index.into()])?)
- }
- DeviceMenuMsg::UnpairAllDevices => Ok(UNPAIR_ALL_DEVICES.as_obj()),
- // Settings
- DeviceMenuMsg::ToggleBluetooth => Ok(TOGGLE_BLUETOOTH.as_obj()),
-
- // Security menu
- DeviceMenuMsg::SetOrChangePin => Ok(SET_OR_CHANGE_PIN.as_obj()),
- DeviceMenuMsg::RemovePin => Ok(REMOVE_PIN.as_obj()),
- DeviceMenuMsg::SetAutoLockBattery => Ok(SET_AUTO_LOCK_BATTERY.as_obj()),
- DeviceMenuMsg::SetAutoLockUSB => Ok(SET_AUTO_LOCK_USB.as_obj()),
- DeviceMenuMsg::SetOrChangeWipeCode => Ok(SET_OR_CHANGE_WIPE_CODE.as_obj()),
- DeviceMenuMsg::RemoveWipeCode => Ok(REMOVE_WIPE_CODE.as_obj()),
- DeviceMenuMsg::CheckBackup => Ok(CHECK_BACKUP.as_obj()),
- // Device menu
- DeviceMenuMsg::SetDeviceName => Ok(SET_DEVICE_NAME.as_obj()),
- DeviceMenuMsg::SetBrightness => Ok(SET_BRIGHTNESS.as_obj()),
- DeviceMenuMsg::ToggleHaptics => Ok(TOGGLE_HAPTICS.as_obj()),
- DeviceMenuMsg::ToggleLed => Ok(TOGGLE_LED.as_obj()),
- DeviceMenuMsg::WipeDevice => Ok(WIPE_DEVICE.as_obj()),
- // Power settings
- DeviceMenuMsg::TurnOff => Ok(TURN_OFF.as_obj()),
- DeviceMenuMsg::Reboot => Ok(REBOOT.as_obj()),
- DeviceMenuMsg::RebootToBootloader => Ok(REBOOT_TO_BOOTLOADER.as_obj()),
- // Misc
- DeviceMenuMsg::RefreshMenu(submenu_id) => {
- let submenu_idx: u8 = submenu_id.into();
- Ok(new_tuple(&[REFRESH_MENU.as_obj(), submenu_idx.into()])?)
- }
- DeviceMenuMsg::Close => Ok(CANCELLED.as_obj()),
+ 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])
}
}
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 65a3874f..07280a08 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
@@ -1,8 +1,6 @@
-use core::{
- convert::TryFrom,
- ops::{Deref, DerefMut},
-};
+use core::ops::{Deref, DerefMut};
+pub use crate::ui::layout::device_menu_result::DeviceMenuMsg;
use crate::{
error::Error,
micropython::{gc::GcBox, obj::Obj},
@@ -38,9 +36,10 @@ use super::{
theme, MediumMenuVec, ShortMenuVec,
};
use heapless::Vec;
+use num_traits::{FromPrimitive, ToPrimitive};
#[repr(u8)]
-#[derive(Copy, Clone, Default)]
+#[derive(Copy, Clone, Default, FromPrimitive, ToPrimitive)]
#[cfg_attr(test, derive(Debug))]
pub enum DeviceMenuId {
#[default]
@@ -55,38 +54,6 @@ pub enum DeviceMenuId {
Power,
}
-impl TryFrom<u8> for DeviceMenuId {
- type Error = ();
- fn try_from(v: u8) -> Result<Self, Self::Error> {
- match v {
- 0 => Ok(DeviceMenuId::Root),
- 1 => Ok(DeviceMenuId::PairAndConnect),
- 2 => Ok(DeviceMenuId::Settings),
- 3 => Ok(DeviceMenuId::Security),
- 4 => Ok(DeviceMenuId::PinCode),
- 5 => Ok(DeviceMenuId::AutoLock),
- 6 => Ok(DeviceMenuId::WipeCode),
- 7 => Ok(DeviceMenuId::Device),
- 8 => Ok(DeviceMenuId::Power),
- _ => Err(()),
- }
- }
-}
-
-impl From<DeviceMenuId> for u8 {
- #[inline]
- fn from(id: DeviceMenuId) -> Self {
- id as u8
- }
-}
-
-impl From<DeviceMenuId> for usize {
- #[inline]
- fn from(id: DeviceMenuId) -> Self {
- usize::from(id as u8)
- }
-}
-
// FIXME: use mem::variant_count when it becomes stable
const MAX_SUBMENUS: usize = 9;
// submenus, device + hostinfo screen couples, regulatory and about screens
@@ -102,46 +69,34 @@ enum Action {
Return(DeviceMenuMsg),
}
-#[derive(Copy, Clone)]
-pub enum DeviceMenuMsg {
- // Root menu
- ReviewFailedBackup,
-
- // "Pair & Connect"
- PairDevice, // pair a new device
- DisconnectDevice, // disconnect a device
- UnpairDevice(
- u8, /* which device to unpair, index in the list of devices */
- ),
- UnpairAllDevices,
-
- // Power
- TurnOff,
- Reboot,
- RebootToBootloader,
-
- // Settings menu
- ToggleBluetooth,
-
- // Security menu
- SetOrChangePin,
- RemovePin,
- SetAutoLockBattery,
- SetAutoLockUSB,
- SetOrChangeWipeCode,
- RemoveWipeCode,
- CheckBackup,
-
- // Device menu
- SetDeviceName,
- SetBrightness,
- ToggleHaptics,
- ToggleLed,
- WipeDevice,
-
- // Misc
- RefreshMenu(DeviceMenuId),
- Close,
+impl DeviceMenuScreen {
+ pub fn parent(msg: DeviceMenuMsg) -> DeviceMenuId {
+ match msg {
+ DeviceMenuMsg::ReviewFailedBackup => DeviceMenuId::Root,
+ DeviceMenuMsg::PairDevice => DeviceMenuId::PairAndConnect,
+ DeviceMenuMsg::DisconnectDevice => DeviceMenuId::PairAndConnect,
+ DeviceMenuMsg::UnpairDevice => DeviceMenuId::PairAndConnect,
+ DeviceMenuMsg::UnpairAllDevices => DeviceMenuId::PairAndConnect,
+ DeviceMenuMsg::TurnOff => DeviceMenuId::Power,
+ DeviceMenuMsg::Reboot => DeviceMenuId::Power,
+ DeviceMenuMsg::RebootToBootloader => DeviceMenuId::Power,
+ DeviceMenuMsg::ToggleBluetooth => DeviceMenuId::Settings,
+ DeviceMenuMsg::SetOrChangePin => DeviceMenuId::Security,
+ DeviceMenuMsg::RemovePin => DeviceMenuId::Security,
+ DeviceMenuMsg::SetAutoLockBattery => DeviceMenuId::Security,
+ DeviceMenuMsg::SetAutoLockUSB => DeviceMenuId::Security,
+ DeviceMenuMsg::SetOrChangeWipeCode => DeviceMenuId::Security,
+ DeviceMenuMsg::RemoveWipeCode => DeviceMenuId::Security,
+ DeviceMenuMsg::CheckBackup => DeviceMenuId::Security,
+ DeviceMenuMsg::SetDeviceName => DeviceMenuId::Device,
+ DeviceMenuMsg::SetBrightness => DeviceMenuId::Device,
+ DeviceMenuMsg::ToggleHaptics => DeviceMenuId::Device,
+ DeviceMenuMsg::ToggleLed => DeviceMenuId::Device,
+ DeviceMenuMsg::WipeDevice => DeviceMenuId::Device,
+ DeviceMenuMsg::RefreshMenu => DeviceMenuId::Root,
+ DeviceMenuMsg::Close => DeviceMenuId::Root,
+ }
+ }
}
trait MenuVecExt {
@@ -314,6 +269,9 @@ 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>,
}
impl DeviceMenuScreen {
@@ -343,6 +301,7 @@ impl DeviceMenuScreen {
submenus: GcBox::new(Vec::new())?,
subscreens: Vec::new(),
submenu_index: [None; MAX_SUBMENUS],
+ result_arg: None,
};
if pin_enabled.is_some()
@@ -416,7 +375,7 @@ impl DeviceMenuScreen {
// Activate the init submenu
let init_submenu_id = init_submenu_idx
- .and_then(|v| DeviceMenuId::try_from(v).ok())
+ .and_then(DeviceMenuId::from_u8)
.unwrap_or_default();
let init_subscreen = unwrap!(screen.try_resolve_submenu(init_submenu_id));
@@ -429,12 +388,12 @@ impl DeviceMenuScreen {
fn register_submenu(&mut self, id: DeviceMenuId, submenu: Submenu) {
let idx_in_submenus = self.add_submenu(submenu);
let subscreen_idx = self.add_subscreen(Subscreen::Submenu(idx_in_submenus, id));
- self.submenu_index[usize::from(id)] = Some(subscreen_idx);
+ self.submenu_index[unwrap!(id.to_usize())] = Some(subscreen_idx);
}
#[inline]
fn try_resolve_submenu(&self, id: DeviceMenuId) -> Option<u8> {
- self.submenu_index[usize::from(id)]
+ self.submenu_index[unwrap!(id.to_usize())]
}
#[inline]
@@ -1048,7 +1007,8 @@ impl Component for DeviceMenuScreen {
ActiveScreen::HostInfo(_) => DeviceMenuId::PairAndConnect,
};
- return Some(DeviceMenuMsg::RefreshMenu(submenu_idx));
+ self.result_arg = submenu_idx.to_u8();
+ return Some(DeviceMenuMsg::RefreshMenu);
}
// Handle the event for the active menu
@@ -1080,9 +1040,8 @@ impl Component for DeviceMenuScreen {
return None;
}
(1, false) | (2, true) => {
- return Some(DeviceMenuMsg::UnpairDevice(
- device_screen.device_index,
- ));
+ self.result_arg = Some(device_screen.device_index);
+ return Some(DeviceMenuMsg::UnpairDevice);
}
_ => {}
}
@@ -1146,7 +1105,7 @@ impl crate::trace::Trace for DeviceMenuScreen {
if let Subscreen::Submenu(_, id) =
self.subscreens[usize::from(self.active_subscreen)]
{
- t.int("MenuId", u8::from(id).into());
+ t.int("MenuId", unwrap!(id.to_i64()));
}
}
ActiveScreen::Device(ref screen) => {
diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi
index 82f8aa44..243d175c 100644
--- a/core/mocks/generated/trezorui_api.pyi
+++ b/core/mocks/generated/trezorui_api.pyi
@@ -639,8 +639,8 @@ def show_device_menu(
haptics_enabled: bool | None,
led_enabled: bool | None,
about_items: Sequence[tuple[str | None, StrOrBytes | None, bool | None]],
-) -> LayoutObj[UiResult | DeviceMenuResult | tuple[DeviceMenuResult, int]]:
- """Show the device menu."""
+) -> LayoutObj[UiResult | tuple[int, int | None, int]]:
+ """Show the device menu. Result is either CANCELLED or a tuple (action, action_arg, parent_menu_id)."""
# rust/src/ui/api/firmware_micropython.rs
@@ -873,25 +873,25 @@ class LayoutState:
# rust/src/ui/api/firmware_micropython.rs
class DeviceMenuResult:
"""Result of a device menu operation."""
- ReviewFailedBackup: ClassVar[DeviceMenuResult]
- DisconnectDevice: ClassVar[DeviceMenuResult]
- PairDevice: ClassVar[DeviceMenuResult]
- UnpairDevice: ClassVar[DeviceMenuResult]
- UnpairAllDevices: ClassVar[DeviceMenuResult]
- ToggleBluetooth: ClassVar[DeviceMenuResult]
- SetOrChangePin: ClassVar[DeviceMenuResult]
- RemovePin: ClassVar[DeviceMenuResult]
- SetAutoLockBattery: ClassVar[DeviceMenuResult]
- SetAutoLockUSB: ClassVar[DeviceMenuResult]
- SetOrChangeWipeCode: ClassVar[DeviceMenuResult]
- RemoveWipeCode: ClassVar[DeviceMenuResult]
- CheckBackup: ClassVar[DeviceMenuResult]
- SetDeviceName: ClassVar[DeviceMenuResult]
- SetBrightness: ClassVar[DeviceMenuResult]
- ToggleHaptics: ClassVar[DeviceMenuResult]
- ToggleLed: ClassVar[DeviceMenuResult]
- WipeDevice: ClassVar[DeviceMenuResult]
- Reboot: ClassVar[DeviceMenuResult]
- RebootToBootloader: ClassVar[DeviceMenuResult]
- TurnOff: ClassVar[DeviceMenuResult]
- RefreshMenu: ClassVar[DeviceMenuResult]
+ 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]
+ ToggleHaptics: ClassVar[int]
+ ToggleLed: ClassVar[int]
+ WipeDevice: ClassVar[int]
+ Reboot: ClassVar[int]
+ RebootToBootloader: ClassVar[int]
+ TurnOff: ClassVar[int]
+ RefreshMenu: ClassVar[int]
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index 8ed4697a..fd82fdad 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -1,5 +1,4 @@
import utime
-from micropython import const
from typing import TYPE_CHECKING
import storage.device as storage_device
@@ -16,17 +15,9 @@ if TYPE_CHECKING:
from trezor.messages import ThpPairedCacheEntry
-# Must be in sync with the DeviceMenuId in device_menu.ui
-class SubmenuId:
- ROOT = const(0)
- PAIR_AND_CONNECT = const(1)
- SETTINGS = const(2)
- SECURITY = const(3)
- PIN_CODE = const(4)
- AUTO_LOCK = const(5)
- WIPE_CODE = const(6)
- DEVICE = const(7)
- POWER = const(8)
+# Idicates that menu should be closed and return to homescreen.
+class ExitDeviceMenu(Exception):
+ pass
def _get_hostinfo(
@@ -155,301 +146,339 @@ async def handle_device_menu() -> None:
"device_menu",
raise_on_cancel=None,
)
- # Root menu
- if menu_result is DeviceMenuResult.ReviewFailedBackup and backup_failed:
- from trezor.messages import WipeDevice
-
- from apps.management.wipe_device import wipe_device
-
- try:
- await raise_if_not_confirmed(
- trezorui_api.show_warning(
- title=TR.homescreen__title_backup_failed,
- button=TR.words__wipe,
- description=TR.wipe__start_again,
- danger=True,
- ),
- "prompt_device_wipe",
- )
- await wipe_device(WipeDevice())
- except ActionCancelled:
- init_submenu_idx = SubmenuId.ROOT
- else:
- break
- # Pair & Connect
- elif menu_result is DeviceMenuResult.DisconnectDevice and ble.is_connected():
- init_submenu_idx = SubmenuId.PAIR_AND_CONNECT
- try:
- utils.notify_send(utils.NOTIFY_DISCONNECT)
- utime.sleep_ms(300)
- ble.disconnect()
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.PAIR_AND_CONNECT
- elif menu_result is DeviceMenuResult.PairDevice:
- from trezor.ui.layouts import show_warning
-
- from apps.management.ble.pair_new_device import pair_new_device
-
- init_submenu_idx = SubmenuId.PAIR_AND_CONNECT
- # Show warning if Bluetooth is not enabled
- if not ble_enabled:
- try:
- await interact(
- trezorui_api.show_warning(
- title=TR.words__important,
- description=TR.ble__must_be_enabled,
- button=TR.buttons__turn_on,
- allow_cancel=True,
- danger=False,
- ),
- "enable_bluetooth",
- )
- except ActionCancelled:
- continue
- else:
- ble_enable(True)
-
- try:
- if len(paired_devices) < BLE_MAX_BONDS:
- await pair_new_device()
- else:
- await show_warning(
- "device_pair",
- TR.ble__limit_reached,
- button=TR.buttons__confirm,
- )
- except ActionCancelled:
- pass
- elif menu_result is DeviceMenuResult.UnpairAllDevices:
- from trezor.messages import BleUnpair
-
- from apps.management.ble.unpair import unpair
-
- try:
- await unpair(BleUnpair(all=True))
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.PAIR_AND_CONNECT
- elif isinstance(menu_result, tuple):
- from trezor.messages import BleUnpair
-
- from apps.management.ble.unpair import unpair
-
- # It's a tuple with (result_type, index)
- result_type, index = menu_result
- if result_type is DeviceMenuResult.UnpairDevice and index < len(bonds):
- try:
- await unpair(BleUnpair(addr=bonds[index]))
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.PAIR_AND_CONNECT
- # Refresh only
- elif result_type is DeviceMenuResult.RefreshMenu:
- init_submenu_idx = index
- else:
- raise RuntimeError(f"Unknown menu {result_type}, {index}")
- # Settings
- elif menu_result is DeviceMenuResult.ToggleBluetooth:
- init_submenu_idx = SubmenuId.SETTINGS
- # Toggle Bluetooth
- ble_enable(not ble_enabled)
- # Security settings
- elif menu_result is DeviceMenuResult.SetOrChangePin and is_initialized:
- from trezor.messages import ChangePin
-
- from apps.management.change_pin import change_pin
-
- try:
- await change_pin(ChangePin())
- except (ActionCancelled, PinCancelled):
- pass
- finally:
- init_submenu_idx = SubmenuId.SECURITY
- elif menu_result is DeviceMenuResult.RemovePin and config.has_pin():
- from trezor.messages import ChangePin
-
- from apps.management.change_pin import change_pin
-
- try:
- await change_pin(ChangePin(remove=True))
- except (ActionCancelled, PinCancelled):
- pass
- finally:
- init_submenu_idx = SubmenuId.SECURITY
- elif (
- menu_result
- in (DeviceMenuResult.SetAutoLockUSB, DeviceMenuResult.SetAutoLockBattery)
- and config.has_pin()
- ):
- from trezor.messages import ApplySettings
-
- from apps.management.apply_settings import apply_settings
-
- try:
- if menu_result is DeviceMenuResult.SetAutoLockUSB:
- duration_ms = storage_device.get_autolock_delay_ms()
- min_ms = storage_device.AUTOLOCK_DELAY_USB_MIN_MS
- max_ms = storage_device.AUTOLOCK_DELAY_USB_MAX_MS
- else:
- duration_ms = storage_device.get_autolock_delay_battery_ms()
- min_ms = storage_device.AUTOLOCK_DELAY_BATT_MIN_MS
- max_ms = storage_device.AUTOLOCK_DELAY_BATT_MAX_MS
-
- auto_lock_delay_ms = await interact(
- trezorui_api.request_duration(
- title=TR.auto_lock__title,
- duration_ms=duration_ms,
- min_ms=min_ms,
- max_ms=max_ms,
- description=TR.auto_lock__description,
- ),
- br_name=None,
- )
- # Necessary for the style check not to raise type error
- assert isinstance(auto_lock_delay_ms, int)
- if menu_result is DeviceMenuResult.SetAutoLockUSB:
- settings = ApplySettings(
- auto_lock_delay_ms=auto_lock_delay_ms,
- )
- else:
- settings = ApplySettings(
- auto_lock_delay_battery_ms=auto_lock_delay_ms,
- )
- await apply_settings(settings)
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.SECURITY
- elif menu_result is DeviceMenuResult.SetOrChangeWipeCode and is_initialized:
- from trezor.messages import ChangeWipeCode
-
- from apps.management.change_wipe_code import change_wipe_code
-
- try:
- await change_wipe_code(ChangeWipeCode())
- except (ActionCancelled, PinCancelled):
- pass
- finally:
- init_submenu_idx = SubmenuId.SECURITY
- elif menu_result is DeviceMenuResult.RemoveWipeCode and config.has_wipe_code():
- from trezor.messages import ChangeWipeCode
-
- from apps.management.change_wipe_code import change_wipe_code
-
- try:
- await change_wipe_code(ChangeWipeCode(remove=True))
- except (ActionCancelled, PinCancelled):
- pass
- finally:
- init_submenu_idx = SubmenuId.SECURITY
- elif menu_result is DeviceMenuResult.CheckBackup and is_initialized:
- from trezor.enums import RecoveryType
- from trezor.messages import RecoveryDevice
-
- from apps.management.recovery_device import recovery_device
-
- try:
-
- await recovery_device(
- RecoveryDevice(
- type=RecoveryType.DryRun,
- )
- )
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.SECURITY
- # Device settings
- elif menu_result is DeviceMenuResult.SetDeviceName and is_initialized:
- from trezor.messages import ApplySettings
-
- from apps.management.apply_settings import apply_settings
-
- try:
- label = await interact(
- trezorui_api.request_string(
- prompt=TR.device_name__enter,
- max_len=storage_device.LABEL_MAXLENGTH,
- allow_empty=True,
- prefill=storage_device.get_label(),
- ),
- "device_name",
- )
- # Necessary for the style check not to raise type error
- assert isinstance(label, str)
- await apply_settings(ApplySettings(label=label))
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.DEVICE
- elif menu_result is DeviceMenuResult.SetBrightness and is_initialized:
- from trezor.messages import SetBrightness
-
- from apps.management.set_brightness import set_brightness
-
- try:
- await set_brightness(SetBrightness())
- utils.notify_send(utils.NOTIFY_SETTING_CHANGE)
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.DEVICE
- elif menu_result is DeviceMenuResult.ToggleHaptics and haptic_configurable:
- from trezor import io
-
- try:
- enable = not storage_device.get_haptic_feedback()
- io.haptic.haptic_set_enabled(enable)
- storage_device.set_haptic_feedback(enable)
- utils.notify_send(utils.NOTIFY_SETTING_CHANGE)
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.DEVICE
- elif menu_result is DeviceMenuResult.ToggleLed and led_configurable:
- from trezor import io
-
- try:
- enable = not storage_device.get_rgb_led()
- io.rgb_led.rgb_led_set_enabled(enable)
- storage_device.set_rgb_led(enable)
- utils.notify_send(utils.NOTIFY_SETTING_CHANGE)
- except ActionCancelled:
- pass
- finally:
- init_submenu_idx = SubmenuId.DEVICE
- elif menu_result is DeviceMenuResult.WipeDevice:
- from trezor.messages import WipeDevice
-
- from apps.management.wipe_device import wipe_device
-
- try:
- await wipe_device(WipeDevice())
- except ActionCancelled:
- init_submenu_idx = SubmenuId.DEVICE
- else:
- break
- # Power settings
- elif menu_result is DeviceMenuResult.TurnOff:
- from trezor import io
-
- io.pm.hibernate()
- raise RuntimeError
- elif menu_result is DeviceMenuResult.Reboot:
- from trezor.utils import reboot
-
- reboot()
- raise RuntimeError
- elif menu_result is DeviceMenuResult.RebootToBootloader:
- from trezor.utils import reboot_to_bootloader
-
- reboot_to_bootloader()
- raise RuntimeError
- elif menu_result is CANCELLED:
+
+ if menu_result is CANCELLED:
return
- else:
+
+ 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
+ handler = _MENU_HANDLERS.get(action)
+ if not handler:
raise RuntimeError(f"Unknown menu {menu_result}")
+
+ # special handling
+ if action == DeviceMenuResult.RefreshMenu:
+ init_submenu_idx = arg
+ continue
+
+ try:
+ if arg is None:
+ await handler()
+ else:
+ await handler(arg)
+ except ExitDeviceMenu:
+ break
+ except (ActionCancelled, PinCancelled):
+ # return to the submenu if flow was cancelled
+ continue
+ finally:
+ # return to submenu on success or cancellation
+ init_submenu_idx = parent_submenu_idx
+
+
+async def handle_ReviewFailedBackup() -> None:
+ from trezor.messages import WipeDevice
+
+ from apps.management.wipe_device import wipe_device
+
+ is_initialized = storage_device.is_initialized()
+ backup_failed = is_initialized and storage_device.unfinished_backup()
+ utils.ensure(backup_failed)
+
+ await raise_if_not_confirmed(
+ trezorui_api.show_warning(
+ title=TR.homescreen__title_backup_failed,
+ button=TR.words__wipe,
+ description=TR.wipe__start_again,
+ danger=True,
+ ),
+ "prompt_device_wipe",
+ )
+ await wipe_device(WipeDevice())
+ raise ExitDeviceMenu # return to homescreen
+
+
+async def handle_DisconnectDevice() -> None:
+ utils.ensure(ble.is_connected())
+
+ utils.notify_send(utils.NOTIFY_DISCONNECT)
+ utime.sleep_ms(300)
+ ble.disconnect()
+
+
+async def handle_PairDevice() -> None:
+ from trezor.ui.layouts import show_warning
+ from trezor.wire.thp import paired_cache
+
+ from apps.management.ble.pair_new_device import pair_new_device
+
+ # Show warning if Bluetooth is not enabled
+ if not ble.get_enabled():
+ await interact(
+ trezorui_api.show_warning(
+ title=TR.words__important,
+ description=TR.ble__must_be_enabled,
+ button=TR.buttons__turn_on,
+ allow_cancel=True,
+ danger=False,
+ ),
+ "enable_bluetooth",
+ )
+ ble_enable(True)
+
+ hostname_map = {e.mac_addr: e for e in paired_cache.load()}
+ paired_devices = [_get_hostinfo(bond, hostname_map) for bond in ble.get_bonds()]
+ if len(paired_devices) < ble.MAX_BONDS:
+ await pair_new_device()
+ else:
+ await show_warning(
+ "device_pair",
+ TR.ble__limit_reached,
+ button=TR.buttons__confirm,
+ )
+
+
+async def handle_UnpairAllDevices() -> None:
+ from trezor.messages import BleUnpair
+
+ from apps.management.ble.unpair import unpair
+
+ await unpair(BleUnpair(all=True))
+
+
+async def handle_UnpairDevice(index: int) -> None:
+ from trezor.messages import BleUnpair
+
+ from apps.management.ble.unpair import unpair
+
+ bonds = ble.get_bonds()
+ if index < len(bonds):
+ await unpair(BleUnpair(addr=bonds[index]))
+
+
+async def handle_ToggleBluetooth() -> None:
+ ble_enable(not ble.get_enabled())
+
+
+async def handle_SetOrChangePin() -> None:
+ from trezor.messages import ChangePin
+
+ from apps.management.change_pin import change_pin
+
+ utils.ensure(storage_device.is_initialized())
+
+ await change_pin(ChangePin())
+
+
+async def handle_RemovePin() -> None:
+ from trezor.messages import ChangePin
+
+ from apps.management.change_pin import change_pin
+
+ utils.ensure(config.has_pin())
+
+ await change_pin(ChangePin(remove=True))
+
+
+async def handle_SetAutoLockUSB() -> None:
+ from trezor.messages import ApplySettings
+
+ from apps.management.apply_settings import apply_settings
+
+ utils.ensure(config.has_pin())
+
+ duration_ms = storage_device.get_autolock_delay_ms()
+ min_ms = storage_device.AUTOLOCK_DELAY_USB_MIN_MS
+ max_ms = storage_device.AUTOLOCK_DELAY_USB_MAX_MS
+
+ auto_lock_delay_ms = await interact(
+ trezorui_api.request_duration(
+ title=TR.auto_lock__title,
+ duration_ms=duration_ms,
+ min_ms=min_ms,
+ max_ms=max_ms,
+ description=TR.auto_lock__description,
+ ),
+ br_name=None,
+ )
+ # Necessary for the style check not to raise type error
+ assert isinstance(auto_lock_delay_ms, int)
+ settings = ApplySettings(
+ auto_lock_delay_ms=auto_lock_delay_ms,
+ )
+ await apply_settings(settings)
+
+
+async def handle_SetAutoLockBattery() -> None:
+ from trezor.messages import ApplySettings
+
+ from apps.management.apply_settings import apply_settings
+
+ utils.ensure(config.has_pin())
+
+ duration_ms = storage_device.get_autolock_delay_battery_ms()
+ min_ms = storage_device.AUTOLOCK_DELAY_BATT_MIN_MS
+ max_ms = storage_device.AUTOLOCK_DELAY_BATT_MAX_MS
+
+ auto_lock_delay_ms = await interact(
+ trezorui_api.request_duration(
+ title=TR.auto_lock__title,
+ duration_ms=duration_ms,
+ min_ms=min_ms,
+ max_ms=max_ms,
+ description=TR.auto_lock__description,
+ ),
+ br_name=None,
+ )
+ # Necessary for the style check not to raise type error
+ assert isinstance(auto_lock_delay_ms, int)
+ settings = ApplySettings(
+ auto_lock_delay_battery_ms=auto_lock_delay_ms,
+ )
+ await apply_settings(settings)
+
+
+async def handle_SetOrChangeWipeCode() -> None:
+ from trezor.messages import ChangeWipeCode
+
+ from apps.management.change_wipe_code import change_wipe_code
+
+ utils.ensure(storage_device.is_initialized())
+
+ await change_wipe_code(ChangeWipeCode())
+
+
+async def handle_RemoveWipeCode() -> None:
+ from trezor.messages import ChangeWipeCode
+
+ from apps.management.change_wipe_code import change_wipe_code
+
+ utils.ensure(config.has_wipe_code())
+
+ await change_wipe_code(ChangeWipeCode(remove=True))
+
+
+async def handle_CheckBackup() -> None:
+ from trezor.enums import RecoveryType
+ from trezor.messages import RecoveryDevice
+
+ from apps.management.recovery_device import recovery_device
+
+ utils.ensure(storage_device.is_initialized())
+
+ await recovery_device(
+ RecoveryDevice(
+ type=RecoveryType.DryRun,
+ )
+ )
+
+
+async def handle_SetDeviceName() -> None:
+ from trezor.messages import ApplySettings
+
+ from apps.management.apply_settings import apply_settings
+
+ utils.ensure(storage_device.is_initialized())
+
+ label = await interact(
+ trezorui_api.request_string(
+ prompt=TR.device_name__enter,
+ max_len=storage_device.LABEL_MAXLENGTH,
+ allow_empty=True,
+ prefill=storage_device.get_label(),
+ ),
+ "device_name",
+ )
+ # Necessary for the style check not to raise type error
+ assert isinstance(label, str)
+ await apply_settings(ApplySettings(label=label))
+
+
+async def handle_SetBrightness() -> None:
+ from trezor.messages import SetBrightness
+
+ from apps.management.set_brightness import set_brightness
+
+ utils.ensure(storage_device.is_initialized())
+
+ await set_brightness(SetBrightness())
+ utils.notify_send(utils.NOTIFY_SETTING_CHANGE)
+
+
+async def handle_ToggleHaptics() -> None:
+ from trezor import io
+
+ utils.ensure(storage_device.is_initialized() and utils.USE_HAPTIC)
+
+ enable = not storage_device.get_haptic_feedback()
+ io.haptic.haptic_set_enabled(enable)
+ storage_device.set_haptic_feedback(enable)
+ utils.notify_send(utils.NOTIFY_SETTING_CHANGE)
+
+
+async def handle_ToggleLed() -> None:
+ from trezor import io
+
+ utils.ensure(storage_device.is_initialized() and utils.USE_RGB_LED)
+
+ enable = not storage_device.get_rgb_led()
+ io.rgb_led.rgb_led_set_enabled(enable)
+ storage_device.set_rgb_led(enable)
+ utils.notify_send(utils.NOTIFY_SETTING_CHANGE)
+
+
+async def handle_WipeDevice() -> None:
+ from trezor.messages import WipeDevice
+
+ from apps.management.wipe_device import wipe_device
+
+ await wipe_device(WipeDevice())
+ raise ExitDeviceMenu # return to homescreen
+
+
+async def handle_TurnOff() -> None:
+ from trezor import io
+
+ io.pm.hibernate()
+ raise RuntimeError
+
+
+async def handle_Reboot() -> None:
+ from trezor.utils import reboot
+
+ reboot()
+ raise RuntimeError
+
+
+async def handle_RebootToBootloader() -> None:
+ from trezor.utils import reboot_to_bootloader
+
+ reboot_to_bootloader()
+ raise RuntimeError
+
+
+_MENU_HANDLERS = {
+ DeviceMenuResult.ReviewFailedBackup: handle_ReviewFailedBackup,
+ DeviceMenuResult.DisconnectDevice: handle_DisconnectDevice,
+ DeviceMenuResult.PairDevice: handle_PairDevice,
+ DeviceMenuResult.UnpairAllDevices: handle_UnpairAllDevices,
+ DeviceMenuResult.UnpairDevice: handle_UnpairDevice,
+ DeviceMenuResult.ToggleBluetooth: handle_ToggleBluetooth,
+ DeviceMenuResult.SetOrChangePin: handle_SetOrChangePin,
+ DeviceMenuResult.RemovePin: handle_RemovePin,
+ DeviceMenuResult.SetAutoLockUSB: handle_SetAutoLockUSB,
+ DeviceMenuResult.SetAutoLockBattery: handle_SetAutoLockBattery,
+ DeviceMenuResult.SetOrChangeWipeCode: handle_SetOrChangeWipeCode,
+ DeviceMenuResult.RemoveWipeCode: handle_RemoveWipeCode,
+ DeviceMenuResult.CheckBackup: handle_CheckBackup,
+ DeviceMenuResult.SetDeviceName: handle_SetDeviceName,
+ DeviceMenuResult.SetBrightness: handle_SetBrightness,
+ DeviceMenuResult.ToggleHaptics: handle_ToggleHaptics,
+ DeviceMenuResult.ToggleLed: handle_ToggleLed,
+ DeviceMenuResult.WipeDevice: handle_WipeDevice,
+ DeviceMenuResult.TurnOff: handle_TurnOff,
+ DeviceMenuResult.Reboot: handle_Reboot,
+ DeviceMenuResult.RebootToBootloader: handle_RebootToBootloader,
+}
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.