refactor(core): use interact for ui.Layout
What changed, and why it matters
This is a small internal code cleanup in the Trezor firmware's user-interface layer. It changes how one screen (the device menu) is launched so it uses a shared helper function called `interact`, rather than manually creating and tearing down a layout object. There is no indication this fixes or introduces a security vulnerability.
No security action required. Treat as routine refactoring during normal review.
Security signals we found
No security-relevant keywords in commit title or message
No changes to cryptographic, authentication, or privileged operations
No changes to input parsing or boundary checks
No changes to memory allocation or deallocation semantics
No vendor or researcher attribution of security relevance
Evidence from the diff
The commit refactors handle_device_menu() in core/src/apps/homescreen/device_menu.py to call the existing interact() helper instead of manually instantiating UsbAwareLayout, awaiting obj.get_result(), and explicitly calling obj.__del__(). To support this, interact() in core/src/trezor/ui/layouts/common.py gains a layout_type parameter and accepts either a ui.LayoutObj or an already-constructed ui.Layout. The previous manual workflow.close_others() call is now handled inside interact(). The diff is purely structural: no logic changes to authentication, cryptography, memory handling, or workflow isolation.
Changed components
core/src/apps/homescreen/device_menu.pycore/src/trezor/ui/layouts/common.pyInspect captured patch +13 / −13
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index d950d9c3..c35ec1dc 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -4,11 +4,11 @@ from typing import TYPE_CHECKING
import storage.device as storage_device
import trezorble as ble
import trezorui_api
-from trezor import TR, config, log, utils, workflow
+from trezor import TR, config, log, utils
from trezor.ui.layouts import interact, raise_if_not_confirmed
from trezor.ui.layouts.homescreen import UsbAwareLayout
from trezor.wire import ActionCancelled, PinCancelled
-from trezorui_api import CANCELLED, DeviceMenuResult
+from trezorui_api import DeviceMenuResult
if TYPE_CHECKING:
from buffer_types import AnyBytes
@@ -117,8 +117,7 @@ async def handle_device_menu() -> None:
firmware_type = "Bitcoin-only" if utils.BITCOIN_ONLY else "Universal"
production_year = _get_production_year()
- workflow.close_others()
- obj = UsbAwareLayout(
+ menu_result = await interact(
trezorui_api.show_device_menu(
init_submenu_idx=init_submenu_idx,
backup_failed=backup_failed,
@@ -155,14 +154,10 @@ async def handle_device_menu() -> None:
],
production_year=production_year,
),
+ br_name=None,
+ raise_on_cancel=None,
+ layout_type=UsbAwareLayout,
)
- try:
- menu_result = await obj.get_result()
- finally:
- obj.__del__()
-
- if menu_result is CANCELLED:
- return
if not isinstance(menu_result, tuple) or len(menu_result) != 3:
raise RuntimeError(f"Unknown menu {menu_result}")
diff --git a/core/src/trezor/ui/layouts/common.py b/core/src/trezor/ui/layouts/common.py
index ef988f05..bb98ce29 100644
--- a/core/src/trezor/ui/layouts/common.py
+++ b/core/src/trezor/ui/layouts/common.py
@@ -24,6 +24,7 @@ if TYPE_CHECKING:
raise_on_cancel: ExceptionType | None = ActionCancelled,
*,
confirm_only: Literal[True],
+ layout_type: type[ui.Layout] = ui.Layout,
) -> None: ...
@overload
@@ -34,16 +35,18 @@ if TYPE_CHECKING:
raise_on_cancel: ExceptionType | None = ActionCancelled,
*,
confirm_only: bool = False,
+ layout_type: type[ui.Layout] = ui.Layout,
) -> T: ...
async def interact(
- layout_obj: ui.LayoutObj[T],
+ layout_obj: ui.LayoutObj[T] | ui.Layout[T],
br_name: str | None,
br_code: ButtonRequestType = ButtonRequestType.Other,
raise_on_cancel: ExceptionType | None = ActionCancelled,
*,
confirm_only: bool = False,
+ layout_type: type[ui.Layout] = ui.Layout,
) -> T | None:
"""Return the result of user interaction with the layout.
@@ -61,7 +64,9 @@ async def interact(
# shut down other workflows to prevent them from interfering with the current one
workflow.close_others()
# start the layout
- layout = ui.Layout(layout_obj)
+ layout = (
+ layout_obj if isinstance(layout_obj, ui.Layout) else layout_type(layout_obj)
+ )
layout.start()
if br_name is not None:
# store the first button request to be sent
Why this scored 12/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.