chore(core/eckhart): differentiate device and submenu screeens
What changed, and why it matters
This commit is a routine UI code cleanup for the Trezor hardware wallet's on-screen menu. It splits one type of menu screen into two separate types—one for general submenus and one specifically for device-related options—so the code can use a shorter menu for device screens. There is no security fix or vulnerability here; it is purely a maintainability and layout refinement.
No security action required. Treat as normal code-quality/UI refactor during review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors ActiveScreen in device_menu_screen.rs to distinguish ActiveScreen::Menu(VerticalMenuScreen<MediumMenuVec>) from a new ActiveScreen::Device(VerticalMenuScreen<ShortMenuVec>). Event handling, placement, rendering, and trace output are updated to handle the new variant. The device-specific screen is now constructed with a ShortMenuVec and its event branch is separated from generic submenu handling. No cryptographic, authentication, or trust-boundary logic is modified.
Changed components
core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rsInspect captured patch +35 / −20
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 0ba6959d..45e58f54 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
@@ -29,7 +29,7 @@ use super::{
VerticalMenu, VerticalMenuScreen, VerticalMenuScreenMsg, MEDIUM_MENU_ITEMS,
},
},
- theme, MediumMenuVec,
+ theme, MediumMenuVec, ShortMenuVec,
};
use heapless::Vec;
@@ -46,6 +46,7 @@ const MAX_DEPTH: usize = 3;
const MAX_SUBSCREENS: usize = MAX_SUBMENUS + MAX_PAIRED_DEVICES + 2;
const DIS_CONNECT_DEVICE_MENU_INDEX: usize = 0;
+const FORGET_DEVICE_MENU_INDEX: usize = 1;
#[derive(Clone)]
enum Action {
@@ -176,6 +177,7 @@ enum Subscreen {
#[allow(clippy::large_enum_variant)]
enum ActiveScreen {
Menu(VerticalMenuScreen<MediumMenuVec>),
+ Device(VerticalMenuScreen<ShortMenuVec>),
About(TextScreen<Paragraphs<PropsList>>),
Regulatory(RegulatoryScreen),
@@ -640,7 +642,7 @@ impl DeviceMenuScreen {
TR::words__forget.into(),
theme::menu_item_title_orange(),
));
- *self.active_screen.deref_mut() = ActiveScreen::Menu(
+ *self.active_screen.deref_mut() = ActiveScreen::Device(
VerticalMenuScreen::new(menu)
.with_header(
Header::new(TR::buttons__back.into())
@@ -730,6 +732,9 @@ impl Component for DeviceMenuScreen {
ActiveScreen::Menu(menu) => {
menu.place(bounds);
}
+ ActiveScreen::Device(device) => {
+ device.place(bounds);
+ }
ActiveScreen::About(about) => {
about.place(bounds);
}
@@ -746,26 +751,32 @@ impl Component for DeviceMenuScreen {
// Handle the event for the active menu
let subscreen = &self.subscreens[self.active_subscreen];
match (subscreen, self.active_screen.deref_mut()) {
- (Subscreen::Submenu(..) | Subscreen::DeviceScreen(..), ActiveScreen::Menu(menu)) => {
+ (Subscreen::Submenu(..), ActiveScreen::Menu(menu)) => match menu.event(ctx, event) {
+ Some(VerticalMenuScreenMsg::Selected(button_idx)) => {
+ return self.handle_submenu(ctx, button_idx);
+ }
+ Some(VerticalMenuScreenMsg::Back) => {
+ return self.go_back(ctx);
+ }
+ Some(VerticalMenuScreenMsg::Close) => {
+ return Some(DeviceMenuMsg::Close);
+ }
+ _ => {}
+ },
+ (Subscreen::DeviceScreen(_, connected, device_idx), ActiveScreen::Device(menu)) => {
match menu.event(ctx, event) {
- Some(VerticalMenuScreenMsg::Selected(button_idx)) => {
- if let Subscreen::DeviceScreen(_, connected, device_idx) = subscreen {
- match button_idx {
- DIS_CONNECT_DEVICE_MENU_INDEX if *connected => {
- return Some(DeviceMenuMsg::DeviceDisconnect);
- }
- DIS_CONNECT_DEVICE_MENU_INDEX if !*connected => {
- return Some(DeviceMenuMsg::DeviceConnect(*device_idx));
- }
- FORGET_DEVICE_MENU_INDEX => {
- return Some(DeviceMenuMsg::DeviceUnpair(*device_idx));
- }
- _ => {}
- }
- } else {
- return self.handle_submenu(ctx, button_idx);
+ Some(VerticalMenuScreenMsg::Selected(button_idx)) => match button_idx {
+ DIS_CONNECT_DEVICE_MENU_INDEX if *connected => {
+ return Some(DeviceMenuMsg::DeviceDisconnect);
}
- }
+ DIS_CONNECT_DEVICE_MENU_INDEX if !*connected => {
+ return Some(DeviceMenuMsg::DeviceConnect(*device_idx));
+ }
+ FORGET_DEVICE_MENU_INDEX => {
+ return Some(DeviceMenuMsg::DeviceUnpair(*device_idx));
+ }
+ _ => {}
+ },
Some(VerticalMenuScreenMsg::Back) => {
return self.go_back(ctx);
}
@@ -794,6 +805,7 @@ impl Component for DeviceMenuScreen {
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
match self.active_screen.deref() {
ActiveScreen::Menu(menu) => menu.render(target),
+ ActiveScreen::Device(device) => device.render(target),
ActiveScreen::About(about) => about.render(target),
ActiveScreen::Regulatory(regulatory) => regulatory.render(target),
ActiveScreen::Empty => {}
@@ -810,6 +822,9 @@ impl crate::trace::Trace for DeviceMenuScreen {
ActiveScreen::Menu(ref screen) => {
t.child("Menu", screen);
}
+ ActiveScreen::Device(ref screen) => {
+ t.child("Device", screen);
+ }
ActiveScreen::About(ref screen) => {
t.child("About", screen);
}
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.