diff --git a/app/keypad/keypad.c b/app/keypad/keypad.c index a1e7402..4c94fe0 100644 --- a/app/keypad/keypad.c +++ b/app/keypad/keypad.c @@ -7,6 +7,16 @@ #define KEYPAD_GPIO_COL_OFF GPIO_KEYPAD_COL_0 #define KEYPAD_DEBOUNCE_THRESHOLD 10 +#define KEYPAD_LONG_PRESS_THRESHOLD 100 + +static inline void keypad_report_key(keypad_key_t key, bool is_long) { + g_ui_ctx.keypad.last_key = key; + g_ui_ctx.keypad.last_key_long = is_long; + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyIndexedFromISR(APP_TASK(ui), UI_NOTIFICATION_IDX, UI_KEY_EVT, eSetBits, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} void keypad_scan_tick() { for (int i = 0; i < KEYPAD_COLS; i++) { @@ -16,17 +26,17 @@ void keypad_scan_tick() { uint32_t duration = g_ui_ctx.keypad.matrix_state[key]; g_ui_ctx.keypad.matrix_state[key] = 0; - if (duration > KEYPAD_DEBOUNCE_THRESHOLD) { - g_ui_ctx.keypad.last_key = key; - g_ui_ctx.keypad.last_key_duration = duration; - - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xTaskNotifyIndexedFromISR(APP_TASK(ui), UI_NOTIFICATION_IDX, UI_KEY_EVT, eSetBits, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + if (duration > KEYPAD_DEBOUNCE_THRESHOLD && duration < KEYPAD_LONG_PRESS_THRESHOLD) { + keypad_report_key(key, false); break; } } else { g_ui_ctx.keypad.matrix_state[key]++; + if (g_ui_ctx.keypad.matrix_state[key] >= KEYPAD_LONG_PRESS_THRESHOLD && g_ui_ctx.keypad.matrix_state[key] < (KEYPAD_LONG_PRESS_THRESHOLD * 2)) { + g_ui_ctx.keypad.matrix_state[key] = KEYPAD_LONG_PRESS_THRESHOLD * 2; + keypad_report_key(key, true); + break; + } } } diff --git a/app/keypad/keypad.h b/app/keypad/keypad.h index a104f85..c4d4aef 100644 --- a/app/keypad/keypad.h +++ b/app/keypad/keypad.h @@ -2,6 +2,7 @@ #define _KEYPAD_H_ #include +#include #define KEYPAD_ROWS 4 #define KEYPAD_COLS 3 @@ -29,7 +30,7 @@ typedef enum { typedef struct { uint32_t matrix_state[KEYPAD_ROWS * KEYPAD_COLS]; - uint32_t last_key_duration; + bool last_key_long; keypad_key_t last_key; uint8_t current_row; } keypad_t; diff --git a/app/screen/screen.c b/app/screen/screen.c index ad5a4d3..3a0532c 100644 --- a/app/screen/screen.c +++ b/app/screen/screen.c @@ -243,6 +243,20 @@ hal_err_t screen_draw_char(const screen_text_ctx_t* ctx, char c) { return screen_draw_glyph(ctx, screen_lookup_glyph(ctx->font, c)); } +hal_err_t screen_draw_chars(screen_text_ctx_t* ctx, const char* str, int len) { + while(len--) { + const glyph_t* glyph = screen_lookup_glyph(ctx->font, *(str++)); + + if (screen_draw_glyph(ctx, glyph) != HAL_SUCCESS) { + return HAL_FAIL; + } + + ctx->x += glyph->xAdvance; + } + + return HAL_SUCCESS; +} + hal_err_t screen_draw_string(screen_text_ctx_t* ctx, const char* str) { char c; diff --git a/app/screen/screen.h b/app/screen/screen.h index 59dc86d..ff41024 100644 --- a/app/screen/screen.h +++ b/app/screen/screen.h @@ -69,6 +69,7 @@ hal_err_t screen_draw_glyph(const screen_text_ctx_t* ctx, const glyph_t* glyph); // High level API hal_err_t screen_draw_char(const screen_text_ctx_t* ctx, char c); +hal_err_t screen_draw_chars(screen_text_ctx_t* ctx, const char* str, int len); hal_err_t screen_draw_string(screen_text_ctx_t* ctx, const char* str); hal_err_t screen_draw_text(screen_text_ctx_t* ctx, uint16_t max_x, uint16_t max_y, const uint8_t* text, size_t len); hal_err_t screen_fill_area(const screen_area_t* area, uint16_t color); diff --git a/app/ui/input.c b/app/ui/input.c index 3cc2dbb..e79c212 100644 --- a/app/ui/input.c +++ b/app/ui/input.c @@ -6,6 +6,7 @@ #include "theme.h" #include "crypto/bip39.h" #include "crypto/util.h" +#include "keypad/keypad.h" #include "ui/ui.h" #include "ui/ui_internal.h" @@ -14,8 +15,22 @@ #define KEY_BACKSPACE 0x08 #define KEY_RETURN 0x0d +#define KEY_ESCAPE 0x1b + #define WORD_MAX_LEN 8 +#define KEYBOARD_TOP_Y (SCREEN_HEIGHT - (TH_KEYBOARD_KEY_SIZE * 3)) + +#define KEYBOARD_ROW1_LEN 10 +#define KEYBOARD_ROW2_LEN 9 +#define KEYBOARD_ROW3_LEN 7 + +#define KEYBOARD_ROW1_LIMIT KEYBOARD_ROW1_LEN +#define KEYBOARD_ROW2_LIMIT (KEYBOARD_ROW1_LIMIT + KEYBOARD_ROW2_LEN) +#define KEYBOARD_ROW3_LIMIT (KEYBOARD_ROW2_LIMIT + KEYBOARD_ROW3_LEN) + + + const char KEYPAD_TO_DIGIT[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', DIG_INV, '0', DIG_INV, DIG_INV, DIG_INV}; const char KEYBOARD_MAP[] = { 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', @@ -144,8 +159,116 @@ app_err_t input_pin() { } } +static inline void input_keyboard_render_key(char c, uint16_t x, uint16_t y, bool selected) { + screen_area_t key_area = { .x = x, .y = y, .width = TH_KEYBOARD_KEY_SIZE, .height = TH_KEYBOARD_KEY_SIZE }; + screen_text_ctx_t ctx = { .font = TH_FONT_TEXT, .fg = TH_COLOR_TEXT_FG, .y = y }; + + const glyph_t* glyph = screen_lookup_glyph(ctx.font, c); + ctx.bg = selected ? TH_KEYBOARD_KEY_SELECTED_BG : TH_KEYBOARD_KEY_BG; + ctx.x = x + ((TH_KEYBOARD_KEY_SIZE - glyph->width) / 2); + + screen_fill_area(&key_area, ctx.bg); + screen_draw_glyph(&ctx, glyph); +} + +static inline void input_keyboard_render(int idx) { + int i = 0; + + while (i < KEYBOARD_ROW1_LIMIT) { + input_keyboard_render_key(KEYBOARD_MAP[i], (i * TH_KEYBOARD_KEY_SIZE), KEYBOARD_TOP_Y, idx == i); + i++; + } + + while (i < KEYBOARD_ROW2_LIMIT) { + input_keyboard_render_key(KEYBOARD_MAP[i], ((i - 10) * TH_KEYBOARD_KEY_SIZE), (KEYBOARD_TOP_Y + TH_KEYBOARD_KEY_SIZE), idx == i); + i++; + } + + screen_area_t padding = { + .x = KEYBOARD_ROW2_LEN * TH_KEYBOARD_KEY_SIZE, + .y = KEYBOARD_TOP_Y + TH_KEYBOARD_KEY_SIZE, + .width = TH_KEYBOARD_KEY_SIZE, + .height = TH_KEYBOARD_KEY_SIZE + }; + + screen_fill_area(&padding, TH_KEYBOARD_KEY_BG); + + while (i < KEYBOARD_ROW3_LIMIT) { + input_keyboard_render_key(KEYBOARD_MAP[i], ((i - 19) * TH_KEYBOARD_KEY_SIZE), (KEYBOARD_TOP_Y + (TH_KEYBOARD_KEY_SIZE * 2)), idx == i); + i++; + } + + padding.x = KEYBOARD_ROW3_LEN * TH_KEYBOARD_KEY_SIZE; + padding.y = KEYBOARD_TOP_Y + (TH_KEYBOARD_KEY_SIZE * 2); + padding.width = (TH_KEYBOARD_KEY_SIZE * 3); + + screen_fill_area(&padding, TH_KEYBOARD_KEY_BG); +} + static char input_keyboard(int *idx) { - return KEY_RETURN; + while(1) { + input_keyboard_render(*idx); + + switch(ui_wait_keypress(portMAX_DELAY)) { + case KEYPAD_KEY_UP: + if (*idx >= KEYBOARD_ROW2_LIMIT) { + *idx -= KEYBOARD_ROW2_LEN; + } else if (*idx >= KEYBOARD_ROW1_LIMIT) { + *idx -= KEYBOARD_ROW1_LEN; + } + break; + case KEYPAD_KEY_LEFT: + if ((*idx > KEYBOARD_ROW2_LIMIT) || + ((*idx > KEYBOARD_ROW1_LIMIT) && (*idx < KEYBOARD_ROW2_LIMIT)) || + ((*idx > 0) && (*idx < KEYBOARD_ROW1_LIMIT))) { + (*idx)--; + } + break; + case KEYPAD_KEY_RIGHT: + if ((*idx < (KEYBOARD_ROW1_LIMIT - 1)) || + ((*idx < (KEYBOARD_ROW2_LIMIT - 1)) && (*idx >= KEYBOARD_ROW1_LIMIT)) || + ((*idx < (KEYBOARD_ROW3_LIMIT - 1)) && (*idx >= KEYBOARD_ROW2_LIMIT))) { + (*idx)++; + } + break; + case KEYPAD_KEY_DOWN: + if (*idx < KEYBOARD_ROW1_LIMIT) { + *idx = APP_MIN(*idx + KEYBOARD_ROW1_LEN, (KEYBOARD_ROW2_LIMIT - 1)); + } else if (*idx < KEYBOARD_ROW2_LIMIT) { + *idx = APP_MIN(*idx + KEYBOARD_ROW2_LEN, (KEYBOARD_ROW3_LIMIT - 1)); + } + break; + case KEYPAD_KEY_BACK: + return g_ui_ctx.keypad.last_key_long ? KEY_ESCAPE : KEY_BACKSPACE; + case KEYPAD_KEY_CONFIRM: + return g_ui_ctx.keypad.last_key_long ? KEY_RETURN : KEYBOARD_MAP[*idx]; + default: + break; + } + } +} + +static void input_render_text_field(char* str, uint16_t y, int len, int suggestion_len) { + screen_text_ctx_t ctx = { + .font = TH_FONT_TEXT, + .fg = TH_TEXT_FIELD_FG, + .bg = TH_TEXT_FIELD_BG, + .x = TH_TEXT_FIELD_MARGIN, + .y = y + }; + + screen_area_t field_area = { + .x = TH_TEXT_FIELD_MARGIN, + .y = y, + .width = SCREEN_WIDTH - (TH_TEXT_FIELD_MARGIN * 2), + .height = TH_TEXT_FIELD_HEIGHT + }; + + screen_fill_area(&field_area, ctx.bg); + + screen_draw_chars(&ctx, str, len); + ctx.fg = TH_TEXT_FIELD_SUGGESTION_FG; + screen_draw_chars(&ctx, &str[len], suggestion_len); } static void input_mnemonic_title(uint8_t i) { @@ -153,31 +276,43 @@ static void input_mnemonic_title(uint8_t i) { int base_len = strlen(base_title); int buf_len = base_len + 4; uint8_t title_buf[buf_len]; - char* title = (char *) u32toa(i, title_buf, buf_len); + char* title = (char *) u32toa(i + 1, title_buf, buf_len); title -= base_len; memcpy(title, base_title, base_len); dialog_title(title); } -static void input_mnemonic_render(char* word, int len, uint16_t idx) { +static void input_mnemonic_render(const char* word, int len, uint16_t idx) { + int suggestion_len; + if (idx != UINT16_MAX) { + word = BIP39_WORDLIST_ENGLISH[idx]; + suggestion_len = strlen(word) - len; + } else { + suggestion_len = 0; + } + + input_render_text_field(word, TH_TITLE_HEIGHT + TH_TEXT_FIELD_MARGIN, len, suggestion_len); } static uint16_t input_mnemonic_lookup(char* word, int len, uint16_t idx) { + if (len == 0) { + return UINT16_MAX; + } + while (idx < BIP39_WORD_COUNT) { int cmp = strncmp(word, BIP39_WORDLIST_ENGLISH[idx], len); - if (!cmp) { - break; + if (cmp == 0) { + return idx; } else if (cmp < 0) { - idx++; + return UINT16_MAX; } else { - idx = UINT16_MAX; - break; + idx++; } } - return idx; + return UINT16_MAX; } static uint16_t input_mnemonic_get_word(int i) { @@ -201,6 +336,8 @@ static uint16_t input_mnemonic_get_word(int i) { len--; idx = input_mnemonic_lookup(word, len, 0); } + } else if (c == KEY_ESCAPE) { + //TODO: implement backtracking } else if (len < WORD_MAX_LEN) { word[len++] = c; idx = input_mnemonic_lookup(word, len, (idx == UINT16_MAX ? 0 : idx)); @@ -209,6 +346,8 @@ static uint16_t input_mnemonic_get_word(int i) { } app_err_t input_mnemonic() { + dialog_footer(TH_TITLE_HEIGHT); + for (int i = 0; i < g_ui_cmd.params.input_mnemo.len; i++) { g_ui_cmd.params.input_mnemo.indexes[i] = input_mnemonic_get_word(i); } diff --git a/app/ui/theme.h b/app/ui/theme.h index da0be96..a6e3b36 100644 --- a/app/ui/theme.h +++ b/app/ui/theme.h @@ -64,4 +64,14 @@ #define TH_PIN_FIELD_VERTICAL_MARGIN 16 #define TH_PIN_FIELD_DIGIT_MARGIN TH_DEF_LEFT_MARGIN +#define TH_KEYBOARD_KEY_SIZE 32 +#define TH_KEYBOARD_KEY_BG SCREEN_RGB(37, 41, 56) +#define TH_KEYBOARD_KEY_SELECTED_BG SCREEN_RGB(8, 183, 156) + +#define TH_TEXT_FIELD_BG SCREEN_RGB(37, 41, 56) +#define TH_TEXT_FIELD_FG TH_COLOR_FG +#define TH_TEXT_FIELD_SUGGESTION_FG SCREEN_RGB(127, 127, 127) +#define TH_TEXT_FIELD_MARGIN 8 +#define TH_TEXT_FIELD_HEIGHT 30 + #endif diff --git a/app/ui/ui.c b/app/ui/ui.c index da988fd..02bf67e 100644 --- a/app/ui/ui.c +++ b/app/ui/ui.c @@ -172,7 +172,7 @@ core_evt_t ui_read_mnemonic(uint16_t* indexes, uint32_t* len) { return CORE_EVT_UI_CANCELLED; } - g_ui_cmd.type = UI_CMD_INPUT_PIN; + g_ui_cmd.type = UI_CMD_INPUT_MNEMO; g_ui_cmd.params.input_mnemo.indexes = indexes; g_ui_cmd.params.input_mnemo.len = *len;