From 6fd09f521ab58cdc242dd138c535ffab1742adc9 Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Thu, 28 May 2026 09:22:42 -0400 Subject: [PATCH] v0.0.31 - Harden LVGL screen transitions with widget pointer resets and null guards --- firmware/cyd_esp32_2432s028/main/mnemonic.c | 66 +++ firmware/cyd_esp32_2432s028/main/mnemonic.h | 3 + firmware/cyd_esp32_2432s028/main/ui.c | 529 +++++++++++++++++--- src/main.c | 4 +- 4 files changed, 529 insertions(+), 73 deletions(-) diff --git a/firmware/cyd_esp32_2432s028/main/mnemonic.c b/firmware/cyd_esp32_2432s028/main/mnemonic.c index 02490f8..279b652 100644 --- a/firmware/cyd_esp32_2432s028/main/mnemonic.c +++ b/firmware/cyd_esp32_2432s028/main/mnemonic.c @@ -277,6 +277,72 @@ size_t mnemonic_words_for_letter(char letter, size_t *out_start) return s_letter_count[idx]; } +size_t mnemonic_words_with_prefix(const char *prefix, uint16_t *out_indices, size_t max_out) +{ + size_t start = 0; + size_t count = 0; + size_t written = 0; + + if (prefix == NULL || prefix[0] == '\0') { + return 0; + } + + count = mnemonic_words_for_letter(prefix[0], &start); + if (count == 0) { + return 0; + } + + for (size_t i = 0; i < count; ++i) { + size_t idx = start + i; + const char *word = BIP39_WORDLIST[idx]; + if (strncmp(word, prefix, strlen(prefix)) == 0) { + if (out_indices != NULL && written < max_out) { + out_indices[written] = (uint16_t)idx; + } + written++; + } + } + + return written; +} + +bool mnemonic_letter_extends_prefix(const char *prefix, size_t prefix_len, char next_letter) +{ + char probe[16]; + size_t start = 0; + size_t count = 0; + + if (next_letter >= 'A' && next_letter <= 'Z') { + next_letter = (char)(next_letter - 'A' + 'a'); + } + + if (next_letter < 'a' || next_letter > 'z') { + return false; + } + + if (prefix == NULL || prefix_len >= sizeof(probe) - 1) { + return false; + } + + memcpy(probe, prefix, prefix_len); + probe[prefix_len] = next_letter; + probe[prefix_len + 1] = '\0'; + + count = mnemonic_words_for_letter(probe[0], &start); + if (count == 0) { + return false; + } + + for (size_t i = 0; i < count; ++i) { + const char *word = BIP39_WORDLIST[start + i]; + if (strncmp(word, probe, prefix_len + 1) == 0) { + return true; + } + } + + return false; +} + const char *mnemonic_word_at(size_t index) { if (index >= 2048) { diff --git a/firmware/cyd_esp32_2432s028/main/mnemonic.h b/firmware/cyd_esp32_2432s028/main/mnemonic.h index 0f60698..e145a71 100644 --- a/firmware/cyd_esp32_2432s028/main/mnemonic.h +++ b/firmware/cyd_esp32_2432s028/main/mnemonic.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -7,4 +8,6 @@ int generate_mnemonic_12(char *out, size_t out_len); int mnemonic_to_seed(const char *mnemonic, uint8_t seed[64]); int mnemonic_validate(const char *mnemonic); size_t mnemonic_words_for_letter(char letter, size_t *out_start); +size_t mnemonic_words_with_prefix(const char *prefix, uint16_t *out_indices, size_t max_out); +bool mnemonic_letter_extends_prefix(const char *prefix, size_t prefix_len, char next_letter); const char *mnemonic_word_at(size_t index); diff --git a/firmware/cyd_esp32_2432s028/main/ui.c b/firmware/cyd_esp32_2432s028/main/ui.c index 3dcbb2a..3c1bf1f 100644 --- a/firmware/cyd_esp32_2432s028/main/ui.c +++ b/firmware/cyd_esp32_2432s028/main/ui.c @@ -13,10 +13,15 @@ #include "ili9341.h" #include "lvgl.h" #include "mnemonic.h" +#include "secure_mem.h" #include "touch.h" #define UI_TICK_MS 2 #define UI_DRAW_LINES 20 +#define UI_MNEMONIC_WORDS 12 +#define UI_BIP39_WORD_MAX 9 +#define UI_SUGGESTION_STORE_MAX 64 +#define UI_SUGGESTION_VISIBLE_MAX 32 static const char *TAG = "ui_lvgl"; @@ -30,7 +35,21 @@ static bool s_done = false; static bool s_have_mnemonic = false; static char s_selected_mnemonic[256]; static lv_obj_t *s_status_label = NULL; -static lv_obj_t *s_entry_ta = NULL; + +static char s_committed_words[UI_MNEMONIC_WORDS][UI_BIP39_WORD_MAX]; +static size_t s_word_count = 0; +static char s_prefix[UI_BIP39_WORD_MAX]; +static size_t s_prefix_len = 0; +static bool s_checksum_override = false; + +static lv_obj_t *s_entry_words_label = NULL; +static lv_obj_t *s_entry_prefix_label = NULL; +static lv_obj_t *s_suggestion_row = NULL; +static lv_obj_t *s_key_matrix = NULL; +static lv_obj_t *s_checksum_panel = NULL; + +static uint16_t s_match_indices[UI_SUGGESTION_STORE_MAX]; +static size_t s_match_total = 0; static ui_approval_decision_t s_approval_decision = UI_APPROVAL_TIMEOUT; static lv_obj_t *s_approval_countdown = NULL; @@ -41,6 +60,18 @@ static int64_t s_approval_deadline_us = 0; static char s_event_lines[UI_EVENT_LOG_MAX][UI_EVENT_LINE_MAX]; static int s_event_count = 0; +static void reset_screen_widget_refs(void) +{ + s_status_label = NULL; + s_entry_words_label = NULL; + s_entry_prefix_label = NULL; + s_suggestion_row = NULL; + s_key_matrix = NULL; + s_checksum_panel = NULL; + s_approval_countdown = NULL; + s_match_total = 0; +} + static void style_screen(lv_obj_t *scr) { lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); @@ -62,31 +93,24 @@ static void style_button(lv_obj_t *btn) lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED); } -static void style_text_area(lv_obj_t *ta) +static void style_key_matrix(lv_obj_t *km) { - lv_obj_set_style_bg_opa(ta, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(ta, lv_color_hex(0x000000), 0); - lv_obj_set_style_border_width(ta, 2, 0); - lv_obj_set_style_border_color(ta, lv_color_hex(0xFFFFFF), 0); - lv_obj_set_style_text_color(ta, lv_color_hex(0xFFFFFF), 0); - lv_obj_set_style_text_color(ta, lv_color_hex(0xFF0000), LV_PART_CURSOR); -} + lv_obj_set_style_bg_opa(km, LV_OPA_COVER, 0); + lv_obj_set_style_bg_color(km, lv_color_hex(0x000000), 0); + lv_obj_set_style_border_width(km, 1, 0); + lv_obj_set_style_border_color(km, lv_color_hex(0xFFFFFF), 0); -static void style_keyboard(lv_obj_t *kb) -{ - lv_obj_set_style_bg_opa(kb, LV_OPA_COVER, 0); - lv_obj_set_style_bg_color(kb, lv_color_hex(0x000000), 0); - lv_obj_set_style_border_width(kb, 1, 0); - lv_obj_set_style_border_color(kb, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_bg_opa(km, LV_OPA_COVER, LV_PART_ITEMS); + lv_obj_set_style_bg_color(km, lv_color_hex(0x000000), LV_PART_ITEMS); + lv_obj_set_style_border_color(km, lv_color_hex(0xFFFFFF), LV_PART_ITEMS); + lv_obj_set_style_border_width(km, 1, LV_PART_ITEMS); + lv_obj_set_style_text_color(km, lv_color_hex(0xFFFFFF), LV_PART_ITEMS); - lv_obj_set_style_bg_opa(kb, LV_OPA_COVER, LV_PART_ITEMS); - lv_obj_set_style_bg_color(kb, lv_color_hex(0x000000), LV_PART_ITEMS); - lv_obj_set_style_border_color(kb, lv_color_hex(0xFFFFFF), LV_PART_ITEMS); - lv_obj_set_style_border_width(kb, 1, LV_PART_ITEMS); - lv_obj_set_style_text_color(kb, lv_color_hex(0xFFFFFF), LV_PART_ITEMS); + lv_obj_set_style_border_color(km, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_set_style_text_color(km, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED); - lv_obj_set_style_border_color(kb, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED); - lv_obj_set_style_text_color(kb, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_set_style_text_color(km, lv_color_hex(0x505050), LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_set_style_border_color(km, lv_color_hex(0x303030), LV_PART_ITEMS | LV_STATE_DISABLED); } static void lvgl_tick_cb(void *arg) @@ -198,85 +222,443 @@ static void on_generate(lv_event_t *e) } static void build_main_menu(void); +static void refresh_entry_ui(void); +static bool verify_or_prompt_checksum(void); -static void on_back_to_menu(lv_event_t *e) +static const char *s_key_map[] = { + "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n", + "a", "s", "d", "f", "g", "h", "j", "k", "l", "\n", + "z", "x", "c", "v", "b", "n", "m", "<", "", +}; + +static void clear_entry_state(void) { - (void)e; - build_main_menu(); + secure_memzero(s_committed_words, sizeof(s_committed_words)); + secure_memzero(s_prefix, sizeof(s_prefix)); + s_word_count = 0; + s_prefix_len = 0; + s_checksum_override = false; + s_match_total = 0; } -static void on_use_entry(lv_event_t *e) +static bool join_committed_words(char *out, size_t out_len) +{ + size_t cursor = 0; + + if (out == NULL || out_len == 0) { + return false; + } + + out[0] = '\0'; + + for (size_t i = 0; i < s_word_count; ++i) { + size_t wl = strlen(s_committed_words[i]); + + if (cursor != 0) { + if (cursor + 1 >= out_len) { + out[0] = '\0'; + return false; + } + out[cursor++] = ' '; + } + + if (cursor + wl >= out_len) { + out[0] = '\0'; + return false; + } + + memcpy(out + cursor, s_committed_words[i], wl); + cursor += wl; + out[cursor] = '\0'; + } + + return true; +} + +static void commit_word(const char *word) +{ + if (word == NULL || s_word_count >= UI_MNEMONIC_WORDS) { + return; + } + + memset(s_committed_words[s_word_count], 0, UI_BIP39_WORD_MAX); + strncpy(s_committed_words[s_word_count], word, UI_BIP39_WORD_MAX - 1); + s_word_count++; + + secure_memzero(s_prefix, sizeof(s_prefix)); + s_prefix_len = 0; + s_checksum_override = false; +} + +static void on_checksum_use_anyway(lv_event_t *e) { (void)e; - if (s_entry_ta == NULL) { - return; - } - - const char *txt = lv_textarea_get_text(s_entry_ta); - if (txt == NULL || txt[0] == '\0') { - set_status("Mnemonic is empty"); - return; - } - - if (mnemonic_validate(txt) != 0) { - set_status("Invalid mnemonic"); + char joined[256]; + memset(joined, 0, sizeof(joined)); + if (!join_committed_words(joined, sizeof(joined))) { + set_status("Mnemonic buffer error"); + secure_memzero(joined, sizeof(joined)); return; } memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic)); - strncpy(s_selected_mnemonic, txt, sizeof(s_selected_mnemonic) - 1); + strncpy(s_selected_mnemonic, joined, sizeof(s_selected_mnemonic) - 1); s_have_mnemonic = true; s_done = true; + s_checksum_override = true; + secure_memzero(joined, sizeof(joined)); +} + +static void on_checksum_edit_words(lv_event_t *e) +{ + (void)e; + + if (s_checksum_panel != NULL) { + lv_obj_del(s_checksum_panel); + s_checksum_panel = NULL; + } + set_status("Checksum mismatch: edit or use anyway"); + refresh_entry_ui(); +} + +static bool verify_or_prompt_checksum(void) +{ + char joined[256]; + + if (s_key_matrix == NULL || s_entry_words_label == NULL || s_entry_prefix_label == NULL) { + return false; + } + + if (s_word_count != UI_MNEMONIC_WORDS || s_prefix_len != 0) { + set_status("Need exactly 12 complete words"); + return false; + } + + memset(joined, 0, sizeof(joined)); + if (!join_committed_words(joined, sizeof(joined))) { + set_status("Mnemonic buffer error"); + secure_memzero(joined, sizeof(joined)); + return false; + } + + if (mnemonic_validate(joined) == 0) { + if (s_checksum_panel != NULL) { + lv_obj_del(s_checksum_panel); + s_checksum_panel = NULL; + } + set_status("Checksum valid"); + memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic)); + strncpy(s_selected_mnemonic, joined, sizeof(s_selected_mnemonic) - 1); + s_have_mnemonic = true; + s_done = true; + secure_memzero(joined, sizeof(joined)); + return true; + } + + if (s_checksum_panel != NULL) { + lv_obj_del(s_checksum_panel); + s_checksum_panel = NULL; + } + + s_checksum_panel = lv_obj_create(lv_scr_act()); + lv_obj_set_size(s_checksum_panel, DISPLAY_WIDTH - 20, 82); + lv_obj_align(s_checksum_panel, LV_ALIGN_TOP_MID, 0, 78); + lv_obj_set_style_bg_color(s_checksum_panel, lv_color_hex(0x000000), 0); + lv_obj_set_style_border_color(s_checksum_panel, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_border_width(s_checksum_panel, 2, 0); + lv_obj_set_style_radius(s_checksum_panel, 6, 0); + lv_obj_set_style_pad_all(s_checksum_panel, 6, 0); + + lv_obj_t *txt = lv_label_create(s_checksum_panel); + lv_label_set_text(txt, "Checksum invalid. Use anyway?"); + lv_obj_set_style_text_color(txt, lv_color_hex(0xFF0000), 0); + lv_obj_align(txt, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t *use_btn = lv_btn_create(s_checksum_panel); + lv_obj_set_size(use_btn, 128, 30); + lv_obj_align(use_btn, LV_ALIGN_BOTTOM_LEFT, 4, -2); + style_button(use_btn); + lv_obj_set_style_border_color(use_btn, lv_color_hex(0xFF0000), 0); + lv_obj_add_event_cb(use_btn, on_checksum_use_anyway, LV_EVENT_RELEASED, NULL); + lv_obj_t *use_lbl = lv_label_create(use_btn); + lv_label_set_text(use_lbl, "Use Anyway"); + lv_obj_center(use_lbl); + + lv_obj_t *edit_btn = lv_btn_create(s_checksum_panel); + lv_obj_set_size(edit_btn, 128, 30); + lv_obj_align(edit_btn, LV_ALIGN_BOTTOM_RIGHT, -4, -2); + style_button(edit_btn); + lv_obj_add_event_cb(edit_btn, on_checksum_edit_words, LV_EVENT_RELEASED, NULL); + lv_obj_t *edit_lbl = lv_label_create(edit_btn); + lv_label_set_text(edit_lbl, "Edit Words"); + lv_obj_center(edit_lbl); + + set_status("Checksum invalid"); + secure_memzero(joined, sizeof(joined)); + return false; +} + +static void on_back_to_menu(lv_event_t *e) +{ + (void)e; + if (s_checksum_panel != NULL) { + lv_obj_del(s_checksum_panel); + s_checksum_panel = NULL; + } + clear_entry_state(); + build_main_menu(); +} + +static void on_suggestion_tap(lv_event_t *e) +{ + const char *word = (const char *)lv_event_get_user_data(e); + + if (s_suggestion_row == NULL || word == NULL || s_word_count >= UI_MNEMONIC_WORDS) { + return; + } + + commit_word(word); + refresh_entry_ui(); + + if (s_word_count == UI_MNEMONIC_WORDS) { + (void)verify_or_prompt_checksum(); + } +} + +static void entry_backspace_step(void) +{ + if (s_prefix_len > 0) { + s_prefix_len--; + s_prefix[s_prefix_len] = '\0'; + } else if (s_word_count > 0) { + s_word_count--; + memset(s_prefix, 0, sizeof(s_prefix)); + strncpy(s_prefix, s_committed_words[s_word_count], sizeof(s_prefix) - 1); + s_prefix_len = strlen(s_prefix); + secure_memzero(s_committed_words[s_word_count], sizeof(s_committed_words[s_word_count])); + } + + s_checksum_override = false; + if (s_checksum_panel != NULL) { + lv_obj_del(s_checksum_panel); + s_checksum_panel = NULL; + } + refresh_entry_ui(); +} + +static void on_key_matrix(lv_event_t *e) +{ + lv_obj_t *obj = lv_event_get_target(e); + if (s_key_matrix == NULL || obj != s_key_matrix) { + return; + } + + uint16_t idx = lv_btnmatrix_get_selected_btn(obj); + const char *txt = lv_btnmatrix_get_btn_text(obj, idx); + + if (txt == NULL || txt[0] == '\0' || txt[1] != '\0') { + return; + } + + if (txt[0] == '<') { + entry_backspace_step(); + return; + } + + if (s_word_count >= UI_MNEMONIC_WORDS) { + set_status("Already have 12 words"); + return; + } + + if (s_prefix_len + 1 >= sizeof(s_prefix)) { + set_status("Word too long"); + return; + } + + s_prefix[s_prefix_len++] = txt[0]; + s_prefix[s_prefix_len] = '\0'; + s_checksum_override = false; + + refresh_entry_ui(); + + if (s_match_total == 1) { + const char *word = mnemonic_word_at(s_match_indices[0]); + if (word != NULL) { + commit_word(word); + refresh_entry_ui(); + if (s_word_count == UI_MNEMONIC_WORDS) { + (void)verify_or_prompt_checksum(); + } + } + } +} + +static void refresh_entry_ui(void) +{ + size_t shown = 0; + char words_line[256]; + char prefix_line[64]; + + if (s_checksum_panel != NULL) { + lv_obj_del(s_checksum_panel); + s_checksum_panel = NULL; + } + + memset(words_line, 0, sizeof(words_line)); + (void)join_committed_words(words_line, sizeof(words_line)); + + if (s_entry_words_label != NULL) { + if (s_word_count == 0) { + lv_label_set_text(s_entry_words_label, "(no words selected)"); + } else { + lv_label_set_text(s_entry_words_label, words_line); + } + } + + snprintf(prefix_line, sizeof(prefix_line), "Word %u/12 prefix: %s", + (unsigned)(s_word_count + 1), + (s_prefix_len > 0) ? s_prefix : "-"); + if (s_entry_prefix_label != NULL) { + lv_label_set_text(s_entry_prefix_label, prefix_line); + } + + if (s_suggestion_row != NULL) { + lv_obj_clean(s_suggestion_row); + + if (s_prefix_len > 0 && s_word_count < UI_MNEMONIC_WORDS) { + s_match_total = mnemonic_words_with_prefix(s_prefix, s_match_indices, UI_SUGGESTION_STORE_MAX); + shown = (s_match_total > UI_SUGGESTION_VISIBLE_MAX) ? UI_SUGGESTION_VISIBLE_MAX : s_match_total; + + for (size_t i = 0; i < shown; ++i) { + const char *word = mnemonic_word_at(s_match_indices[i]); + lv_obj_t *btn; + lv_obj_t *lbl; + if (word == NULL) { + continue; + } + btn = lv_btn_create(s_suggestion_row); + lv_obj_set_size(btn, 84, 26); + style_button(btn); + lv_obj_add_event_cb(btn, on_suggestion_tap, LV_EVENT_RELEASED, (void *)word); + lbl = lv_label_create(btn); + lv_label_set_text(lbl, word); + lv_obj_center(lbl); + } + + if (s_match_total > shown) { + char more[24]; + lv_obj_t *m = lv_label_create(s_suggestion_row); + snprintf(more, sizeof(more), "+%u more", (unsigned)(s_match_total - shown)); + lv_label_set_text(m, more); + lv_obj_set_style_text_color(m, lv_color_hex(0xFFFFFF), 0); + } + + if (s_match_total == 0) { + set_status("No matches"); + } else { + set_status(""); + } + } else { + s_match_total = 0; + } + } + + if (s_key_matrix != NULL) { + const uint16_t matrix_btn_count = 27; + for (uint16_t i = 0; i < matrix_btn_count; ++i) { + const char *txt = lv_btnmatrix_get_btn_text(s_key_matrix, i); + bool enabled = false; + if (txt == NULL || txt[0] == '\0' || txt[1] != '\0') { + continue; + } + + if (txt[0] == '<') { + enabled = (s_prefix_len > 0 || s_word_count > 0); + } else if (s_word_count >= UI_MNEMONIC_WORDS) { + enabled = false; + } else if (s_prefix_len == 0) { + size_t start = 0; + enabled = (mnemonic_words_for_letter(txt[0], &start) > 0); + } else { + enabled = mnemonic_letter_extends_prefix(s_prefix, s_prefix_len, txt[0]); + } + + if (enabled) { + lv_btnmatrix_clear_btn_ctrl(s_key_matrix, i, LV_BTNMATRIX_CTRL_DISABLED); + } else { + lv_btnmatrix_set_btn_ctrl(s_key_matrix, i, LV_BTNMATRIX_CTRL_DISABLED); + } + } + } } static void build_entry_screen(void) { lv_obj_t *scr = lv_scr_act(); + lv_obj_t *title; + lv_obj_t *close_btn; + lv_obj_t *close_lbl; + + reset_screen_widget_refs(); lv_obj_clean(scr); style_screen(scr); + clear_entry_state(); - lv_obj_t *title = lv_label_create(scr); + title = lv_label_create(scr); lv_label_set_text(title, "Enter Mnemonic"); lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0); - lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 6); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 4); - s_entry_ta = lv_textarea_create(scr); - lv_obj_set_size(s_entry_ta, DISPLAY_WIDTH - 16, 70); - lv_obj_align(s_entry_ta, LV_ALIGN_TOP_MID, 0, 32); - lv_textarea_set_placeholder_text(s_entry_ta, "12 words separated by spaces"); - lv_textarea_set_one_line(s_entry_ta, false); - style_text_area(s_entry_ta); + s_entry_words_label = lv_label_create(scr); + lv_obj_set_width(s_entry_words_label, DISPLAY_WIDTH - 16); + lv_label_set_long_mode(s_entry_words_label, LV_LABEL_LONG_WRAP); + lv_label_set_text(s_entry_words_label, "(no words selected)"); + lv_obj_set_style_text_color(s_entry_words_label, lv_color_hex(0xFFFFFF), 0); + lv_obj_align(s_entry_words_label, LV_ALIGN_TOP_LEFT, 8, 26); - lv_obj_t *kb = lv_keyboard_create(scr); - lv_obj_set_size(kb, DISPLAY_WIDTH, 110); - lv_obj_align(kb, LV_ALIGN_BOTTOM_MID, 0, 0); - lv_keyboard_set_textarea(kb, s_entry_ta); - style_keyboard(kb); - - lv_obj_t *use_btn = lv_btn_create(scr); - lv_obj_set_size(use_btn, 150, 30); - lv_obj_align(use_btn, LV_ALIGN_TOP_LEFT, 8, 110); - lv_obj_add_event_cb(use_btn, on_use_entry, LV_EVENT_RELEASED, NULL); - style_button(use_btn); - lv_obj_t *use_lbl = lv_label_create(use_btn); - lv_label_set_text(use_lbl, "Use Mnemonic"); - lv_obj_center(use_lbl); - - lv_obj_t *back_btn = lv_btn_create(scr); - lv_obj_set_size(back_btn, 150, 30); - lv_obj_align(back_btn, LV_ALIGN_TOP_RIGHT, -8, 110); - lv_obj_add_event_cb(back_btn, on_back_to_menu, LV_EVENT_RELEASED, NULL); - style_button(back_btn); - lv_obj_t *back_lbl = lv_label_create(back_btn); - lv_label_set_text(back_lbl, "Back"); - lv_obj_center(back_lbl); + s_entry_prefix_label = lv_label_create(scr); + lv_label_set_text(s_entry_prefix_label, "Word 1/12 prefix: -"); + lv_obj_set_style_text_color(s_entry_prefix_label, lv_color_hex(0xFFFFFF), 0); + lv_obj_align(s_entry_prefix_label, LV_ALIGN_TOP_LEFT, 8, 66); s_status_label = lv_label_create(scr); lv_label_set_text(s_status_label, ""); lv_obj_set_style_text_color(s_status_label, lv_color_hex(0xFF0000), 0); - lv_obj_align(s_status_label, LV_ALIGN_TOP_LEFT, 8, 144); + lv_obj_align(s_status_label, LV_ALIGN_TOP_LEFT, 8, 82); + + s_suggestion_row = lv_obj_create(scr); + lv_obj_set_size(s_suggestion_row, DISPLAY_WIDTH - 12, 30); + lv_obj_align(s_suggestion_row, LV_ALIGN_TOP_MID, 0, 96); + lv_obj_set_style_bg_color(s_suggestion_row, lv_color_hex(0x000000), 0); + lv_obj_set_style_border_color(s_suggestion_row, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_border_width(s_suggestion_row, 1, 0); + lv_obj_set_style_pad_all(s_suggestion_row, 2, 0); + lv_obj_set_scroll_dir(s_suggestion_row, LV_DIR_HOR); + lv_obj_set_flex_flow(s_suggestion_row, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(s_suggestion_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + close_btn = lv_btn_create(scr); + lv_obj_set_size(close_btn, 42, 30); + lv_obj_align(close_btn, LV_ALIGN_TOP_RIGHT, -4, 2); + style_button(close_btn); + lv_obj_add_event_cb(close_btn, on_back_to_menu, LV_EVENT_CLICKED, NULL); + close_lbl = lv_label_create(close_btn); + lv_label_set_text(close_lbl, "X"); + lv_obj_center(close_lbl); + + s_key_matrix = lv_btnmatrix_create(scr); + lv_obj_set_size(s_key_matrix, DISPLAY_WIDTH - 16, 100); + lv_obj_align(s_key_matrix, LV_ALIGN_TOP_MID, 0, 132); + lv_btnmatrix_set_map(s_key_matrix, s_key_map); + lv_obj_add_event_cb(s_key_matrix, on_key_matrix, LV_EVENT_VALUE_CHANGED, NULL); + style_key_matrix(s_key_matrix); + lv_obj_set_style_pad_all(s_key_matrix, 0, 0); + lv_obj_set_style_pad_gap(s_key_matrix, 0, LV_PART_ITEMS); + lv_obj_set_style_border_width(s_key_matrix, 1, LV_PART_ITEMS); + + refresh_entry_ui(); } static void on_enter(lv_event_t *e) @@ -316,6 +698,7 @@ static void on_use_viewed(lv_event_t *e) static void build_view_screen(void) { lv_obj_t *scr = lv_scr_act(); + reset_screen_widget_refs(); lv_obj_clean(scr); style_screen(scr); @@ -370,6 +753,7 @@ static void on_view(lv_event_t *e) static void build_main_menu(void) { lv_obj_t *scr = lv_scr_act(); + reset_screen_widget_refs(); lv_obj_clean(scr); style_screen(scr); @@ -424,6 +808,7 @@ void ui_show_approval_prompt(const char *caller_pubkey_hex, int kind, const char } lv_obj_t *scr = lv_scr_act(); + reset_screen_widget_refs(); lv_obj_clean(scr); style_screen(scr); @@ -535,6 +920,7 @@ ui_approval_decision_t ui_wait_for_approval(uint32_t timeout_ms) static void show_event_log_screen(void) { lv_obj_t *scr = lv_scr_act(); + reset_screen_widget_refs(); lv_obj_clean(scr); style_screen(scr); @@ -579,6 +965,7 @@ void ui_show_idle(const char *npub_full, const char *mnemonic_full, const char * } lv_obj_t *scr = lv_scr_act(); + reset_screen_widget_refs(); lv_obj_clean(scr); style_screen(scr); diff --git a/src/main.c b/src/main.c index 4bb1511..83b7dbf 100644 --- a/src/main.c +++ b/src/main.c @@ -491,8 +491,8 @@ int socket_name_random(char *out, size_t out_len); /* Version information (auto-updated by build/version tooling) */ #define NSIGNER_VERSION_MAJOR 0 #define NSIGNER_VERSION_MINOR 0 -#define NSIGNER_VERSION_PATCH 30 -#define NSIGNER_VERSION "v0.0.30" +#define NSIGNER_VERSION_PATCH 31 +#define NSIGNER_VERSION "v0.0.31" /* NSIGNER_HEADERLESS_DECLS_END */