From c3981cdebef82b6b69cf23edf9912667a3b1c8a1 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Fri, 7 Feb 2025 16:02:49 +0100 Subject: [PATCH] refactor(core): render prodtest UI in rust [no changelog] --- core/SConscript.prodtest | 50 +++++-- .../projects/prodtest/cmd/prodtest_display.c | 47 ++---- .../projects/prodtest/cmd/prodtest_prodtest.c | 19 +-- .../projects/prodtest/cmd/prodtest_touch.c | 66 +++------ core/embed/projects/prodtest/main.c | 65 ++------- core/embed/rust/Cargo.toml | 1 + core/embed/rust/rust_ui_prodtest.h | 14 ++ core/embed/rust/src/ui/api/mod.rs | 3 + core/embed/rust/src/ui/api/prodtest_c.rs | 49 +++++++ core/embed/rust/src/ui/layout_bolt/mod.rs | 3 + .../rust/src/ui/layout_bolt/prodtest/mod.rs | 136 ++++++++++++++++++ .../src/ui/layout_bolt/theme/backlight.rs | 10 +- core/embed/rust/src/ui/layout_caesar/mod.rs | 4 + .../rust/src/ui/layout_caesar/prodtest/mod.rs | 115 +++++++++++++++ core/embed/rust/src/ui/mod.rs | 4 + core/embed/rust/src/ui/ui_prodtest.rs | 15 ++ 16 files changed, 428 insertions(+), 173 deletions(-) create mode 100644 core/embed/rust/rust_ui_prodtest.h create mode 100644 core/embed/rust/src/ui/api/prodtest_c.rs create mode 100644 core/embed/rust/src/ui/layout_bolt/prodtest/mod.rs create mode 100644 core/embed/rust/src/ui/layout_caesar/prodtest/mod.rs create mode 100644 core/embed/rust/src/ui/ui_prodtest.rs diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index 9ae208b14a..dc173ac2fa 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -58,7 +58,7 @@ SOURCE_MOD_CRYPTO += [ # modtrezorui CPPPATH_MOD += [ - 'vendor/micropython/lib/uzlib', + 'vendor/micropython/lib/uzlib' ] SOURCE_MOD += [ @@ -138,6 +138,23 @@ env.Replace( env.Replace( TREZOR_MODEL=TREZOR_MODEL, ) +ALLPATHS = [ + 'embed/rust', + 'embed/projects/prodtest', + 'embed/rtl/inc', + 'embed/models', + 'embed/gfx/inc', + 'embed/sys/bsp/inc', + 'embed/util/image/inc', + 'embed/util/rsod/inc', + 'embed/util/scm_revision/inc', + 'embed/util/translations/inc', + 'embed/upymod/modtrezorui', + ] + +ALLPATHS += CPPPATH_MOD + PATH_HAL + + env.Replace( COPT=env.get('ENV').get('OPTIMIZE', '-Os'), CCFLAGS='$COPT ' @@ -149,18 +166,8 @@ env.Replace( '-fstack-protector-all ' + env.get('ENV')["CPU_CCFLAGS"] + CCFLAGS_MOD, CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB', - LINKFLAGS=f'-T build/prodtest/memory.ld -Wl,--gc-sections -Wl,--print-memory-usage -Wl,-Map=build/prodtest/prodtest.map -Wl,--warn-common', - CPPPATH=[ - 'embed/projects/prodtest', - 'embed/rtl/inc', - 'embed/models', - 'embed/gfx/inc', - 'embed/sys/bsp/inc', - 'embed/util/image/inc', - 'embed/util/rsod/inc', - 'embed/util/scm_revision/inc', - 'embed/upymod/modtrezorui', - ] + CPPPATH_MOD + PATH_HAL, + LINKFLAGS=['-Tbuild/prodtest/memory.ld', '-Wl,--gc-sections', '-Wl,--print-memory-usage', '-Wl,-Map=build/prodtest/prodtest.map', '-Wl,--warn-common'], + CPPPATH=ALLPATHS, CPPDEFINES=[ 'TREZOR_PRODTEST', 'TREZOR_MODEL_'+TREZOR_MODEL, @@ -197,6 +204,20 @@ obj_program.extend(env.Object(source=SOURCE_MOD_CRYPTO, CCFLAGS='$CCFLAGS -ftriv obj_program.extend(env.Object(source=SOURCE_PRODTEST)) obj_program.extend(env.Object(source=SOURCE_HAL)) +# +# Rust library +# +features = ['prodtest',] + FEATURES_AVAILABLE + RUST_UI_FEATURES + + +rust = tools.add_rust_lib( + env, + 'prodtest', + 'release', + features, + ALLPATHS, + str(Dir('.').abspath)) + if (vh := ARGUMENTS.get("VENDOR_HEADER", None)): VENDORHEADER = vh @@ -232,10 +253,11 @@ program_elf = env.Command( target='prodtest.elf', source=obj_program, action= - '$LINK -o $TARGET $CCFLAGS $CFLAGS $LINKFLAGS $SOURCES -lc_nano -lgcc -lm', + '$LINK -o $TARGET $CCFLAGS $CFLAGS $SOURCES $LINKFLAGS -lc_nano -lgcc -lm', ) env.Depends(program_elf, linkerscript_gen) +env.Depends(program_elf, rust) BINARY_NAME = f"build/prodtest/prodtest-{TREZOR_MODEL}" BINARY_NAME += "-" + tools.get_version('embed/projects/prodtest/version.h') diff --git a/core/embed/projects/prodtest/cmd/prodtest_display.c b/core/embed/projects/prodtest/cmd/prodtest_display.c index f0a5f9dc36..a09cbf7bb5 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_display.c +++ b/core/embed/projects/prodtest/cmd/prodtest_display.c @@ -17,9 +17,9 @@ * along with this program. If not, see . */ +#include #include -#include #include #include @@ -29,24 +29,14 @@ static void prodtest_display_border(cli_t* cli) { return; } - gfx_clear(); - cli_trace(cli, "Drawing display border..."); - gfx_rect_t r_out = gfx_rect_wh(0, 0, DISPLAY_RESX, DISPLAY_RESY); - gfx_rect_t r_in = gfx_rect_wh(1, 1, DISPLAY_RESX - 2, DISPLAY_RESY - 2); - - gfx_draw_bar(r_out, COLOR_WHITE); - gfx_draw_bar(r_in, COLOR_BLACK); - - display_refresh(); + screen_prodtest_border(); cli_ok(cli, ""); } static void prodtest_display_bars(cli_t* cli) { - gfx_clear(); - const char* colors = cli_arg(cli, "colors"); size_t color_count = strlen(colors); @@ -59,34 +49,13 @@ static void prodtest_display_bars(cli_t* cli) { cli_trace(cli, "Drawing %d vertical bars...", color_count); + screen_prodtest_bars(colors, color_count); + for (size_t i = 0; i < color_count; i++) { - gfx_color_t c = COLOR_BLACK; // black - switch (colors[i]) { - case 'R': - case 'r': - c = gfx_color_rgb(255, 0, 0); - break; - case 'G': - case 'g': - c = gfx_color_rgb(0, 255, 0); - break; - case 'B': - case 'b': - c = gfx_color_rgb(0, 0, 255); - break; - case 'W': - case 'w': - c = COLOR_WHITE; - break; - default: - invalid_color = true; - break; + if (strchr("RGBWrgbw", colors[i]) == NULL) { + invalid_color = true; + break; } - - int x1 = (DISPLAY_RESX * i) / color_count; - int x2 = (DISPLAY_RESX * (i + 1)) / color_count; - - gfx_draw_bar(gfx_rect(x1, 0, x2, DISPLAY_RESY), c); } if (strlen(colors) == 0 || invalid_color) { @@ -102,7 +71,7 @@ static void prodtest_display_set_backlight(cli_t* cli) { uint32_t level = 0; if (!cli_arg_uint32(cli, "level", &level) || level > 255) { - cli_error_arg(cli, "Expecting backlig level in range 0-255 (100%%)."); + cli_error_arg(cli, "Expecting backlight level in range 0-255 (100%%)."); return; } diff --git a/core/embed/projects/prodtest/cmd/prodtest_prodtest.c b/core/embed/projects/prodtest/cmd/prodtest_prodtest.c index 35beec759b..550b0837d4 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_prodtest.c +++ b/core/embed/projects/prodtest/cmd/prodtest_prodtest.c @@ -17,26 +17,15 @@ * along with this program. If not, see . */ +#include #include #include -#include -#include -#include #include -#include -#include -#include #include #include -static gfx_text_attr_t bold = { - .font = FONT_BOLD, - .fg_color = COLOR_WHITE, - .bg_color = COLOR_BLACK, -}; - static void prodtest_prodtest_intro(cli_t* cli) { cli_trace(cli, "Welcome to Trezor %s Production Test Firmware v%d.%d.%d.", MODEL_NAME, VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); @@ -63,10 +52,8 @@ static void prodtest_prodtest_wipe(cli_t* cli) { cli_trace(cli, "Invalidating the production test firmware header..."); firmware_invalidate_header(); - gfx_clear(); - gfx_offset_t pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2 + 10); - gfx_draw_text(pos, "WIPED", -1, &bold, GFX_ALIGN_CENTER); - display_refresh(); + const char msg[] = "WIPED"; + screen_prodtest_show_text(msg, strlen(msg)); cli_ok(cli, ""); } diff --git a/core/embed/projects/prodtest/cmd/prodtest_touch.c b/core/embed/projects/prodtest/cmd/prodtest_touch.c index 4842ec2ae4..75646277b6 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_touch.c +++ b/core/embed/projects/prodtest/cmd/prodtest_touch.c @@ -17,23 +17,16 @@ * along with this program. If not, see . */ +#include #ifdef USE_TOUCH #include -#include -#include #include #include #include #include -const static gfx_text_attr_t bold = { - .font = FONT_BOLD, - .fg_color = COLOR_WHITE, - .bg_color = COLOR_BLACK, -}; - static bool ensure_touch_init(cli_t* cli) { cli_trace(cli, "Initializing the touch controller..."); if (sectrue != touch_init()) { @@ -108,27 +101,24 @@ static void prodtest_touch_test(cli_t* cli) { return; } - const int width = DISPLAY_RESX / 2; - const int height = DISPLAY_RESY / 2; + const int16_t width = DISPLAY_RESX / 2; + const int16_t height = DISPLAY_RESY / 2; - gfx_clear(); switch (position) { case 1: - gfx_draw_bar(gfx_rect_wh(0, 0, width, height), COLOR_WHITE); + screen_prodtest_touch(0, 0, width, height); break; case 2: - gfx_draw_bar(gfx_rect_wh(width, 0, width, height), COLOR_WHITE); + screen_prodtest_touch(width, 0, width, height); break; case 3: - gfx_draw_bar(gfx_rect_wh(width, height, width, height), COLOR_WHITE); + screen_prodtest_touch(width, height, width, height); break; default: - gfx_draw_bar(gfx_rect_wh(0, height, width, height), COLOR_WHITE); + screen_prodtest_touch(0, height, width, height); break; } - display_refresh(); - uint32_t event = 0; if (touch_click_timeout(cli, &event, timeout)) { uint16_t x = touch_unpack_x(event); @@ -140,8 +130,7 @@ static void prodtest_touch_test(cli_t* cli) { } } - gfx_clear(); - display_refresh(); + screen_prodtest_welcome(); } static void prodtest_touch_test_custom(cli_t* cli) { @@ -188,9 +177,7 @@ static void prodtest_touch_test_custom(cli_t* cli) { cli_trace(cli, "Drawing a rectangle at [%d, %d] with size [%d x %d]...", x, y, width, height); - gfx_clear(); - gfx_draw_bar(gfx_rect_wh(x, y, width, height), COLOR_WHITE); - display_refresh(); + screen_prodtest_touch(x, y, width, height); uint32_t expire_time = ticks_timeout(timeout); @@ -226,8 +213,7 @@ static void prodtest_touch_test_custom(cli_t* cli) { } } - gfx_clear(); - display_refresh(); + screen_prodtest_welcome(); } static void prodtest_touch_test_idle(cli_t* cli) { @@ -243,10 +229,8 @@ static void prodtest_touch_test_idle(cli_t* cli) { return; } - gfx_clear(); - gfx_offset_t pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2); - gfx_draw_text(pos, "DON'T TOUCH", -1, &bold, GFX_ALIGN_CENTER); - display_refresh(); + const char msg[] = "DON'T TOUCH"; + screen_prodtest_show_text(msg, strlen(msg)); if (!ensure_touch_init(cli)) { return; @@ -273,8 +257,7 @@ static void prodtest_touch_test_idle(cli_t* cli) { cli_ok(cli, ""); cleanup: - gfx_clear(); - display_refresh(); + screen_prodtest_welcome(); } static void prodtest_touch_test_power(cli_t* cli) { @@ -290,10 +273,8 @@ static void prodtest_touch_test_power(cli_t* cli) { return; } - gfx_clear(); - gfx_offset_t pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2); - gfx_draw_text(pos, "MEASURING", -1, &bold, GFX_ALIGN_CENTER); - display_refresh(); + const char text[] = "MEASURING"; + screen_prodtest_show_text(text, strlen(text)); cli_trace(cli, "Setting touch controller power for %d ms...", timeout); @@ -311,8 +292,7 @@ static void prodtest_touch_test_power(cli_t* cli) { cleanup: touch_power_set(false); - gfx_clear(); - display_refresh(); + screen_prodtest_welcome(); } static void prodtest_touch_test_sensitivity(cli_t* cli) { @@ -338,28 +318,22 @@ static void prodtest_touch_test_sensitivity(cli_t* cli) { cli_trace(cli, "Running touch controller test..."); cli_trace(cli, "Press CTRL+C for exit."); - gfx_clear(); - display_refresh(); - for (;;) { uint32_t evt = touch_get_event(); if (evt & TOUCH_START || evt & TOUCH_MOVE) { int x = touch_unpack_x(evt); int y = touch_unpack_y(evt); - gfx_clear(); - gfx_draw_bar(gfx_rect_wh(x - 48, y - 48, 96, 96), COLOR_WHITE); - display_refresh(); + + screen_prodtest_touch(x - 48, y - 48, 96, 96); } else if (evt & TOUCH_END) { - gfx_clear(); - display_refresh(); + screen_prodtest_touch(0, 0, 0, 0); } if (cli_aborted(cli)) { break; } } - gfx_clear(); - display_refresh(); + screen_prodtest_welcome(); } // clang-format off diff --git a/core/embed/projects/prodtest/main.c b/core/embed/projects/prodtest/main.c index 25a8655532..3dc3546371 100644 --- a/core/embed/projects/prodtest/main.c +++ b/core/embed/projects/prodtest/main.c @@ -20,18 +20,15 @@ #include #include -#include -#include #include -#include #include #include #include -#include -#include #include #include +#include "rust_ui_prodtest.h" + #ifdef USE_BUTTON #include #endif @@ -144,53 +141,17 @@ static void usb_init_all(void) { ensure(usb_start(), NULL); } -static inline gfx_rect_t gfx_rect_shrink(gfx_rect_t r, int padding) { - gfx_rect_t result = { - .x0 = r.x0 + padding, - .y0 = r.y0 + padding, - .x1 = r.x1 - padding, - .y1 = r.y1 - padding, - }; - return result; -} - -static void draw_welcome_screen(void) { - gfx_clear(); - gfx_rect_t r = gfx_rect_wh(0, 0, DISPLAY_RESX, DISPLAY_RESY); - uint8_t qr_scale = 4; - int16_t text_offset = 30; - gfx_text_attr_t bold = { - .font = FONT_BOLD, - .fg_color = COLOR_WHITE, - .bg_color = COLOR_BLACK, - }; - -#if defined TREZOR_MODEL_T2B1 || defined TREZOR_MODEL_T3B1 - gfx_draw_bar(r, COLOR_WHITE); - qr_scale = 2; - text_offset = 9; - bold.fg_color = COLOR_BLACK; - bold.bg_color = COLOR_WHITE; -#else - gfx_draw_bar(gfx_rect_shrink(r, 3), COLOR_WHITE); - gfx_draw_bar(gfx_rect_shrink(r, 4), COLOR_BLACK); -#endif - - char dom[32]; +static void show_welcome_screen(void) { + char dom[32] = {0}; // format: {MODEL_IDENTIFIER}YYMMDD - if (sectrue == flash_otp_read(FLASH_OTP_BLOCK_BATCH, 0, (uint8_t *)dom, 32) && - dom[31] == 0 && cstr_starts_with(dom, MODEL_IDENTIFIER)) { - gfx_offset_t pos; - - pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY / 2); - gfx_draw_qrcode(pos, qr_scale, dom); - - pos = gfx_offset(DISPLAY_RESX / 2, DISPLAY_RESY - text_offset); - gfx_draw_text(pos, dom + sizeof(MODEL_IDENTIFIER) - 1, -1, &bold, - GFX_ALIGN_CENTER); + if ((sectrue == + flash_otp_read(FLASH_OTP_BLOCK_BATCH, 0, (uint8_t *)dom, 32) && + dom[31] == 0 && cstr_starts_with(dom, MODEL_IDENTIFIER))) { + screen_prodtest_info(dom, strlen(dom), dom + sizeof(MODEL_IDENTIFIER) - 1, + strlen(dom) - sizeof(MODEL_IDENTIFIER) + 1); + } else { + screen_prodtest_welcome(); } - - display_refresh(); } static void drivers_init(void) { @@ -230,9 +191,7 @@ int main(void) { drivers_init(); usb_init_all(); - // Draw welcome screen - draw_welcome_screen(); - display_fade(0, BACKLIGHT_NORMAL, 1000); + show_welcome_screen(); // Initialize command line interface cli_init(&g_cli, console_read, console_write, NULL); diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 76bf8e0fc3..8db6885bb5 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -29,6 +29,7 @@ ui_empty_lock = [] ui_jpeg = [] hw_jpeg_decoder = [] bootloader = [] +prodtest = [] button = [] touch = [] clippy = [] diff --git a/core/embed/rust/rust_ui_prodtest.h b/core/embed/rust/rust_ui_prodtest.h new file mode 100644 index 0000000000..b25a7166bc --- /dev/null +++ b/core/embed/rust/rust_ui_prodtest.h @@ -0,0 +1,14 @@ +#include + +void screen_prodtest_info(char* id, uint8_t id_len, char* date, + uint8_t date_len); + +void screen_prodtest_welcome(void); + +void screen_prodtest_bars(const char* colors, size_t color_count); + +void screen_prodtest_show_text(const char* text, uint8_t text_len); + +void screen_prodtest_touch(int16_t x0, int16_t y0, int16_t w, int16_t h); + +void screen_prodtest_border(void); diff --git a/core/embed/rust/src/ui/api/mod.rs b/core/embed/rust/src/ui/api/mod.rs index 4b91b6315a..361b6ca4d5 100644 --- a/core/embed/rust/src/ui/api/mod.rs +++ b/core/embed/rust/src/ui/api/mod.rs @@ -5,3 +5,6 @@ pub mod bootloader_c; #[cfg(feature = "micropython")] pub mod firmware_micropython; + +#[cfg(feature = "prodtest")] +pub mod prodtest_c; diff --git a/core/embed/rust/src/ui/api/prodtest_c.rs b/core/embed/rust/src/ui/api/prodtest_c.rs new file mode 100644 index 0000000000..57f1497f2b --- /dev/null +++ b/core/embed/rust/src/ui/api/prodtest_c.rs @@ -0,0 +1,49 @@ +use crate::ui::{ui_prodtest::ProdtestUI, util::from_c_array, ModelUI}; + +#[cfg(feature = "touch")] +use crate::ui::geometry::{Offset, Point, Rect}; +#[cfg(feature = "touch")] +use cty::int16_t; + +#[no_mangle] +extern "C" fn screen_prodtest_welcome() { + ModelUI::screen_prodtest_welcome(); +} + +#[no_mangle] +extern "C" fn screen_prodtest_info( + id: *const cty::c_char, + id_len: u8, + date: *const cty::c_char, + date_len: u8, +) { + let id = unwrap!(unsafe { from_c_array(id, id_len as usize) }); + let date = unwrap!(unsafe { from_c_array(date, date_len as usize) }); + + ModelUI::screen_prodtest_info(id, date); +} + +#[no_mangle] +extern "C" fn screen_prodtest_show_text(text: *const cty::c_char, text_len: u8) { + let text = unwrap!(unsafe { from_c_array(text, text_len as usize) }); + + ModelUI::screen_prodtest_show_text(text); +} + +#[no_mangle] +extern "C" fn screen_prodtest_border() { + ModelUI::screen_prodtest_border(); +} + +#[no_mangle] +extern "C" fn screen_prodtest_bars(colors: *const cty::c_char, colors_len: u8) { + let colors: &str = unwrap!(unsafe { from_c_array(colors, colors_len as usize) }); + ModelUI::screen_prodtest_bars(colors); +} + +#[no_mangle] +#[cfg(feature = "touch")] +extern "C" fn screen_prodtest_touch(x0: int16_t, y0: int16_t, w: int16_t, h: int16_t) { + let area = Rect::from_top_left_and_size(Point::new(x0, y0), Offset::new(w, h)); + ModelUI::screen_prodtest_touch(area); +} diff --git a/core/embed/rust/src/ui/layout_bolt/mod.rs b/core/embed/rust/src/ui/layout_bolt/mod.rs index 0eab25ece2..70da78a46b 100644 --- a/core/embed/rust/src/ui/layout_bolt/mod.rs +++ b/core/embed/rust/src/ui/layout_bolt/mod.rs @@ -23,6 +23,9 @@ pub struct UIBolt; #[cfg(feature = "micropython")] pub mod ui_firmware; +#[cfg(feature = "prodtest")] +pub mod prodtest; + impl CommonUI for UIBolt { #[cfg(feature = "backlight")] fn fadein() { diff --git a/core/embed/rust/src/ui/layout_bolt/prodtest/mod.rs b/core/embed/rust/src/ui/layout_bolt/prodtest/mod.rs new file mode 100644 index 0000000000..b23880b671 --- /dev/null +++ b/core/embed/rust/src/ui/layout_bolt/prodtest/mod.rs @@ -0,0 +1,136 @@ +use crate::ui::{ + component::{base::Component, Qr}, + constant::screen, + display, + display::Color, + geometry::{Alignment, Offset, Rect}, + layout_bolt::{fonts, theme, UIBolt}, + shape, + shape::render_on_display, + ui_prodtest::ProdtestUI, +}; + +impl ProdtestUI for UIBolt { + fn screen_prodtest_welcome() { + display::sync(); + + render_on_display(None, Some(Color::black()), |target| { + let area = screen(); + shape::Bar::new(area) + .with_fg(Color::white()) + .with_thickness(1) + .render(target); + }); + + display::refresh(); + display::fade_backlight_duration(theme::backlight::get_backlight_normal(), 150); + } + + fn screen_prodtest_info(id: &str, date: &str) { + display::sync(); + let qr = Qr::new(id, true); + let mut qr = unwrap!(qr).with_border(4); + + // place the qr in the middle of the screen and size it to half the screen + let qr_width = screen().width() / 2; + + let qr_area = Rect::from_center_and_size(screen().center(), Offset::uniform(qr_width)); + qr.place(qr_area); + + render_on_display(None, Some(Color::black()), |target| { + let area = screen(); + shape::Bar::new(area).with_fg(Color::white()).render(target); + + qr.render(target); + + shape::Text::new( + screen().bottom_center() - Offset::y(10), + date, + fonts::FONT_BOLD_UPPER, + ) + .with_fg(Color::white()) + .with_align(Alignment::Center) + .render(target); + }); + + display::refresh(); + display::fade_backlight_duration(theme::backlight::get_backlight_normal(), 150); + } + + fn screen_prodtest_show_text(text: &str) { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + shape::Text::new(screen().center(), text, fonts::FONT_BOLD_UPPER) + .with_fg(Color::white()) + .with_align(Alignment::Center) + .render(target); + }); + + display::refresh(); + display::fade_backlight_duration(theme::backlight::get_backlight_normal(), 150); + } + + fn screen_prodtest_border() { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + let area = screen(); + shape::Bar::new(area) + .with_fg(Color::white()) + .with_thickness(1) + .render(target); + }); + + display::refresh(); + display::fade_backlight_duration(theme::backlight::get_backlight_normal(), 150); + } + + fn screen_prodtest_bars(colors: &str) { + display::sync(); + + let num_colors = colors.chars().count(); + + let width = if num_colors > 0 { + screen().width() / num_colors as i16 + } else { + 0 + }; + + render_on_display(None, Some(Color::black()), |target| { + for (i, c) in colors.chars().enumerate() { + let color = match c { + 'r' | 'R' => Color::rgb(255, 0, 0), + 'g' | 'G' => Color::rgb(0, 255, 0), + 'b' | 'B' => Color::rgb(0, 0, 255), + 'w' | 'W' => Color::white(), + _ => Color::black(), + }; + + let area = Rect::from_top_left_and_size( + screen().top_left() + Offset::x(i as i16 * width), + Offset::new(width, screen().height()), + ); + + shape::Bar::new(area) + .with_fg(color) + .with_bg(color) + .render(target); + } + }); + + display::refresh(); + display::fade_backlight_duration(theme::backlight::get_backlight_normal(), 150); + } + + fn screen_prodtest_touch(area: Rect) { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + shape::Bar::new(area) + .with_fg(Color::white()) + .with_bg(Color::white()) + .render(target); + }); + + display::refresh(); + display::set_backlight(theme::backlight::get_backlight_normal()); + } +} diff --git a/core/embed/rust/src/ui/layout_bolt/theme/backlight.rs b/core/embed/rust/src/ui/layout_bolt/theme/backlight.rs index fd92eed535..468313367f 100644 --- a/core/embed/rust/src/ui/layout_bolt/theme/backlight.rs +++ b/core/embed/rust/src/ui/layout_bolt/theme/backlight.rs @@ -1,4 +1,4 @@ -#[cfg(not(feature = "bootloader"))] +#[cfg(not(any(feature = "bootloader", feature = "prodtest")))] use crate::storage; // Typical backlight values. @@ -9,24 +9,24 @@ const BACKLIGHT_NONE: u8 = 0; const BACKLIGHT_MIN: u8 = 10; const BACKLIGHT_MAX: u8 = 255; -#[cfg(feature = "bootloader")] +#[cfg(any(feature = "bootloader", feature = "prodtest"))] pub fn get_backlight_normal() -> u8 { BACKLIGHT_NORMAL } -#[cfg(not(feature = "bootloader"))] +#[cfg(not(any(feature = "bootloader", feature = "prodtest")))] pub fn get_backlight_normal() -> u8 { storage::get_brightness() .unwrap_or(BACKLIGHT_NORMAL) .clamp(BACKLIGHT_MIN, BACKLIGHT_MAX) } -#[cfg(feature = "bootloader")] +#[cfg(any(feature = "bootloader", feature = "prodtest"))] pub fn get_backlight_low() -> u8 { BACKLIGHT_LOW } -#[cfg(not(feature = "bootloader"))] +#[cfg(not(any(feature = "bootloader", feature = "prodtest")))] pub fn get_backlight_low() -> u8 { storage::get_brightness() .unwrap_or(BACKLIGHT_LOW) diff --git a/core/embed/rust/src/ui/layout_caesar/mod.rs b/core/embed/rust/src/ui/layout_caesar/mod.rs index 4cc2825bd1..49e51b088c 100644 --- a/core/embed/rust/src/ui/layout_caesar/mod.rs +++ b/core/embed/rust/src/ui/layout_caesar/mod.rs @@ -2,6 +2,10 @@ use super::{geometry::Rect, CommonUI}; #[cfg(feature = "bootloader")] pub mod bootloader; + +#[cfg(feature = "prodtest")] +pub mod prodtest; + pub mod common_messages; pub mod component; #[cfg(feature = "micropython")] diff --git a/core/embed/rust/src/ui/layout_caesar/prodtest/mod.rs b/core/embed/rust/src/ui/layout_caesar/prodtest/mod.rs new file mode 100644 index 0000000000..430103ccb0 --- /dev/null +++ b/core/embed/rust/src/ui/layout_caesar/prodtest/mod.rs @@ -0,0 +1,115 @@ +use crate::ui::{ + component::{base::Component, Qr}, + constant::screen, + display, + display::Color, + geometry::{Alignment, Offset, Rect}, + layout_caesar::{fonts, UICaesar}, + shape, + shape::render_on_display, + ui_prodtest::ProdtestUI, +}; + +impl ProdtestUI for UICaesar { + fn screen_prodtest_welcome() { + display::sync(); + + render_on_display(None, Some(Color::black()), |target| { + let area = screen(); + shape::Bar::new(area) + .with_fg(Color::white()) + .with_bg(Color::white()) + .render(target); + }); + + display::refresh(); + } + + fn screen_prodtest_info(id: &str, date: &str) { + display::sync(); + let qr = Qr::new(id, true); + let mut qr = unwrap!(qr).with_border(1); + + let qr_width = 50; + + let qr_area = Rect::from_center_and_size(screen().center(), Offset::uniform(qr_width)) + .translate(Offset::y(-5)); + qr.place(qr_area); + + render_on_display(None, Some(Color::black()), |target| { + qr.render(target); + + shape::Text::new(screen().bottom_center(), date, fonts::FONT_BOLD_UPPER) + .with_fg(Color::white()) + .with_align(Alignment::Center) + .render(target); + }); + + display::refresh(); + } + + fn screen_prodtest_show_text(text: &str) { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + shape::Text::new(screen().center(), text, fonts::FONT_BOLD_UPPER) + .with_fg(Color::white()) + .with_align(Alignment::Center) + .render(target); + }); + + display::refresh(); + } + + fn screen_prodtest_border() { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + let area = screen(); + shape::Bar::new(area) + .with_fg(Color::white()) + .with_thickness(1) + .render(target); + }); + + display::refresh(); + } + + fn screen_prodtest_bars(colors: &str) { + display::sync(); + + let num_colors = colors.chars().count(); + + let width = if num_colors > 0 { + screen().width() / num_colors as i16 + } else { + 0 + }; + + render_on_display(None, Some(Color::black()), |target| { + for (i, c) in colors.chars().enumerate() { + let color = match c { + 'r' | 'R' => Color::rgb(255, 0, 0), + 'g' | 'G' => Color::rgb(0, 255, 0), + 'b' | 'B' => Color::rgb(0, 0, 255), + 'w' | 'W' => Color::white(), + _ => Color::black(), + }; + + let area = Rect::from_top_left_and_size( + screen().top_left() + Offset::x(i as i16 * width), + Offset::new(width, screen().height()), + ); + + shape::Bar::new(area) + .with_fg(color) + .with_bg(color) + .render(target); + } + }); + + display::refresh(); + } + + fn screen_prodtest_touch(_area: Rect) { + unimplemented!(); + } +} diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index 4fe5b62b77..c99f669d5c 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -26,6 +26,10 @@ pub mod layout_delizia; #[cfg(feature = "bootloader")] pub mod ui_bootloader; + +#[cfg(feature = "prodtest")] +pub mod ui_prodtest; + pub mod ui_common; #[cfg(feature = "micropython")] pub mod ui_firmware; diff --git a/core/embed/rust/src/ui/ui_prodtest.rs b/core/embed/rust/src/ui/ui_prodtest.rs new file mode 100644 index 0000000000..fea55f7a4c --- /dev/null +++ b/core/embed/rust/src/ui/ui_prodtest.rs @@ -0,0 +1,15 @@ +use crate::ui::geometry::Rect; + +pub trait ProdtestUI { + fn screen_prodtest_welcome(); + + fn screen_prodtest_info(id: &str, date: &str); + + fn screen_prodtest_show_text(text: &str); + + fn screen_prodtest_border(); + + fn screen_prodtest_bars(colors: &str); + + fn screen_prodtest_touch(area: Rect); +}