refactor(core): rewrite debug overlay in rust
What changed, and why it matters
This commit is a developer-facing refactor that moves the emulator-only debug overlay and screenshot recording logic from Python into Rust. It does not change production firmware behavior; the new features are gated behind debug/emulator build flags and are not present in release builds. There is no obvious security vulnerability introduced, but the change touches low-level display code and removes some old Python APIs.
No immediate action required. Treat as routine refactor. If reviewing further, verify that `display_record_start` correctly rejects or truncates over-long target_dir values and that the new automatic refresh in `ui_layout_paint` does not introduce race conditions or unexpected re-entrancy in debug builds.
Security signals we found
Removal of Python-level display.bar() and display.refresh() reduces MicroPython attack surface for direct display manipulation
New C code copies an untrusted-length directory path into a fixed 256-byte buffer, but uses MIN() to bound the copy; no obvious buffer overflow
Recording logic is emulator-only (#ifdef TREZOR_EMULATOR) and debug-overlay feature is not enabled in production builds (PYOPT gated)
Refresh is now automatically called from Rust paint path, which could change timing/behavior of display updates in debug builds
No input validation on refresh_index used in snprintf filename construction beyond the caller
Evidence from the diff
The commit rewrites the debug overlay (a small red indicator shown during emulator screen recording) in Rust and consolidates screenshot recording under a new display_record_start/stop C/Rust API. It adds ui_debug_overlay feature flags, removes Display.refresh() and Display.bar() from the MicroPython trezorui module, and moves refresh triggering into the Rust ui_layout_paint function. All new recording/overlay code is conditionally compiled with TREZOR_EMULATOR or ui_debug_overlay and is disabled in optimized/production firmware builds.
Changed components
core/embed/io/display/display_utils.ccore/embed/io/display/unix/display_driver.ccore/embed/rust/src/ui/display/mod.rscore/embed/rust/src/ui/layout/obj.rscore/embed/rust/src/ui/shape/display/*core/embed/upymod/modtrezorui/modtrezorui-display.hcore/src/apps/debug/__init__.pycore/src/trezor/ui/__init__.pyInspect captured patch +311 / −155
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 54d7541f..8193f451 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -866,6 +866,9 @@ if EVERYTHING:
features.append('universal_fw')
if UI_PERFORMANCE_OVERLAY:
features.append('ui_performance_overlay')
+if PYOPT:
+ features.append('ui_debug_overlay')
+
rust = tools.add_rust_lib(
env=env,
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 7164ca69..c39ba896 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -885,6 +885,7 @@ features = ['micropython', 'protobuf', 'ui', 'translations'] + FEATURES_AVAILABL
if PYOPT == '0':
features.append('debug')
features.append('ui_debug')
+ features.append('ui_debug_overlay')
if EVERYTHING:
features.append('universal_fw')
diff --git a/core/embed/io/display/display_utils.c b/core/embed/io/display/display_utils.c
index 92df2dde..c2e7384b 100644
--- a/core/embed/io/display/display_utils.c
+++ b/core/embed/io/display/display_utils.c
@@ -34,3 +34,72 @@ void display_fade(int start, int end, int delay) {
display_set_backlight(end);
#endif
}
+
+#ifdef TREZOR_EMULATOR
+
+extern void display_clear_save(void);
+
+typedef struct {
+ // Screen recording status
+ bool recording;
+ uint8_t target_directory[256];
+ int refresh_index;
+
+} display_recording_t;
+
+static display_recording_t g_display_recording = {0};
+
+void display_record_start(uint8_t *target_dir, size_t target_dir_len,
+ int refresh_index) {
+ display_recording_t *rec = &g_display_recording;
+
+ rec->recording = true;
+
+ if (strlen((char *)rec->target_directory) != strlen((char *)target_dir) ||
+ strncmp((char *)target_dir, (char *)rec->target_directory,
+ target_dir_len) != 0) {
+ // If the target directory is not set, we assume the recording is not
+ // started yet.
+ display_clear_save();
+ }
+
+ memset(rec->target_directory, 0, sizeof(rec->target_directory));
+ memcpy(rec->target_directory, target_dir,
+ MIN(sizeof(rec->target_directory), target_dir_len));
+ rec->refresh_index = refresh_index;
+}
+
+void display_record_stop(void) {
+ display_recording_t *rec = &g_display_recording;
+ rec->recording = false;
+ display_clear_save();
+}
+
+bool display_is_recording(void) {
+ display_recording_t *rec = &g_display_recording;
+
+ return rec->recording;
+}
+
+void display_record_screen(void) {
+ display_recording_t *rec = &g_display_recording;
+
+ if (!rec->recording) {
+ return;
+ }
+
+ char prefix[512];
+ snprintf(prefix, sizeof(prefix), "%s/refresh%02d-", rec->target_directory,
+ rec->refresh_index);
+
+ display_save(prefix);
+}
+
+#else
+void display_record_start(uint8_t *target_dir, size_t target_dir_len,
+ int refresh_index) {}
+void display_record_stop(void) {}
+bool display_is_recording(void) { return false; }
+void display_record_screen(void) {}
+
+#endif
diff --git a/core/embed/io/display/inc/io/display.h b/core/embed/io/display/inc/io/display.h
index afb5252f..264d4e64 100644
--- a/core/embed/io/display/inc/io/display.h
+++ b/core/embed/io/display/inc/io/display.h
@@ -157,6 +157,6 @@ void display_copy_mono1p(const gfx_bitblt_t *bb);
#ifdef TREZOR_EMULATOR
// Save the screen content to a file.
// The function is available only on the emulator.
-const char *display_save(const char *prefix);
-void display_clear_save(void);
+void display_save(const char *prefix);
+
#endif
diff --git a/core/embed/io/display/inc/io/display_utils.h b/core/embed/io/display/inc/io/display_utils.h
index dd1383e8..810a749e 100644
--- a/core/embed/io/display/inc/io/display_utils.h
+++ b/core/embed/io/display/inc/io/display_utils.h
@@ -17,9 +17,42 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef LIB_DISPLAY_UTILS_H
-#define LIB_DISPLAY_UTILS_H
+#pragma once
+/**
+ * @brief Performs a fade effect on the display backlight
+ * @param start Starting backlight level (0-255)
+ * @param end Target backlight level (0-255)
+ * @param delay Total duration of the fade effect in milliseconds
+ */
void display_fade(int start, int end, int delay);
-#endif // LIB_DISPLAY_UTILS_H
+/**
+ * @brief Starts recording the display output to files
+ * @param target_dir Directory where the screen captures will be saved
+ * @param target_dir_len Length of the target directory path
+ * @param refresh_index Index used for the refresh sequence in filenames
+ * @note Only available in emulator builds
+ */
+void display_record_start(uint8_t *target_dir, size_t target_dir_len,
+ int refresh_index);
+
+/**
+ * @brief Stops the display recording
+ * @note Only available in emulator builds
+ */
+void display_record_stop(void);
+
+/**
+ * @brief Checks if the display recording is currently active
+ * @return true if recording is in progress, false otherwise
+ * @note Only available in emulator builds
+ */
+bool display_is_recording(void);
+
+/**
+ * @brief Captures and saves the current screen content
+ * @details Saves the current screen content to a file if recording is active
+ * @note Only available in emulator builds
+ */
+void display_record_screen(void);
diff --git a/core/embed/io/display/unix/display_driver.c b/core/embed/io/display/unix/display_driver.c
index 7b3d172e..e6101169 100644
--- a/core/embed/io/display/unix/display_driver.c
+++ b/core/embed/io/display/unix/display_driver.c
@@ -518,11 +518,11 @@ void display_copy_mono1p(const gfx_bitblt_t *bb) {
#endif
-const char *display_save(const char *prefix) {
+void display_save(const char *prefix) {
display_driver_t *drv = &g_display_driver;
if (!drv->initialized) {
- return NULL;
+ return;
}
#ifdef DISPLAY_MONO
@@ -543,7 +543,7 @@ const char *display_save(const char *prefix) {
if (memcmp(drv->prev_saved->pixels, crop->pixels, crop->pitch * crop->h) ==
0) {
SDL_FreeSurface(crop);
- return filename;
+ return;
}
SDL_FreeSurface(drv->prev_saved);
}
@@ -551,7 +551,6 @@ const char *display_save(const char *prefix) {
snprintf(filename, sizeof(filename), "%s%08d.png", prefix, count++);
IMG_SavePNG(crop, filename);
drv->prev_saved = crop;
- return filename;
}
void display_clear_save(void) {
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index 38039198..e85bef2b 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -22,6 +22,7 @@ display_rgb565 = ["ui_antialiasing"]
display_rgba8888 = ["ui_antialiasing"]
ui_debug = []
ui_performance_overlay = []
+ui_debug_overlay = []
ui_antialiasing = []
ui_blurring = []
ui_image_buffer = []
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 431397ea..f3d95cce 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -367,6 +367,8 @@ fn generate_trezorhal_bindings() {
.allowlist_function("display_get_frame_buffer")
.allowlist_function("display_fill")
.allowlist_function("display_copy_rgb565")
+ .allowlist_function("display_is_recording")
+ .allowlist_function("display_record_screen")
// gfx_bitblt
.allowlist_type("gfx_bitblt_t")
.allowlist_function("gfx_rgb565_fill")
diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs
index 5663a5b4..c88958c6 100644
--- a/core/embed/rust/src/trezorhal/display.rs
+++ b/core/embed/rust/src/trezorhal/display.rs
@@ -27,6 +27,16 @@ pub fn refresh() {
}
}
+pub fn is_recording() -> bool {
+ unsafe { ffi::display_is_recording() }
+}
+
+pub fn record_screen() {
+ unsafe {
+ ffi::display_record_screen();
+ }
+}
+
#[cfg(feature = "framebuffer")]
pub fn get_frame_buffer() -> Option<(&'static mut [u8], usize)> {
let mut fb_info = ffi::display_fb_info_t {
diff --git a/core/embed/rust/src/ui/display/mod.rs b/core/embed/rust/src/ui/display/mod.rs
index 3c7fe9e9..efe03a6b 100644
--- a/core/embed/rust/src/ui/display/mod.rs
+++ b/core/embed/rust/src/ui/display/mod.rs
@@ -121,5 +121,10 @@ pub fn sync() {
}
pub fn refresh() {
+ #[cfg(feature = "ui_debug_overlay")]
+ if display::is_recording() {
+ display::record_screen();
+ }
+
display::refresh();
}
diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs
index f5d13032..fbbca4fa 100644
--- a/core/embed/rust/src/ui/layout/obj.rs
+++ b/core/embed/rust/src/ui/layout/obj.rs
@@ -632,6 +632,9 @@ extern "C" fn ui_layout_paint(this: Obj) -> Obj {
let block = || {
let this: Gc<LayoutObj> = this.try_into()?;
let painted = this.inner_mut().obj_paint_if_requested();
+ if painted? {
+ display::refresh();
+ }
Ok(painted?.into())
};
unsafe { util::try_or_raise(block) }
diff --git a/core/embed/rust/src/ui/layout_bolt/mod.rs b/core/embed/rust/src/ui/layout_bolt/mod.rs
index 35dc920b..d466f46b 100644
--- a/core/embed/rust/src/ui/layout_bolt/mod.rs
+++ b/core/embed/rust/src/ui/layout_bolt/mod.rs
@@ -1,7 +1,13 @@
use super::{geometry::Rect, CommonUI};
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
+use super::shape;
+
+#[cfg(feature = "ui_debug_overlay")]
+use super::{display::Color, geometry::Offset};
+
#[cfg(feature = "ui_performance_overlay")]
-use super::{shape, PerformanceOverlay};
+use super::PerformanceOverlay;
#[cfg(feature = "bootloader")]
pub mod bootloader;
@@ -82,6 +88,17 @@ impl CommonUI for UIBolt {
fn screen_update() {}
+ #[cfg(feature = "ui_debug_overlay")]
+ fn render_debug_overlay<'s>(target: &mut impl shape::Renderer<'s>) {
+ const RECT_SIZE: i16 = constant::SCREEN.width() / 30;
+ let r = Rect::from_top_left_and_size(
+ Self::SCREEN.top_right() - Offset::x(RECT_SIZE),
+ Offset::new(RECT_SIZE, RECT_SIZE),
+ );
+ shape::Bar::new(r)
+ .with_bg(Color::rgb(0xff, 0, 0))
+ .render(target);
+ }
#[cfg(feature = "ui_performance_overlay")]
fn render_performance_overlay<'s>(
_target: &mut impl shape::Renderer<'s>,
diff --git a/core/embed/rust/src/ui/layout_caesar/mod.rs b/core/embed/rust/src/ui/layout_caesar/mod.rs
index 1fbd828f..db2e5c63 100644
--- a/core/embed/rust/src/ui/layout_caesar/mod.rs
+++ b/core/embed/rust/src/ui/layout_caesar/mod.rs
@@ -1,6 +1,13 @@
use super::{geometry::Rect, CommonUI};
+
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
+use super::shape;
+
+#[cfg(feature = "ui_debug_overlay")]
+use super::{display::Color, geometry::Offset};
+
#[cfg(feature = "ui_performance_overlay")]
-use super::{shape, PerformanceOverlay};
+use super::PerformanceOverlay;
#[cfg(feature = "bootloader")]
pub mod bootloader;
@@ -40,6 +47,18 @@ impl CommonUI for UICaesar {
fn screen_update() {}
+ #[cfg(feature = "ui_debug_overlay")]
+ fn render_debug_overlay<'s>(target: &mut impl shape::Renderer<'s>) {
+ const RECT_SIZE: i16 = constant::SCREEN.width() / 30;
+ let r = Rect::from_top_left_and_size(
+ Self::SCREEN.top_right() - Offset::x(RECT_SIZE),
+ Offset::new(RECT_SIZE, RECT_SIZE),
+ );
+ shape::Bar::new(r)
+ .with_bg(Color::rgb(0xff, 0, 0))
+ .render(target);
+ }
+
#[cfg(feature = "ui_performance_overlay")]
fn render_performance_overlay<'s>(
_target: &mut impl shape::Renderer<'s>,
diff --git a/core/embed/rust/src/ui/layout_delizia/mod.rs b/core/embed/rust/src/ui/layout_delizia/mod.rs
index 3ec9545c..76d044d5 100644
--- a/core/embed/rust/src/ui/layout_delizia/mod.rs
+++ b/core/embed/rust/src/ui/layout_delizia/mod.rs
@@ -1,10 +1,12 @@
use super::{geometry::Rect, CommonUI};
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
+use super::{display::Color, geometry::Offset, shape};
+
#[cfg(feature = "ui_performance_overlay")]
use super::{
- display::Color,
- geometry::{Alignment, Alignment2D, Offset, Point},
- shape, PerformanceOverlay,
+ geometry::{Alignment, Alignment2D, Point},
+ PerformanceOverlay,
};
#[cfg(feature = "ui_performance_overlay")]
@@ -88,6 +90,18 @@ impl CommonUI for UIDelizia {
fn screen_update() {}
+ #[cfg(feature = "ui_debug_overlay")]
+ fn render_debug_overlay<'s>(target: &mut impl shape::Renderer<'s>) {
+ const RECT_SIZE: i16 = constant::SCREEN.width() / 30;
+ let r = Rect::from_top_left_and_size(
+ Self::SCREEN.top_right() - Offset::x(RECT_SIZE),
+ Offset::new(RECT_SIZE, RECT_SIZE),
+ );
+ shape::Bar::new(r)
+ .with_bg(Color::rgb(0xff, 0, 0))
+ .render(target);
+ }
+
#[cfg(feature = "ui_performance_overlay")]
fn render_performance_overlay<'s>(
target: &mut impl shape::Renderer<'s>,
diff --git a/core/embed/rust/src/ui/layout_eckhart/mod.rs b/core/embed/rust/src/ui/layout_eckhart/mod.rs
index 2603c75a..212036bb 100644
--- a/core/embed/rust/src/ui/layout_eckhart/mod.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/mod.rs
@@ -1,11 +1,13 @@
use super::{geometry::Rect, CommonUI};
use theme::backlight;
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
+use super::{display::Color, geometry::Offset, shape};
+
#[cfg(feature = "ui_performance_overlay")]
use super::{
- display::Color,
- geometry::{Alignment, Alignment2D, Offset, Point},
- shape, PerformanceOverlay,
+ geometry::{Alignment, Alignment2D, Point},
+ PerformanceOverlay,
};
#[cfg(feature = "ui_performance_overlay")]
@@ -101,6 +103,18 @@ impl CommonUI for UIEckhart {
show(&mut screen, true);
}
+ #[cfg(feature = "ui_debug_overlay")]
+ fn render_debug_overlay<'s>(target: &mut impl shape::Renderer<'s>) {
+ const RECT_SIZE: i16 = 15;
+ let r = Rect::from_top_left_and_size(
+ Self::SCREEN.top_right() - Offset::new(10 + RECT_SIZE, -10),
+ Offset::new(RECT_SIZE, RECT_SIZE),
+ );
+ shape::Bar::new(r)
+ .with_bg(Color::rgb(0xff, 0, 0))
+ .render(target);
+ }
+
#[cfg(feature = "ui_performance_overlay")]
fn render_performance_overlay<'s>(
target: &mut impl shape::Renderer<'s>,
diff --git a/core/embed/rust/src/ui/shape/display/fb_mono8.rs b/core/embed/rust/src/ui/shape/display/fb_mono8.rs
index 7e8cd6c1..1f3219b4 100644
--- a/core/embed/rust/src/ui/shape/display/fb_mono8.rs
+++ b/core/embed/rust/src/ui/shape/display/fb_mono8.rs
@@ -6,6 +6,9 @@ use crate::ui::{
},
};
+#[cfg(feature = "ui_debug_overlay")]
+use crate::ui::{CommonUI, ModelUI};
+
use crate::trezorhal::display;
use static_alloc::Bump;
@@ -61,5 +64,13 @@ where
let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache));
func(&mut target);
+
+ #[cfg(feature = "ui_debug_overlay")]
+ {
+ // In debug mode, render the debug overlay.
+ if !display::is_recording() {
+ ModelUI::render_debug_overlay(&mut target);
+ }
+ }
}
}
diff --git a/core/embed/rust/src/ui/shape/display/fb_rgb565.rs b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs
index 3f1282a7..f38693fa 100644
--- a/core/embed/rust/src/ui/shape/display/fb_rgb565.rs
+++ b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs
@@ -10,11 +10,11 @@ use crate::{
},
};
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
+use crate::ui::{CommonUI, ModelUI};
+
#[cfg(feature = "ui_performance_overlay")]
-use crate::{
- trezorhal::time,
- ui::{CommonUI, ModelUI, PerformanceOverlay},
-};
+use crate::{trezorhal::time, ui::PerformanceOverlay};
use super::bumps;
@@ -73,6 +73,15 @@ where
let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache));
+ #[cfg(all(feature = "ui_debug_overlay", not(feature = "ui_performance_overlay")))]
+ {
+ func(&mut target);
+ // In debug mode, render the debug overlay.
+ if !display::is_recording() {
+ ModelUI::render_debug_overlay(&mut target);
+ }
+ }
+
// In debug mode, measure the time spent on rendering.
#[cfg(feature = "ui_performance_overlay")]
{
@@ -84,8 +93,11 @@ where
ModelUI::render_performance_overlay(&mut target, info);
}
- // In production, just execute the drawing function without timing.
- #[cfg(not(feature = "ui_performance_overlay"))]
+ // In production, just execute the drawing function without overlays.
+ #[cfg(all(
+ not(feature = "ui_debug_overlay"),
+ not(feature = "ui_performance_overlay")
+ ))]
{
func(&mut target);
}
diff --git a/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs
index d09f89be..d07ffa3c 100644
--- a/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs
+++ b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs
@@ -1,3 +1,5 @@
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
+use crate::ui::{CommonUI, ModelUI};
use crate::{
trezorhal::display,
ui::{
@@ -11,10 +13,7 @@ use crate::{
};
#[cfg(feature = "ui_performance_overlay")]
-use crate::{
- trezorhal::time,
- ui::{CommonUI, ModelUI, PerformanceOverlay},
-};
+use crate::{trezorhal::time, ui::PerformanceOverlay};
use super::bumps;
@@ -73,6 +72,15 @@ where
let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache));
+ #[cfg(all(feature = "ui_debug_overlay", not(feature = "ui_performance_overlay")))]
+ {
+ func(&mut target);
+ // In debug mode, render the debug overlay.
+ if !display::is_recording() {
+ ModelUI::render_debug_overlay(&mut target);
+ }
+ }
+
// In debug mode, measure the time spent on rendering.
#[cfg(feature = "ui_performance_overlay")]
{
@@ -84,8 +92,11 @@ where
ModelUI::render_performance_overlay(&mut target, info);
}
- // In production, just execute the drawing function without timing.
- #[cfg(not(feature = "ui_performance_overlay"))]
+ // In production, just execute the drawing function without overlays.
+ #[cfg(all(
+ not(feature = "ui_debug_overlay"),
+ not(feature = "ui_performance_overlay")
+ ))]
{
func(&mut target);
}
diff --git a/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs b/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs
index 36dc2f43..153b37b7 100644
--- a/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs
+++ b/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs
@@ -6,6 +6,9 @@ use crate::{
ui::shape::render::ScopedRenderer,
};
+#[cfg(feature = "ui_debug_overlay")]
+use crate::ui::{CommonUI, ModelUI};
+
use crate::ui::{
display::Color,
geometry::{Offset, Rect},
@@ -52,6 +55,14 @@ where
func(&mut target);
+ #[cfg(feature = "ui_debug_overlay")]
+ {
+ // In debug mode, render the debug overlay.
+ if !display::is_recording() {
+ ModelUI::render_debug_overlay(&mut target);
+ }
+ }
+
target.into_inner().render(16);
});
}
diff --git a/core/embed/rust/src/ui/ui_common.rs b/core/embed/rust/src/ui/ui_common.rs
index 5b1ece59..08e61de7 100644
--- a/core/embed/rust/src/ui/ui_common.rs
+++ b/core/embed/rust/src/ui/ui_common.rs
@@ -1,6 +1,6 @@
use crate::ui::geometry::Rect;
-#[cfg(feature = "ui_performance_overlay")]
+#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
use crate::ui::shape::Renderer;
/// A structure containing information to be displayed in the debug overlay
@@ -46,6 +46,10 @@ pub trait CommonUI {
fn screen_update();
+ /// Renders an overlay over the screen content with debug indication
+ #[cfg(feature = "ui_debug_overlay")]
+ fn render_debug_overlay<'s>(target: &mut impl Renderer<'s>);
+
/// Renders a partially transparent overlay over the screen content
/// using data from the `PerformanceOverlay` struct.
#[cfg(feature = "ui_performance_overlay")]
diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h
index 97664eea..6d332330 100644
--- a/core/embed/rust/trezorhal.h
+++ b/core/embed/rust/trezorhal.h
@@ -4,6 +4,7 @@
#include <gfx/gfx_bitblt.h>
#include <io/display.h>
+#include <io/display_utils.h>
#include <io/usb.h>
#include <rtl/secbool.h>
#include <sec/entropy.h>
diff --git a/core/embed/upymod/modtrezorui/modtrezorui-display.h b/core/embed/upymod/modtrezorui/modtrezorui-display.h
index b63a635d..9bd6758b 100644
--- a/core/embed/upymod/modtrezorui/modtrezorui-display.h
+++ b/core/embed/upymod/modtrezorui/modtrezorui-display.h
@@ -19,8 +19,8 @@
#include <trezor_model.h>
-#include <gfx/gfx_draw.h>
#include <io/display.h>
+#include <io/display_utils.h>
/// class Display:
/// """
@@ -45,34 +45,6 @@ STATIC mp_obj_t mod_trezorui_Display_make_new(const mp_obj_type_t *type,
return MP_OBJ_FROM_PTR(o);
}
-/// def refresh(self) -> None:
-/// """
-/// Refresh display (update screen).
-/// """
-STATIC mp_obj_t mod_trezorui_Display_refresh(mp_obj_t self) {
- display_refresh();
- return mp_const_none;
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_refresh_obj,
- mod_trezorui_Display_refresh);
-
-/// def bar(self, x: int, y: int, w: int, h: int, color: int) -> None:
-/// """
-/// Renders a bar at position (x,y = upper left corner) with width w and
-/// height h of color color.
-/// """
-STATIC mp_obj_t mod_trezorui_Display_bar(size_t n_args, const mp_obj_t *args) {
- mp_int_t x = mp_obj_get_int(args[1]);
- mp_int_t y = mp_obj_get_int(args[2]);
- mp_int_t w = mp_obj_get_int(args[3]);
- mp_int_t h = mp_obj_get_int(args[4]);
- uint16_t c = mp_obj_get_int(args[5]);
- gfx_draw_bar(gfx_rect(x, y, w, h), gfx_color16_to_color(c));
- return mp_const_none;
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_bar_obj, 6, 6,
- mod_trezorui_Display_bar);
-
/// def orientation(self, degrees: int | None = None) -> int:
/// """
/// Sets display orientation to 0, 90, 180 or 270 degrees.
@@ -97,46 +69,45 @@ STATIC mp_obj_t mod_trezorui_Display_orientation(size_t n_args,
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_orientation_obj,
1, 2,
mod_trezorui_Display_orientation);
-
-/// def save(self, prefix: str) -> None:
+/// def record_start(self, target_directory: bytes, refresh_index: int) -> None:
/// """
-/// Saves current display contents to PNG file with given prefix.
+/// Starts screen recording with specified target directory and refresh
+/// index.
/// """
-STATIC mp_obj_t mod_trezorui_Display_save(mp_obj_t self, mp_obj_t prefix) {
+STATIC mp_obj_t mod_trezorui_Display_record_start(mp_obj_t self,
+ mp_obj_t target_directory,
+ mp_obj_t refresh_index) {
#ifdef TREZOR_EMULATOR
- mp_buffer_info_t pfx = {0};
- mp_get_buffer_raise(prefix, &pfx, MP_BUFFER_READ);
- if (pfx.len > 0) {
- display_save(pfx.buf);
- }
+ mp_buffer_info_t target_dir;
+ mp_int_t refresh_idx = mp_obj_get_int(refresh_index);
+ mp_get_buffer_raise(target_directory, &target_dir, MP_BUFFER_READ);
+ display_record_start(target_dir.buf, target_dir.len, refresh_idx);
#endif
return mp_const_none;
}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj,
- mod_trezorui_Display_save);
+STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorui_Display_record_start_obj,
+ mod_trezorui_Display_record_start);
-/// def clear_save(self) -> None:
+/// def record_stop(self) -> None:
/// """
-/// Clears buffers in display saving.
+/// Stops screen recording.
/// """
-STATIC mp_obj_t mod_trezorui_Display_clear_save(mp_obj_t self) {
+STATIC mp_obj_t mod_trezorui_Display_record_stop(mp_obj_t self) {
#ifdef TREZOR_EMULATOR
- display_clear_save();
+ display_record_stop();
#endif
return mp_const_none;
}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_clear_save_obj,
- mod_trezorui_Display_clear_save);
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_record_stop_obj,
+ mod_trezorui_Display_record_stop);
STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
- {MP_ROM_QSTR(MP_QSTR_refresh),
- MP_ROM_PTR(&mod_trezorui_Display_refresh_obj)},
- {MP_ROM_QSTR(MP_QSTR_bar), MP_ROM_PTR(&mod_trezorui_Display_bar_obj)},
{MP_ROM_QSTR(MP_QSTR_orientation),
MP_ROM_PTR(&mod_trezorui_Display_orientation_obj)},
- {MP_ROM_QSTR(MP_QSTR_save), MP_ROM_PTR(&mod_trezorui_Display_save_obj)},
- {MP_ROM_QSTR(MP_QSTR_clear_save),
- MP_ROM_PTR(&mod_trezorui_Display_clear_save_obj)},
+ {MP_ROM_QSTR(MP_QSTR_record_start),
+ MP_ROM_PTR(&mod_trezorui_Display_record_start_obj)},
+ {MP_ROM_QSTR(MP_QSTR_record_stop),
+ MP_ROM_PTR(&mod_trezorui_Display_record_stop_obj)},
{MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(DISPLAY_RESX)},
{MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(DISPLAY_RESY)},
};
diff --git a/core/mocks/generated/trezorui.pyi b/core/mocks/generated/trezorui.pyi
index b8941a36..c20d76a1 100644
--- a/core/mocks/generated/trezorui.pyi
+++ b/core/mocks/generated/trezorui.pyi
@@ -14,17 +14,6 @@ class Display:
Initialize the display.
"""
- def refresh(self) -> None:
- """
- Refresh display (update screen).
- """
-
- def bar(self, x: int, y: int, w: int, h: int, color: int) -> None:
- """
- Renders a bar at position (x,y = upper left corner) with width w and
- height h of color color.
- """
-
def orientation(self, degrees: int | None = None) -> int:
"""
Sets display orientation to 0, 90, 180 or 270 degrees.
@@ -33,12 +22,13 @@ class Display:
value.
"""
- def save(self, prefix: str) -> None:
+ def record_start(self, target_directory: bytes, refresh_index: int) -> None:
"""
- Saves current display contents to PNG file with given prefix.
+ Starts screen recording with specified target directory and refresh
+ index.
"""
- def clear_save(self) -> None:
+ def record_stop(self) -> None:
"""
- Clears buffers in display saving.
+ Stops screen recording.
"""
diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py
index 9248c2d2..fc867511 100644
--- a/core/src/apps/debug/__init__.py
+++ b/core/src/apps/debug/__init__.py
@@ -47,16 +47,6 @@ if __debug__:
_DEADLOCK_SLEEP_MS = const(3000)
_DEADLOCK_DETECT_SLEEP = loop.sleep(_DEADLOCK_SLEEP_MS)
- def screenshot() -> bool:
- if storage.save_screen:
- # Starting with "refresh00", allowing for 100 emulator restarts
- # without losing the order of the screenshots based on filename.
- display.save(
- f"{storage.save_screen_directory.decode()}/refresh{storage.refresh_index:0>2}-"
- )
- return True
- return False
-
def notify_layout_change(layout: Layout | None) -> None:
layout_change_box.put(layout, replace=True)
@@ -355,9 +345,7 @@ if __debug__:
# In case emulator is restarted but we still want to record screenshots
# into the same directory as before, we need to increment the refresh index,
# so that the screenshots are not overwritten.
- storage.refresh_index = msg.refresh_index
- storage.save_screen_directory[:] = msg.target_directory.encode()
- storage.save_screen = True
+ display.record_start(msg.target_directory.encode(), msg.refresh_index)
# force repaint current layout, in order to take an initial screenshot
# (doing it this way also clears the red square, because the repaint is
@@ -367,8 +355,8 @@ if __debug__:
ui.CURRENT_LAYOUT._paint()
else:
- storage.save_screen = False
- display.clear_save() # clear C buffers
+ print("stopping recording")
+ display.record_stop()
return Success()
diff --git a/core/src/storage/debug.py b/core/src/storage/debug.py
index a4b80e4d..e573aab0 100644
--- a/core/src/storage/debug.py
+++ b/core/src/storage/debug.py
@@ -1,15 +1,9 @@
-from trezorutils import EMULATOR, halt
+from trezorutils import halt
if not __debug__:
halt("Debugging is disabled")
if __debug__:
- save_screen = False
- if EMULATOR:
- refresh_index = 0
- save_screen_directory = bytearray(4096)
- save_screen_directory[:] = b"."
-
layout_watcher = False
reset_internal_entropy = bytearray(32)
diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py
index fd31309b..55129fc8 100644
--- a/core/src/trezor/ui/__init__.py
+++ b/core/src/trezor/ui/__init__.py
@@ -52,20 +52,6 @@ See `trezor::ui::layout::base::EventCtx::ANIM_FRAME_TIMER`.
# allow only one alert at a time to avoid alerts overlapping
_alert_in_progress = False
-# in debug mode, display an indicator in top right corner
-if __debug__:
-
- def refresh() -> None:
- from apps.debug import screenshot
-
- if not screenshot():
- side = Display.WIDTH // 30
- display.bar(Display.WIDTH - side, 0, side, side, 0xF800)
- display.refresh()
-
-else:
- refresh = display.refresh
-
async def _alert(count: int) -> None:
short_sleep = loop.sleep(20)
@@ -324,8 +310,6 @@ class Layout(Generic[T]):
import storage.cache as storage_cache
painted = self.layout.paint()
- if painted:
- refresh()
if storage_cache.homescreen_shown is not None and painted:
storage_cache.homescreen_shown = None
@@ -566,8 +550,7 @@ class ProgressLayout:
def do_progress_event(val: int) -> None:
msg = self.layout.progress_event(val, description or "")
assert msg is None
- if self.layout.paint():
- refresh()
+ self.layout.paint()
# animate the progress bar in a blocking fashion
step = min(self.progress_step, max(value - self.value, 1))
@@ -589,9 +572,7 @@ class ProgressLayout:
set_current_layout(self)
self.layout.request_complete_repaint()
- painted = self.layout.paint()
- if painted:
- refresh()
+ self.layout.paint()
backlight_fade(BacklightLevels.NORMAL)
def stop(self) -> None:
diff --git a/core/src/trezor/ui/layouts/bolt/fido.py b/core/src/trezor/ui/layouts/bolt/fido.py
index 1671186f..c8b1fbda 100644
--- a/core/src/trezor/ui/layouts/bolt/fido.py
+++ b/core/src/trezor/ui/layouts/bolt/fido.py
@@ -28,11 +28,9 @@ async def confirm_fido(
from trezor import io
confirm.touch_event(io.TOUCH_START, 220, 220)
- if confirm.paint():
- ui.refresh()
+ confirm.paint()
msg = confirm.touch_event(io.TOUCH_END, 220, 220)
- if confirm.paint():
- ui.refresh()
+ confirm.paint()
assert msg is trezorui_api.LayoutState.DONE
retval = confirm.return_value()
assert isinstance(retval, int)
diff --git a/core/src/trezor/ui/layouts/homescreen.py b/core/src/trezor/ui/layouts/homescreen.py
index 33ae56c8..a9f16a46 100644
--- a/core/src/trezor/ui/layouts/homescreen.py
+++ b/core/src/trezor/ui/layouts/homescreen.py
@@ -49,8 +49,7 @@ class HomescreenBase(ui.Layout):
return storage_cache.homescreen_shown is self.RENDER_INDICATOR
def _paint(self) -> None:
- if self.layout.paint():
- ui.refresh()
+ self.layout.paint()
def _first_paint(self) -> None:
if not self.should_resume:
diff --git a/core/tests/test_trezor.ui.display.py b/core/tests/test_trezor.ui.display.py
index 55c7502a..feefbfdf 100644
--- a/core/tests/test_trezor.ui.display.py
+++ b/core/tests/test_trezor.ui.display.py
@@ -6,11 +6,6 @@ from trezorui_api import backlight_set
class TestDisplay(unittest.TestCase):
- def test_refresh(self):
- display.refresh()
-
- def test_bar(self):
- display.bar(0, 0, 10, 10, 0xFFFF)
def test_orientation(self):
for o in [0, 90, 180, 270]:
diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json
index 9688016f..d24ac06b 100644
--- a/tests/ui_tests/fixtures.json
+++ b/tests/ui_tests/fixtures.json
@@ -10082,7 +10082,7 @@
"T2T1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "96009f5c0834cb4fcfe266bfd97309c0280a4cd053f906359021c4d8c78a3e63",
"T2T1_en_test_shamir_persistence.py::test_recovery_on_old_wallet": "41e408617bb1d314602e03cc1d3bcdf2064096b99d08e9a07220dbd9347a81d3",
"T2T1_en_test_shamir_persistence.py::test_recovery_single_reset": "1cefe6558c3dfcbbbda25a28441d289e313fe76fdd76227d719ec34e646dd149",
-"T2T1_en_test_wipe_code.py::test_wipe_code_activate_core": "e74e10959d441313fa31636e26fd4c8dd1e2cc3c6566d55920a146b89254de63"
+"T2T1_en_test_wipe_code.py::test_wipe_code_activate_core": "d687f07093ba79325674f52e5698ccd9845a15440869c915a7231e32099672ec"
}
},
"T3B1": {
@@ -18992,7 +18992,7 @@
"T3B1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "65ca98ff5d20a49aaa579e05fbe6b8509f6e27e65a0406c90d87a6ac1282c6b9",
"T3B1_en_test_shamir_persistence.py::test_recovery_on_old_wallet": "f3543f0eef741ee0049417583ad5d28d45dd3a28a881e76d157f19b03c553a84",
"T3B1_en_test_shamir_persistence.py::test_recovery_single_reset": "a63babaf9d891c50b850106da2fc986a2d9f6a28f70a47e041385f01709f7804",
-"T3B1_en_test_wipe_code.py::test_wipe_code_activate_core": "1d0166d3dde6a1295ad9d4c488207e50ab786a236e6a4e837b00a29630a13f3a"
+"T3B1_en_test_wipe_code.py::test_wipe_code_activate_core": "932a10c15bf87bf7cb617965e1ad4bef4cbf97d861d4aa33ea4870b730f13143"
}
},
"T3T1": {
@@ -28158,7 +28158,7 @@
"T3T1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "83ec37ee3f6f09a73a1b4364e71dfec465f29b9c586af127c67bd1575465a1d2",
"T3T1_en_test_shamir_persistence.py::test_recovery_on_old_wallet": "5e10c2031a47ebce57c927754821b7551edf7a1712186c4205b806ae1926598b",
"T3T1_en_test_shamir_persistence.py::test_recovery_single_reset": "7c4a57a42733c5da4e0ee5272345f9a6ef4586522e2c2a5961608f3779a11301",
-"T3T1_en_test_wipe_code.py::test_wipe_code_activate_core": "36bda60f9bac32c914f79f3dc2d78f6ece093b79d333685ca03c212e20ef9029"
+"T3T1_en_test_wipe_code.py::test_wipe_code_activate_core": "4c5c188aa81077d4aa77418e379212cb39e6b9b3944d73fdea43d6a2368da701"
}
},
"T3W1": {
@@ -37257,7 +37257,7 @@
"T3W1_en_test_shamir_persistence.py::test_abort": "2ae1fcc550acafcd8ab009c47f4ff31148ea078b30705f8358c23844779ef01e",
"T3W1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "997362a25f733faeb6b39d629995cff990387474eed2f588cfcacee7641b1470",
"T3W1_en_test_shamir_persistence.py::test_recovery_single_reset": "bc5569d3b6055b698faa41e6d8411c4152f48dc0fba62e75252a4ab70f4aefbe",
-"T3W1_en_test_wipe_code.py::test_wipe_code_activate_core": "7b0abafc30f1fca3a72cb4589fade1c95e308f16352299e6ec9c778c50bdc7f9"
+"T3W1_en_test_wipe_code.py::test_wipe_code_activate_core": "8680514feff202976686c21512fb4284c2dd9c0acf27e799430e5ee31005c452"
}
}
}
Why this scored 19/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.