Files
n_signer/firmware/cyd_esp32_2432s028/main/ui.c

696 lines
22 KiB
C

#include "ui.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "display.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ili9341.h"
#include "lvgl.h"
#include "mnemonic.h"
#include "touch.h"
#define UI_TICK_MS 2
#define UI_DRAW_LINES 20
static const char *TAG = "ui_lvgl";
static bool s_lvgl_inited = false;
static lv_disp_draw_buf_t s_draw_buf;
static lv_color_t s_lv_draw_buf[DISPLAY_WIDTH * UI_DRAW_LINES];
static uint8_t s_flush_bytes[DISPLAY_WIDTH * UI_DRAW_LINES * 2];
static esp_timer_handle_t s_lv_tick_timer;
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 ui_approval_decision_t s_approval_decision = UI_APPROVAL_TIMEOUT;
static lv_obj_t *s_approval_countdown = NULL;
static int64_t s_approval_deadline_us = 0;
#define UI_EVENT_LOG_MAX 8
#define UI_EVENT_LINE_MAX 96
static char s_event_lines[UI_EVENT_LOG_MAX][UI_EVENT_LINE_MAX];
static int s_event_count = 0;
static void style_screen(lv_obj_t *scr)
{
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(scr, lv_color_hex(0x000000), 0);
}
static void style_button(lv_obj_t *btn)
{
lv_obj_set_style_radius(btn, 6, 0);
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x000000), 0);
lv_obj_set_style_border_width(btn, 2, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_color(btn, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED);
lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED);
lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
}
static void style_text_area(lv_obj_t *ta)
{
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);
}
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(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(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);
}
static void lvgl_tick_cb(void *arg)
{
(void)arg;
lv_tick_inc(UI_TICK_MS);
}
static void lvgl_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
const uint16_t w = (uint16_t)(area->x2 - area->x1 + 1);
const uint16_t h = (uint16_t)(area->y2 - area->y1 + 1);
const uint32_t px_count = (uint32_t)w * h;
if (px_count > (uint32_t)(DISPLAY_WIDTH * UI_DRAW_LINES)) {
lv_disp_flush_ready(disp_drv);
return;
}
for (uint32_t i = 0; i < px_count; ++i) {
uint16_t c = color_p[i].full;
s_flush_bytes[i * 2 + 0] = (uint8_t)(c >> 8);
s_flush_bytes[i * 2 + 1] = (uint8_t)(c & 0xFF);
}
(void)ili9341_blit_rgb565((uint16_t)area->x1,
(uint16_t)area->y1,
w,
h,
s_flush_bytes);
lv_disp_flush_ready(disp_drv);
}
static void lvgl_touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
(void)drv;
int sx = 0;
int sy = 0;
uint16_t pressure = 0;
if (!touch_read_screen(&sx, &sy, &pressure)) {
data->state = LV_INDEV_STATE_RELEASED;
return;
}
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = sx;
data->point.y = sy;
}
static esp_err_t ui_lvgl_init_once(void)
{
if (s_lvgl_inited) {
return ESP_OK;
}
ESP_ERROR_CHECK(touch_init());
lv_init();
lv_disp_draw_buf_init(&s_draw_buf, s_lv_draw_buf, NULL, DISPLAY_WIDTH * UI_DRAW_LINES);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = DISPLAY_WIDTH;
disp_drv.ver_res = DISPLAY_HEIGHT;
disp_drv.flush_cb = lvgl_flush_cb;
disp_drv.draw_buf = &s_draw_buf;
lv_disp_drv_register(&disp_drv);
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = lvgl_touch_read_cb;
lv_indev_drv_register(&indev_drv);
const esp_timer_create_args_t lv_tick_timer_args = {
.callback = lvgl_tick_cb,
.name = "lv_tick",
};
ESP_ERROR_CHECK(esp_timer_create(&lv_tick_timer_args, &s_lv_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(s_lv_tick_timer, UI_TICK_MS * 1000));
s_lvgl_inited = true;
return ESP_OK;
}
static void set_status(const char *text)
{
if (s_status_label != NULL) {
lv_label_set_text(s_status_label, text);
}
}
static void on_generate(lv_event_t *e)
{
(void)e;
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
if (generate_mnemonic_12(s_selected_mnemonic, sizeof(s_selected_mnemonic)) != 0) {
set_status("Failed to generate mnemonic");
return;
}
s_have_mnemonic = true;
s_done = true;
}
static void build_main_menu(void);
static void on_back_to_menu(lv_event_t *e)
{
(void)e;
build_main_menu();
}
static void on_use_entry(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");
return;
}
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
strncpy(s_selected_mnemonic, txt, sizeof(s_selected_mnemonic) - 1);
s_have_mnemonic = true;
s_done = true;
}
static void build_entry_screen(void)
{
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
style_screen(scr);
lv_obj_t *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);
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);
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_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);
}
static void on_enter(lv_event_t *e)
{
(void)e;
build_entry_screen();
}
static void on_approve_deny(lv_event_t *e)
{
(void)e;
s_approval_decision = UI_APPROVAL_DENY;
}
static void on_approve_once(lv_event_t *e)
{
(void)e;
s_approval_decision = UI_APPROVAL_ONCE;
}
static void on_approve_always(lv_event_t *e)
{
(void)e;
s_approval_decision = UI_APPROVAL_ALWAYS;
}
static void on_use_viewed(lv_event_t *e)
{
(void)e;
if (!s_have_mnemonic) {
set_status("No generated mnemonic yet");
return;
}
s_done = true;
}
static void build_view_screen(void)
{
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
style_screen(scr);
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "Mnemonic Preview");
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_t *mn = lv_label_create(scr);
lv_obj_set_width(mn, DISPLAY_WIDTH - 20);
lv_label_set_long_mode(mn, LV_LABEL_LONG_WRAP);
lv_label_set_text(mn, s_have_mnemonic ? s_selected_mnemonic : "No mnemonic generated yet");
lv_obj_align(mn, LV_ALIGN_TOP_LEFT, 10, 40);
lv_obj_set_style_text_color(mn, lv_color_hex(0xFFFFFF), 0);
lv_obj_t *use_btn = lv_btn_create(scr);
lv_obj_set_size(use_btn, 150, 36);
lv_obj_align(use_btn, LV_ALIGN_BOTTOM_LEFT, 8, -8);
lv_obj_add_event_cb(use_btn, on_use_viewed, 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 This");
lv_obj_center(use_lbl);
lv_obj_t *back_btn = lv_btn_create(scr);
lv_obj_set_size(back_btn, 150, 36);
lv_obj_align(back_btn, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
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_status_label = NULL;
}
static void on_view(lv_event_t *e)
{
(void)e;
if (!s_have_mnemonic) {
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
if (generate_mnemonic_12(s_selected_mnemonic, sizeof(s_selected_mnemonic)) == 0) {
s_have_mnemonic = true;
}
}
build_view_screen();
}
static void build_main_menu(void)
{
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
style_screen(scr);
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "n_signer CYD");
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, 8);
lv_obj_t *subtitle = lv_label_create(scr);
lv_label_set_text(subtitle, "Select startup mode");
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 36);
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xFFFFFF), 0);
lv_obj_t *btn_gen = lv_btn_create(scr);
lv_obj_set_size(btn_gen, DISPLAY_WIDTH - 40, 42);
lv_obj_align(btn_gen, LV_ALIGN_TOP_MID, 0, 64);
lv_obj_add_event_cb(btn_gen, on_generate, LV_EVENT_RELEASED, NULL);
style_button(btn_gen);
lv_obj_t *lbl_gen = lv_label_create(btn_gen);
lv_label_set_text(lbl_gen, "Generate New Mnemonic");
lv_obj_center(lbl_gen);
lv_obj_t *btn_enter = lv_btn_create(scr);
lv_obj_set_size(btn_enter, DISPLAY_WIDTH - 40, 42);
lv_obj_align(btn_enter, LV_ALIGN_TOP_MID, 0, 112);
lv_obj_add_event_cb(btn_enter, on_enter, LV_EVENT_RELEASED, NULL);
style_button(btn_enter);
lv_obj_t *lbl_enter = lv_label_create(btn_enter);
lv_label_set_text(lbl_enter, "Enter Existing Mnemonic");
lv_obj_center(lbl_enter);
lv_obj_t *btn_view = lv_btn_create(scr);
lv_obj_set_size(btn_view, DISPLAY_WIDTH - 40, 42);
lv_obj_align(btn_view, LV_ALIGN_TOP_MID, 0, 160);
lv_obj_add_event_cb(btn_view, on_view, LV_EVENT_RELEASED, NULL);
style_button(btn_view);
lv_obj_t *lbl_view = lv_label_create(btn_view);
lv_label_set_text(lbl_view, "View Current Mnemonic");
lv_obj_center(lbl_view);
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_BOTTOM_MID, 0, -10);
}
void ui_show_approval_prompt(const char *caller_pubkey_hex, int kind, const char *content, uint32_t timeout_ms)
{
if (!s_lvgl_inited) {
return;
}
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
style_screen(scr);
char caller_line[64];
char kind_line[32];
char content_line[96];
if (caller_pubkey_hex != NULL && strlen(caller_pubkey_hex) >= 12) {
snprintf(caller_line, sizeof(caller_line), "from %.6s..%.4s",
caller_pubkey_hex, caller_pubkey_hex + strlen(caller_pubkey_hex) - 4);
} else {
snprintf(caller_line, sizeof(caller_line), "from %s",
caller_pubkey_hex ? caller_pubkey_hex : "anon");
}
snprintf(kind_line, sizeof(kind_line), "kind: %d", kind);
snprintf(content_line, sizeof(content_line), "%.80s", content ? content : "");
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "Approve sign_event?");
lv_obj_set_style_text_color(title, lv_color_hex(0xFF0000), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
lv_obj_t *caller_lbl = lv_label_create(scr);
lv_label_set_text(caller_lbl, caller_line);
lv_obj_set_style_text_color(caller_lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(caller_lbl, LV_ALIGN_TOP_LEFT, 8, 34);
lv_obj_t *kind_lbl = lv_label_create(scr);
lv_label_set_text(kind_lbl, kind_line);
lv_obj_set_style_text_color(kind_lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(kind_lbl, LV_ALIGN_TOP_LEFT, 8, 52);
lv_obj_t *content_lbl = lv_label_create(scr);
lv_obj_set_width(content_lbl, DISPLAY_WIDTH - 16);
lv_label_set_long_mode(content_lbl, LV_LABEL_LONG_WRAP);
lv_label_set_text(content_lbl, content_line);
lv_obj_set_style_text_color(content_lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(content_lbl, LV_ALIGN_TOP_LEFT, 8, 72);
lv_obj_t *deny_btn = lv_btn_create(scr);
lv_obj_set_size(deny_btn, 98, 34);
lv_obj_align(deny_btn, LV_ALIGN_BOTTOM_LEFT, 8, -8);
lv_obj_add_event_cb(deny_btn, on_approve_deny, LV_EVENT_RELEASED, NULL);
style_button(deny_btn);
lv_obj_t *deny_lbl = lv_label_create(deny_btn);
lv_label_set_text(deny_lbl, "Deny");
lv_obj_center(deny_lbl);
lv_obj_t *once_btn = lv_btn_create(scr);
lv_obj_set_size(once_btn, 98, 34);
lv_obj_align(once_btn, LV_ALIGN_BOTTOM_MID, 0, -8);
lv_obj_add_event_cb(once_btn, on_approve_once, LV_EVENT_RELEASED, NULL);
style_button(once_btn);
lv_obj_t *once_lbl = lv_label_create(once_btn);
lv_label_set_text(once_lbl, "Approve");
lv_obj_center(once_lbl);
lv_obj_t *always_btn = lv_btn_create(scr);
lv_obj_set_size(always_btn, 98, 34);
lv_obj_align(always_btn, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
lv_obj_add_event_cb(always_btn, on_approve_always, LV_EVENT_RELEASED, NULL);
style_button(always_btn);
lv_obj_t *always_lbl = lv_label_create(always_btn);
lv_label_set_text(always_lbl, "Always");
lv_obj_center(always_lbl);
s_approval_countdown = lv_label_create(scr);
lv_obj_set_style_text_color(s_approval_countdown, lv_color_hex(0xFF0000), 0);
lv_obj_align(s_approval_countdown, LV_ALIGN_BOTTOM_MID, 0, -46);
s_approval_decision = UI_APPROVAL_TIMEOUT;
s_approval_deadline_us = esp_timer_get_time() + ((int64_t)timeout_ms * 1000);
}
ui_approval_decision_t ui_wait_for_approval(uint32_t timeout_ms)
{
if (!s_lvgl_inited) {
return UI_APPROVAL_TIMEOUT;
}
int64_t deadline_us = esp_timer_get_time() + ((int64_t)timeout_ms * 1000);
while (1) {
int64_t now = esp_timer_get_time();
if (s_approval_decision != UI_APPROVAL_TIMEOUT) {
return s_approval_decision;
}
if (now >= deadline_us) {
return UI_APPROVAL_TIMEOUT;
}
if (s_approval_countdown != NULL) {
int remain_s = (int)((deadline_us - now + 999999) / 1000000);
if (remain_s < 0) {
remain_s = 0;
}
char buf[32];
snprintf(buf, sizeof(buf), "timeout: %ds", remain_s);
lv_label_set_text(s_approval_countdown, buf);
}
lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(5));
}
}
static void show_event_log_screen(void)
{
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
style_screen(scr);
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "recent events");
lv_obj_set_style_text_color(title, lv_color_hex(0xFF0000), 0);
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 6);
lv_obj_t *sub = lv_label_create(scr);
lv_label_set_text(sub, "newest first");
lv_obj_set_style_text_color(sub, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(sub, LV_ALIGN_TOP_RIGHT, -8, 8);
int y = 30;
for (int i = 0; i < s_event_count; ++i) {
lv_obj_t *line = lv_label_create(scr);
lv_obj_set_width(line, DISPLAY_WIDTH - 16);
lv_label_set_long_mode(line, LV_LABEL_LONG_DOT);
lv_label_set_text(line, s_event_lines[i]);
lv_obj_set_style_text_color(line, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(line, LV_ALIGN_TOP_LEFT, 8, y);
y += 24;
}
if (s_event_count == 0) {
lv_obj_t *empty = lv_label_create(scr);
lv_label_set_text(empty, "No events yet");
lv_obj_set_style_text_color(empty, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(empty, LV_ALIGN_TOP_LEFT, 8, 36);
}
lv_obj_t *ready = lv_label_create(scr);
lv_label_set_text(ready, "ready");
lv_obj_set_style_text_color(ready, lv_color_hex(0xFF0000), 0);
lv_obj_align(ready, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
}
void ui_show_idle(const char *npub_full, const char *mnemonic_full, const char *version_text)
{
if (!s_lvgl_inited) {
return;
}
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
style_screen(scr);
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "nostr npub");
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 6);
lv_obj_t *npub = lv_label_create(scr);
lv_obj_set_width(npub, DISPLAY_WIDTH - 16);
lv_label_set_long_mode(npub, LV_LABEL_LONG_WRAP);
lv_label_set_text(npub, npub_full ? npub_full : "");
lv_obj_set_style_text_color(npub, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(npub, LV_ALIGN_TOP_LEFT, 8, 24);
lv_obj_t *mn_title = lv_label_create(scr);
lv_label_set_text(mn_title, "mnemonic");
lv_obj_set_style_text_color(mn_title, lv_color_hex(0xFFFFFF), 0);
lv_obj_align_to(mn_title, npub, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 8);
lv_obj_t *mn = lv_label_create(scr);
lv_obj_set_width(mn, DISPLAY_WIDTH - 16);
lv_label_set_long_mode(mn, LV_LABEL_LONG_WRAP);
lv_label_set_text(mn, mnemonic_full ? mnemonic_full : "");
lv_obj_set_style_text_color(mn, lv_color_hex(0xFFFFFF), 0);
lv_obj_align_to(mn, mn_title, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 4);
lv_obj_t *ver = lv_label_create(scr);
lv_label_set_text(ver, version_text ? version_text : "");
lv_obj_set_style_text_color(ver, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(ver, LV_ALIGN_BOTTOM_LEFT, 8, -8);
lv_obj_t *ready = lv_label_create(scr);
lv_label_set_text(ready, "ready");
lv_obj_set_style_text_color(ready, lv_color_hex(0xFF0000), 0);
lv_obj_align(ready, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
}
void ui_add_event(const char *action, const char *caller_pubkey_hex, const char *extra)
{
char caller_short[20] = "anon";
char line[UI_EVENT_LINE_MAX];
if (!s_lvgl_inited) {
return;
}
if (caller_pubkey_hex != NULL && strlen(caller_pubkey_hex) >= 12) {
snprintf(caller_short, sizeof(caller_short), "%.6s..%.4s",
caller_pubkey_hex, caller_pubkey_hex + strlen(caller_pubkey_hex) - 4);
} else if (caller_pubkey_hex != NULL && caller_pubkey_hex[0] != '\0') {
snprintf(caller_short, sizeof(caller_short), "%.16s", caller_pubkey_hex);
}
snprintf(line, sizeof(line), "%s %s %s",
action ? action : "event",
caller_short,
extra ? extra : "");
for (int i = (s_event_count < UI_EVENT_LOG_MAX ? s_event_count : UI_EVENT_LOG_MAX - 1); i > 0; --i) {
strncpy(s_event_lines[i], s_event_lines[i - 1], UI_EVENT_LINE_MAX - 1);
s_event_lines[i][UI_EVENT_LINE_MAX - 1] = '\0';
}
strncpy(s_event_lines[0], line, UI_EVENT_LINE_MAX - 1);
s_event_lines[0][UI_EVENT_LINE_MAX - 1] = '\0';
if (s_event_count < UI_EVENT_LOG_MAX) {
s_event_count++;
}
show_event_log_screen();
}
void ui_pump(void)
{
if (!s_lvgl_inited) {
return;
}
lv_timer_handler();
}
int ui_init_runtime(void)
{
return (ui_lvgl_init_once() == ESP_OK) ? 0 : -1;
}
int ui_run_until_working(char out_mnemonic[256])
{
if (out_mnemonic == NULL) {
return -1;
}
if (ui_lvgl_init_once() != ESP_OK) {
return -1;
}
s_done = false;
s_have_mnemonic = false;
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
build_main_menu();
while (!s_done) {
lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(5));
}
memset(out_mnemonic, 0, 256);
strncpy(out_mnemonic, s_selected_mnemonic, 255);
ESP_LOGI(TAG, "Mnemonic selected via LVGL UI");
return 0;
}