ui: add a better UI for index selection vs PIN entry
What changed, and why it matters
This commit is a user-interface refactor for the Blockstream Jade hardware wallet. It replaces a PIN-only digit-entry screen with a shared 'digit entry' widget that can also accept shorter numeric indices (for things like BIP85 child mnemonic indexes or account indexes). The PIN entry behavior itself is preserved: PINs still require all six digits. The main functional change is that index entry can now be shorter than six digits, which is a usability improvement rather than a security fix. There is no vendor statement that this change addresses a security vulnerability.
No immediate security action is required. Treat this as a normal feature/usability commit. If reviewing for security, verify that callers using DIGIT_ENTRY_INDEX correctly validate the returned number against their documented maximums and that the new 'enter' path cannot be triggered for PIN entry (the code asserts this, but a quick static check is worthwhile).
Security signals we found
UI-only refactor with no change to PIN size, PIN validation, key derivation, or storage
New index-entry mode allows shorter numeric input, bounded by existing caller-side maximum checks
No buffer size changes: arrays remain DIGIT_ENTRY_SIZE (6) bytes
No vendor claim of security relevance, CVE, or researcher attribution in commit or supplied references
Evidence from the diff
The change renames pin_insert_t/pin.c to digit_entry_t/digit_entry.c and introduces an entry_type field (DIGIT_ENTRY_PIN vs DIGIT_ENTRY_INDEX). For DIGIT_ENTRY_PIN, get_max_digit_entry_char() still returns only 0-9 + backspace, and callers still require exactly DIGIT_ENTRY_SIZE (6) digits. For DIGIT_ENTRY_INDEX, an ‘enter’ character is added to allow completing entry before all six digits are filled. The conversion function get_entry_as_number() now uses selected_digit instead of a fixed size, and callers validate the returned index against BIP85_INDEX_MAX / ACCOUNT_INDEX_MAX. The font glyph for ‘~’ was repurposed as a checkmark icon for the new enter button. No cryptographic, storage, or protocol logic was changed.
Changed components
main/ui/digit_entry.c (new shared digit-entry widget)main/ui/pin.c (removed)main/ui.h (digit_entry_t API and constants)main/process/auth_user.c (PIN entry via new widget)main/process/dashboard.c (reset confirmation and wallet-erase PIN)main/process/mnemonic.c (BIP85 index entry)main/qrmode.c (xpub/account index entry)main/amalgamated.c (build include update)main/fonts/DejaVuSans24.c (checkmark glyph)Inspect captured patch +422 / −382
diff --git a/main/amalgamated.c b/main/amalgamated.c
index 659641f..94a29a3 100644
--- a/main/amalgamated.c
+++ b/main/amalgamated.c
@@ -147,6 +147,7 @@ void __wrap_abort(void);
#include "./ui/dashboard.c"
#include "./ui/descriptor.c"
#include "./ui/dialogs.c"
+#include "./ui/digit_entry.c"
#ifndef CONFIG_LIBJADE
#include "./ui/keyboard.c"
#endif // CONFIG_LIBJADE
@@ -154,7 +155,6 @@ void __wrap_abort(void);
#include "./ui/multisig.c"
#include "./ui/ota.c"
#include "./ui/otpauth.c"
-#include "./ui/pin.c"
#include "./ui/qrmode.c"
#include "./ui/select_registered_wallet.c"
#include "./ui/sign_identity.c"
diff --git a/main/fonts/DejaVuSans24.c b/main/fonts/DejaVuSans24.c
index 9677691..e313f6a 100644
--- a/main/fonts/DejaVuSans24.c
+++ b/main/fonts/DejaVuSans24.c
@@ -326,8 +326,12 @@ const unsigned char tft_Dejavu24[] = { 0x00, 0x17, 0x00, 0x00,
// '}'
0x7D, 0x01, 0x09, 0x16, 0x03, 0x0F, 0xE0, 0x78, 0x0E, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xE1,
0xF1, 0xC0, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x07, 0x0F, 0x07, 0x00,
- // '~'
- 0x7E, 0x09, 0x0F, 0x05, 0x03, 0x14, 0x00, 0x00, 0x7C, 0x05, 0xFE, 0x1E, 0x1F, 0xE0, 0x0F, 0x80,
+ // '~' - repurposed for 'checkmark'
+ 0x7E, 0x00, 0x18, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00,
+ 0x00, 0x18, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0x60, 0x00, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x01,
+ 0x80, 0x00, 0x03, 0x80, 0x00, 0x03, 0x00, 0x30, 0x07, 0x00, 0x38, 0x0E, 0x00, 0x1C, 0x0C, 0x00, 0x0E, 0x1C, 0x00,
+ 0x07, 0x38, 0x00, 0x03, 0xB0, 0x00, 0x01, 0xF0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
// Terminator
0xFF };
diff --git a/main/process/auth_user.c b/main/process/auth_user.c
index a6d0d04..ac61af5 100644
--- a/main/process/auth_user.c
+++ b/main/process/auth_user.c
@@ -33,7 +33,7 @@ static void check_wallet_erase_pin(jade_process_t* process, const uint8_t* pin_e
{
JADE_ASSERT(pin_entered);
- uint8_t pin_erase[PIN_SIZE];
+ uint8_t pin_erase[DIGIT_ENTRY_SIZE];
if (pin_len == sizeof(pin_erase) && storage_get_wallet_erase_pin(pin_erase, sizeof(pin_erase))
&& !sodium_memcmp(pin_erase, pin_entered, pin_len)) {
// 'Wallet erase' PIN entered. Erase wallet keys and reset passphrase setting
@@ -56,7 +56,7 @@ static bool get_pin_get_aeskey(jade_process_t* process, const char* title, uint8
JADE_ASSERT(process);
JADE_ASSERT(title);
JADE_ASSERT(pin);
- JADE_ASSERT(pin_len == PIN_SIZE);
+ JADE_ASSERT(pin_len == DIGIT_ENTRY_SIZE);
JADE_ASSERT(aeskey);
JADE_ASSERT(aes_len == AES_KEY_LEN_256);
@@ -79,30 +79,30 @@ static bool get_pin_get_aeskey(jade_process_t* process, const char* title, uint8
msg = NULL;
}
- pin_insert_t pin_insert = { .initial_state = RANDOM, .pin_digits_shown = false };
- JADE_ASSERT(sizeof(pin_insert.pin) == pin_len);
- make_pin_insert_activity(&pin_insert, title, msg);
- JADE_ASSERT(pin_insert.activity);
- SENSITIVE_PUSH(&pin_insert, sizeof(pin_insert_t));
+ digit_entry_t digit_entry = { .entry_type = DIGIT_ENTRY_PIN, .initial_state = RANDOM, .digits_shown = false };
+ JADE_ASSERT(sizeof(digit_entry.digit) == pin_len);
+ make_digit_entry_activity(&digit_entry, title, msg);
+ JADE_ASSERT(digit_entry.activity);
+ SENSITIVE_PUSH(&digit_entry, sizeof(digit_entry_t));
// If getting PIN via QRs, free gui memory before attempting QR roundtrip
- gui_set_current_activity_ex(pin_insert.activity, process->ctx.source == SOURCE_INTERNAL);
+ gui_set_current_activity_ex(digit_entry.activity, process->ctx.source == SOURCE_INTERNAL);
// In a debug unattended ci build, use hardcoded pin after a short delay
#ifndef CONFIG_DEBUG_UNATTENDED_CI
- if (!run_pin_entry_loop(&pin_insert)) {
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned entering pin
jade_process_reject_message(process, CBOR_RPC_USER_CANCELLED, "User abandonded pin entry");
- SENSITIVE_POP(&pin_insert);
+ SENSITIVE_POP(&digit_entry);
return false;
}
#else
vTaskDelay(CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
- const uint8_t testpin[sizeof(pin_insert.pin)] = { 0, 1, 2, 3, 4, 5 };
- memcpy(pin_insert.pin, testpin, sizeof(testpin));
+ const uint8_t testpin[sizeof(digit_entry.digit)] = { 0, 1, 2, 3, 4, 5 };
+ memcpy(digit_entry.digit, testpin, sizeof(testpin));
#endif
- memcpy(pin, pin_insert.pin, sizeof(pin_insert.pin));
- SENSITIVE_POP(&pin_insert);
+ memcpy(pin, digit_entry.digit, sizeof(digit_entry.digit));
+ SENSITIVE_POP(&digit_entry);
const char* message[] = { "Checking..." };
display_message_activity(message, 1);
@@ -118,54 +118,54 @@ static bool set_pin_get_aeskey(jade_process_t* process, const char* title, uint8
JADE_ASSERT(process);
JADE_ASSERT(title);
JADE_ASSERT(pin);
- JADE_ASSERT(pin_len == PIN_SIZE);
+ JADE_ASSERT(pin_len == DIGIT_ENTRY_SIZE);
JADE_ASSERT(aeskey);
JADE_ASSERT(aes_len == AES_KEY_LEN_256);
// Enter PIN to lock mnemonic/key material.
// In a debug unattended ci build, use hardcoded pin after a short delay
- pin_insert_t pin_insert = { .initial_state = RANDOM, .pin_digits_shown = false };
- JADE_ASSERT(sizeof(pin_insert.pin) == pin_len);
- make_pin_insert_activity(&pin_insert, title, NULL);
- JADE_ASSERT(pin_insert.activity);
- SENSITIVE_PUSH(&pin_insert, sizeof(pin_insert_t));
+ digit_entry_t digit_entry = { .entry_type = DIGIT_ENTRY_PIN, .initial_state = RANDOM, .digits_shown = false };
+ JADE_ASSERT(sizeof(digit_entry.digit) == pin_len);
+ make_digit_entry_activity(&digit_entry, title, NULL);
+ JADE_ASSERT(digit_entry.activity);
+ SENSITIVE_PUSH(&digit_entry, sizeof(digit_entry_t));
while (true) {
- reset_pin(&pin_insert, title);
+ reset_digit_entry(&digit_entry, title);
// If getting PIN via QRs, free gui memory before attempting QR roundtrip
- gui_set_current_activity_ex(pin_insert.activity, process->ctx.source == SOURCE_INTERNAL);
+ gui_set_current_activity_ex(digit_entry.activity, process->ctx.source == SOURCE_INTERNAL);
#ifndef CONFIG_DEBUG_UNATTENDED_CI
- if (!run_pin_entry_loop(&pin_insert)) {
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned setting new pin
jade_process_reject_message(process, CBOR_RPC_USER_CANCELLED, "User abandoned setting new PIN");
- SENSITIVE_POP(&pin_insert);
+ SENSITIVE_POP(&digit_entry);
return false;
}
#else
vTaskDelay(CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
- const uint8_t testpin[sizeof(pin_insert.pin)] = { 0, 1, 2, 3, 4, 5 };
- memcpy(pin_insert.pin, testpin, sizeof(testpin));
+ const uint8_t testpin[sizeof(digit_entry.digit)] = { 0, 1, 2, 3, 4, 5 };
+ memcpy(digit_entry.digit, testpin, sizeof(testpin));
#endif
// this is the first pin, copy it and clear screen fields and have the user confirm
- memcpy(pin, pin_insert.pin, sizeof(pin_insert.pin));
- reset_pin(&pin_insert, "Confirm PIN");
+ memcpy(pin, digit_entry.digit, sizeof(digit_entry.digit));
+ reset_digit_entry(&digit_entry, "Confirm PIN");
#ifndef CONFIG_DEBUG_UNATTENDED_CI
- if (!run_pin_entry_loop(&pin_insert)) {
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned second input - back to first ...
continue;
}
#else
vTaskDelay(CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
- memcpy(pin_insert.pin, testpin, sizeof(testpin));
+ memcpy(digit_entry.digit, testpin, sizeof(testpin));
#endif
// check that the two pins are the same
JADE_LOGD("Checking pins match");
- if (!sodium_memcmp(pin, pin_insert.pin, sizeof(pin_insert.pin))) {
+ if (!sodium_memcmp(pin, digit_entry.digit, sizeof(digit_entry.digit))) {
// Pins match
JADE_LOGI("New pin confirmed");
break;
@@ -175,12 +175,12 @@ static bool set_pin_get_aeskey(jade_process_t* process, const char* title, uint8
if (!await_continueback_activity(NULL, message, 2, true, NULL)) {
// Abandon setting new pin
jade_process_reject_message(process, CBOR_RPC_USER_CANCELLED, "User abandoned setting new PIN");
- SENSITIVE_POP(&pin_insert);
+ SENSITIVE_POP(&digit_entry);
return false;
}
}
}
- SENSITIVE_POP(&pin_insert);
+ SENSITIVE_POP(&digit_entry);
const char* message[] = { "Persisting PIN data..." };
display_message_activity(message, 1);
@@ -200,7 +200,7 @@ static bool get_pin_load_keys(jade_process_t* process, const bool suppress_pin_c
JADE_ASSERT(keychain_has_pin());
bool rslt = false;
- uint8_t pin[PIN_SIZE];
+ uint8_t pin[DIGIT_ENTRY_SIZE];
SENSITIVE_PUSH(pin, sizeof(pin));
uint8_t aeskey[AES_KEY_LEN_256];
SENSITIVE_PUSH(aeskey, sizeof(aeskey));
@@ -304,7 +304,7 @@ static bool set_pin_save_keys(jade_process_t* process)
JADE_ASSERT(!keychain_has_temporary());
bool rslt = false;
- uint8_t pin[PIN_SIZE];
+ uint8_t pin[DIGIT_ENTRY_SIZE];
SENSITIVE_PUSH(pin, sizeof(pin));
uint8_t aeskey[AES_KEY_LEN_256];
SENSITIVE_PUSH(aeskey, sizeof(aeskey));
diff --git a/main/process/dashboard.c b/main/process/dashboard.c
index 2d97a96..292763a 100644
--- a/main/process/dashboard.c
+++ b/main/process/dashboard.c
@@ -363,10 +363,10 @@ static void update_home_screen_menu(void)
// Function to print a pin into a char buffer.
// Assumes each pin component value is a single digit.
// NOTE: the passed buffer must be large enough.
-// (In normal circumstances that should be PIN_SIZE digits)
+// (In normal circumstances that should be DIGIT_ENTRY_SIZE digits)
static void format_pin(char* buf, const uint8_t buf_len, const uint8_t* pin, const size_t pin_len)
{
- JADE_ASSERT(pin_len == PIN_SIZE);
+ JADE_ASSERT(pin_len == DIGIT_ENTRY_SIZE);
JADE_ASSERT(buf_len > pin_len);
for (int i = 0; i < pin_len; ++i) {
@@ -663,8 +663,8 @@ static void offer_jade_reset(void)
}
// Force user to confirm a random number
- uint8_t num[PIN_SIZE];
- for (int i = 0; i < PIN_SIZE; ++i) {
+ uint8_t num[DIGIT_ENTRY_SIZE];
+ for (int i = 0; i < DIGIT_ENTRY_SIZE; ++i) {
num[i] = get_uniform_random_byte(10);
}
char pinstr[sizeof(num) + 1];
@@ -676,22 +676,22 @@ static void offer_jade_reset(void)
const int ret = snprintf(confirm_msg, sizeof(confirm_msg), "Confirm reset: %s", pinstr);
JADE_ASSERT(ret > 0 && ret < sizeof(confirm_msg));
- pin_insert_t pin_insert = { .initial_state = RANDOM, .pin_digits_shown = true };
- make_pin_insert_activity(&pin_insert, "Reset Jade", confirm_msg);
- JADE_ASSERT(pin_insert.activity);
- JADE_STATIC_ASSERT(sizeof(num) == sizeof(pin_insert.pin));
+ digit_entry_t digit_entry = { .entry_type = DIGIT_ENTRY_PIN, .initial_state = RANDOM, .digits_shown = true };
+ make_digit_entry_activity(&digit_entry, "Reset Jade", confirm_msg);
+ JADE_ASSERT(digit_entry.activity);
+ JADE_STATIC_ASSERT(sizeof(num) == sizeof(digit_entry.digit));
- gui_set_current_activity(pin_insert.activity);
- if (!run_pin_entry_loop(&pin_insert)) {
+ gui_set_current_activity(digit_entry.activity);
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned pin entry - continue to boot screen
JADE_LOGI("User confirmation abandoned, not wiping data.");
return;
}
- format_pin(pinstr, sizeof(pinstr), pin_insert.pin, sizeof(pin_insert.pin));
+ format_pin(pinstr, sizeof(pinstr), digit_entry.digit, sizeof(digit_entry.digit));
JADE_LOGI("User entered: %s", pinstr);
- if (!sodium_memcmp(num, pin_insert.pin, sizeof(num))) {
+ if (!sodium_memcmp(num, digit_entry.digit, sizeof(num))) {
// Correct - erase all jade non-volatile storage
JADE_LOGI("User confirmed - erasing Jade data");
if (storage_erase()) {
@@ -1233,36 +1233,36 @@ static void set_wallet_erase_pin(void)
JADE_LOGI("Requesting wallet-erase PIN");
// Ask user to enter a wallet-erase pin
- pin_insert_t pin_insert = { .initial_state = RANDOM, .pin_digits_shown = false };
- make_pin_insert_activity(&pin_insert, "Wallet-Erase PIN", "Different from main PIN");
- JADE_ASSERT(pin_insert.activity);
+ digit_entry_t digit_entry = { .entry_type = DIGIT_ENTRY_PIN, .initial_state = RANDOM, .digits_shown = false };
+ make_digit_entry_activity(&digit_entry, "Wallet-Erase PIN", "Different from main PIN");
+ JADE_ASSERT(digit_entry.activity);
while (true) {
- reset_pin(&pin_insert, "Wallet-Erase PIN");
- gui_set_current_activity(pin_insert.activity);
+ reset_digit_entry(&digit_entry, "Wallet-Erase PIN");
+ gui_set_current_activity(digit_entry.activity);
- if (!run_pin_entry_loop(&pin_insert)) {
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned pin entry
JADE_LOGI("User abandoned setting wallet erase PIN");
break;
}
// This is the first pin, copy it and clear screen fields
- uint8_t pin[sizeof(pin_insert.pin)];
- memcpy(pin, pin_insert.pin, sizeof(pin));
- reset_pin(&pin_insert, "Confirm Erase PIN");
+ uint8_t pin[sizeof(digit_entry.digit)];
+ memcpy(pin, digit_entry.digit, sizeof(pin));
+ reset_digit_entry(&digit_entry, "Confirm Erase PIN");
// Ask user to re-enter PIN
- if (!run_pin_entry_loop(&pin_insert)) {
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned second input - back to first ...
continue;
}
// Check that the two pins are the same
JADE_LOGD("Checking pins match");
- if (!sodium_memcmp(pin, pin_insert.pin, sizeof(pin))) {
+ if (!sodium_memcmp(pin, digit_entry.digit, sizeof(pin))) {
JADE_LOGI("Setting Wallet-Erase PIN");
- storage_set_wallet_erase_pin(pin_insert.pin, sizeof(pin_insert.pin));
+ storage_set_wallet_erase_pin(digit_entry.digit, sizeof(digit_entry.digit));
break;
} else {
// Pins mismatch - try again
@@ -1285,7 +1285,7 @@ static void handle_wallet_erase_pin(void)
while (true) {
// Add wallet erase pin confirmation screens
- uint8_t pin_erase[PIN_SIZE];
+ uint8_t pin_erase[DIGIT_ENTRY_SIZE];
gui_activity_t* act = NULL;
if (storage_get_wallet_erase_pin(pin_erase, sizeof(pin_erase))) {
char pinstr[sizeof(pin_erase) + 1];
diff --git a/main/process/mnemonic.c b/main/process/mnemonic.c
index d665871..4690b0f 100644
--- a/main/process/mnemonic.c
+++ b/main/process/mnemonic.c
@@ -1520,22 +1520,22 @@ void handle_bip85_mnemonic()
JADE_ASSERT(nwords == 12 || nwords == 24);
// Fetch index (uses pin-entry screen)
- pin_insert_t pin_insert = { .initial_state = ZERO, .pin_digits_shown = true };
- make_pin_insert_activity(&pin_insert, "BIP85", "Index #:");
- JADE_ASSERT(pin_insert.activity);
+ digit_entry_t digit_entry = { .entry_type = DIGIT_ENTRY_INDEX, .initial_state = ZERO, .digits_shown = true };
+ make_digit_entry_activity(&digit_entry, "BIP85", "Index #:");
+ JADE_ASSERT(digit_entry.activity);
size_t index = 0;
while (true) {
- reset_pin(&pin_insert, "BIP85");
- gui_set_current_activity(pin_insert.activity);
- if (!run_pin_entry_loop(&pin_insert)) {
+ reset_digit_entry(&digit_entry, "BIP85");
+ gui_set_current_activity(digit_entry.activity);
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned index entry
JADE_LOGI("User abandoned selecting index");
return;
}
// Get entered digits as single numeric value
- index = get_pin_as_number(&pin_insert);
+ index = get_entry_as_number(&digit_entry);
JADE_ASSERT(index < BIP85_INDEX_MAX);
// User to confirm
diff --git a/main/qrmode.c b/main/qrmode.c
index 8a994f5..5b1baf0 100644
--- a/main/qrmode.c
+++ b/main/qrmode.c
@@ -288,9 +288,9 @@ bool handle_xpub_options(uint32_t* qr_flags, bool for_descriptor)
gui_activity_t* const act_wallettype = make_carousel_activity("Wallet Type", NULL, &wallet_textbox);
gui_update_text(wallet_textbox, xpub_wallettype_desc_from_flags(*qr_flags));
- pin_insert_t pin_insert = { .initial_state = ZERO, .pin_digits_shown = true };
- make_pin_insert_activity(&pin_insert, "Account Index", "Enter index:");
- JADE_ASSERT(pin_insert.activity);
+ digit_entry_t digit_entry = { .entry_type = DIGIT_ENTRY_INDEX, .initial_state = ZERO, .digits_shown = true };
+ make_digit_entry_activity(&digit_entry, "Account Index", "Enter index:");
+ JADE_ASSERT(digit_entry.activity);
const uint32_t initial_flags = *qr_flags;
while (true) {
@@ -331,15 +331,15 @@ bool handle_xpub_options(uint32_t* qr_flags, bool for_descriptor)
} else if (ev_id == BTN_XPUB_OPTIONS_ACCOUNT) {
while (true) {
- reset_pin(&pin_insert, NULL);
- gui_set_current_activity(pin_insert.activity);
- if (!run_pin_entry_loop(&pin_insert)) {
+ reset_digit_entry(&digit_entry, NULL);
+ gui_set_current_activity(digit_entry.activity);
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned index entry
break;
}
// Get entered digits as single numeric value
- const uint32_t new_account_index = get_pin_as_number(&pin_insert);
+ const uint32_t new_account_index = get_entry_as_number(&digit_entry);
if (new_account_index < ACCOUNT_INDEX_MAX) {
account_index = new_account_index;
@@ -567,19 +567,20 @@ static bool handle_address_options(const bool show_account, uint16_t* account_in
if (gui_activity_wait_event(act_options, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0)) {
if (ev_id == BTN_SCAN_ADDRESS_OPTIONS_ACCOUNT && show_account) {
- pin_insert_t pin_insert = { .initial_state = ZERO, .pin_digits_shown = true };
- make_pin_insert_activity(&pin_insert, "Account Index", "Enter index:");
- JADE_ASSERT(pin_insert.activity);
+ digit_entry_t digit_entry
+ = { .entry_type = DIGIT_ENTRY_INDEX, .initial_state = ZERO, .digits_shown = true };
+ make_digit_entry_activity(&digit_entry, "Account Index", "Enter index:");
+ JADE_ASSERT(digit_entry.activity);
while (true) {
- gui_set_current_activity(pin_insert.activity);
- if (!run_pin_entry_loop(&pin_insert)) {
+ gui_set_current_activity(digit_entry.activity);
+ if (!run_digit_entry_loop(&digit_entry)) {
// User abandoned index entry
break;
}
// Get entered digits as single numeric value
- uint32_t new_account_index = get_pin_as_number(&pin_insert);
+ uint32_t new_account_index = get_entry_as_number(&digit_entry);
if (new_account_index < ACCOUNT_INDEX_MAX) {
*account_index = new_account_index;
diff --git a/main/ui.h b/main/ui.h
index b7e5d59..09f5ee8 100644
--- a/main/ui.h
+++ b/main/ui.h
@@ -35,12 +35,18 @@ typedef struct {
gui_view_node_t* textbox_nodes[NUM_KBS];
} keyboard_entry_t;
-// PIN entry
-#define PIN_SIZE 6
+// digit entry
+#define DIGIT_ENTRY_SIZE 6
-enum pin_digit_initial_state { RANDOM, ZERO, POSITION };
+enum __attribute__((__packed__)) digit_entry_type {
+ DIGIT_ENTRY_INVALID = 0,
+ DIGIT_ENTRY_PIN,
+ DIGIT_ENTRY_INDEX,
+};
+
+enum __attribute__((__packed__)) digit_entry_initial_state { RANDOM, ZERO, POSITION };
-enum pin_digit_status {
+enum __attribute__((__packed__)) digit_entry_status {
EMPTY,
SELECTED,
SET,
@@ -51,23 +57,23 @@ typedef struct {
gui_view_node_t* up_arrow_node;
gui_view_node_t* digit_node;
gui_view_node_t* down_arrow_node;
-} pin_digit_t;
+} digit_entry_node_t;
typedef struct {
- const enum pin_digit_initial_state initial_state;
- const bool pin_digits_shown;
-
- uint8_t pin[PIN_SIZE];
- enum pin_digit_status digit_status[PIN_SIZE];
+ const enum digit_entry_type entry_type;
+ const enum digit_entry_initial_state initial_state;
+ const bool digits_shown;
+ uint8_t digit[DIGIT_ENTRY_SIZE];
+ enum digit_entry_status digit_status[DIGIT_ENTRY_SIZE];
gui_activity_t* activity;
gui_view_node_t* title;
- pin_digit_t pin_digit_nodes[PIN_SIZE];
+ digit_entry_node_t digit_nodes[DIGIT_ENTRY_SIZE];
uint8_t selected_digit;
uint8_t current_selected_value;
-} pin_insert_t;
+} digit_entry_t;
typedef struct {
gui_view_node_t* symbol;
@@ -159,11 +165,11 @@ void update_carousel_highlight_color(const gui_view_node_t* text_label, color_t
void make_keyboard_entry_activity(keyboard_entry_t* kb_entry, const char* title);
void run_keyboard_entry_loop(keyboard_entry_t* kb_entry);
-// Functions for pin entry
-void make_pin_insert_activity(pin_insert_t* pin_insert, const char* title, const char* message);
-bool run_pin_entry_loop(pin_insert_t* pin_insert);
-void reset_pin(pin_insert_t* pin_insert, const char* title);
-size_t get_pin_as_number(const pin_insert_t* pin_insert);
+// Functions for number entry
+void make_digit_entry_activity(digit_entry_t* digit_entry, const char* title, const char* message);
+bool run_digit_entry_loop(digit_entry_t* digit_entry);
+void reset_digit_entry(digit_entry_t* digit_entry, const char* title);
+size_t get_entry_as_number(const digit_entry_t* digit_entry);
// Generic progress-bar
void make_progress_bar(gui_view_node_t* parent, progress_bar_t* progress_bar);
diff --git a/main/ui/digit_entry.c b/main/ui/digit_entry.c
new file mode 100644
index 0000000..81dcbfe
--- /dev/null
+++ b/main/ui/digit_entry.c
@@ -0,0 +1,311 @@
+#ifndef AMALGAMATED_BUILD
+#include "../button_events.h"
+#include "../jade_assert.h"
+#include "../random.h"
+#include "../ui.h"
+
+#include <math.h>
+
+#define CHAR_BACKSPACE '|'
+#define CHAR_ENTER '~'
+static const char ENTRY_CHARS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', CHAR_BACKSPACE, CHAR_ENTER };
+// The number of available digits, i.e. not including backspace or enter
+#define NUM_ENTRY_DIGITS (sizeof(ENTRY_CHARS) / sizeof(ENTRY_CHARS[0]) - 2)
+
+static inline bool entry_invert_navigation(void)
+{
+#if defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3)
+ // TTGO boards need to locally invert navigation so number entry matches the rest of the UI.
+ return true;
+#else
+ return false;
+#endif
+}
+
+static uint32_t get_max_digit_entry_char(const digit_entry_t* digit_entry)
+{
+ if (digit_entry->entry_type == DIGIT_ENTRY_INDEX) {
+ return NUM_ENTRY_DIGITS + 1; // 0-9 + backspace + 'enter' to enter a short number
+ }
+ return NUM_ENTRY_DIGITS; // 0-9 + backspace only since PIN entry requires all digits
+}
+
+static inline char get_current_digit_entry_char(const digit_entry_t* digit_entry)
+{
+ JADE_ASSERT(digit_entry->current_selected_value < get_max_digit_entry_char(digit_entry));
+ return ENTRY_CHARS[digit_entry->current_selected_value];
+}
+
+static void reinitialise_current_entry_digit(digit_entry_t* digit_entry)
+{
+ JADE_ASSERT(digit_entry);
+
+ switch (digit_entry->initial_state) {
+ case ZERO:
+ digit_entry->current_selected_value = 0;
+ break;
+ case POSITION:
+ digit_entry->current_selected_value = digit_entry->selected_digit;
+ break;
+ default:
+ digit_entry->current_selected_value = get_uniform_random_byte(NUM_ENTRY_DIGITS);
+ break;
+ }
+}
+
+static void update_digit_node(digit_entry_t* digit_entry, uint8_t i)
+{
+ JADE_ASSERT(digit_entry);
+ JADE_ASSERT(i < DIGIT_ENTRY_SIZE);
+
+ char strdigit[] = { '\0', '\0' };
+ switch (digit_entry->digit_status[i]) {
+ case EMPTY:
+ gui_set_color(digit_entry->digit_nodes[i].fill_node, TFT_BLACK);
+ gui_set_borders(digit_entry->digit_nodes[i].fill_node, TFT_LIGHTGREY, 2, GUI_BORDER_ALL);
+ gui_update_text(digit_entry->digit_nodes[i].up_arrow_node, "");
+ gui_update_text(digit_entry->digit_nodes[i].down_arrow_node, "");
+ break;
+ case SELECTED:
+ gui_set_color(digit_entry->digit_nodes[i].fill_node, gui_get_highlight_color());
+ gui_set_borders(digit_entry->digit_nodes[i].fill_node, gui_get_highlight_color(), 2, GUI_BORDER_ALL);
+ gui_update_text(digit_entry->digit_nodes[i].up_arrow_node, "K");
+ gui_update_text(digit_entry->digit_nodes[i].down_arrow_node, "L");
+ strdigit[0] = ENTRY_CHARS[digit_entry->current_selected_value];
+ break;
+ case SET:
+ gui_set_color(digit_entry->digit_nodes[i].fill_node, TFT_BLACK);
+ gui_set_borders(digit_entry->digit_nodes[i].fill_node, gui_get_highlight_color(), 2, GUI_BORDER_ALL);
+ gui_update_text(digit_entry->digit_nodes[i].up_arrow_node, "");
+ gui_update_text(digit_entry->digit_nodes[i].down_arrow_node, "");
+ strdigit[0] = digit_entry->digits_shown ? ENTRY_CHARS[digit_entry->digit[i]] : '*';
+ break;
+ }
+ gui_update_text(digit_entry->digit_nodes[i].digit_node, strdigit);
+ gui_repaint(digit_entry->digit_nodes[i].fill_node);
+}
+
+void make_digit_entry_activity(digit_entry_t* digit_entry, const char* title, const char* message)
+{
+ JADE_ASSERT(digit_entry);
+ JADE_ASSERT(digit_entry->entry_type != DIGIT_ENTRY_INVALID);
+
+ digit_entry->activity = gui_make_activity();
+ gui_view_node_t* parent = add_title_bar(digit_entry->activity, title, NULL, 0, &digit_entry->title);
+ gui_view_node_t* node;
+
+ gui_view_node_t* vsplit;
+ if (message) {
+ gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 2, 25, 75);
+ gui_make_text(&node, message, TFT_WHITE);
+ gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
+ } else {
+ gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 3, 10, 75, 15);
+ gui_make_fill(&node, TFT_BLACK, FILL_PLAIN, NULL);
+ }
+ gui_set_parent(vsplit, parent);
+ gui_set_parent(node, vsplit);
+
+ const size_t toppad = CONFIG_DISPLAY_HEIGHT > 200 ? 20 : CONFIG_DISPLAY_HEIGHT > 160 ? 12 : 4;
+ const size_t lrpad = (CONFIG_DISPLAY_WIDTH - (6 * 35)) / 2;
+ gui_view_node_t* hsplit;
+ gui_make_hsplit(&hsplit, GUI_SPLIT_ABSOLUTE, 6, 35, 35, 35, 35, 35, 35);
+ gui_set_margins(hsplit, GUI_MARGIN_ALL_DIFFERENT, toppad, lrpad, toppad + 8, lrpad);
+ gui_set_parent(hsplit, vsplit);
+
+ reinitialise_current_entry_digit(digit_entry);
+
+ for (size_t i = 0; i < DIGIT_ENTRY_SIZE; ++i) {
+ digit_entry->digit[i] = 0xFF;
+ digit_entry->digit_status[i] = i == 0 ? SELECTED : EMPTY;
+
+ gui_make_fill(&node, TFT_BLACK, FILL_PLAIN, hsplit);
+ digit_entry->digit_nodes[i].fill_node = node;
+
+ gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 3, 25, 50, 25);
+ gui_set_parent(vsplit, node);
+ // no need to store the vsplit
+
+ // Up arrow
+ gui_make_text_font(&node, "K", TFT_WHITE, JADE_SYMBOLS_16x16_FONT);
+ gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
+ gui_set_parent(node, vsplit);
+ digit_entry->digit_nodes[i].up_arrow_node = node;
+
+ // Digit
+ gui_make_text_font(&node, "", TFT_WHITE, DEJAVU24_FONT);
+ gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
+ gui_set_parent(node, vsplit);
+ gui_set_padding(node, GUI_MARGIN_ALL_DIFFERENT, 5, 0, 0, 0);
+ digit_entry->digit_nodes[i].digit_node = node;
+
+ // Down arrow
+ gui_make_text_font(&node, "L", TFT_WHITE, JADE_SYMBOLS_16x16_FONT);
+ gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
+ gui_set_parent(node, vsplit);
+ digit_entry->digit_nodes[i].down_arrow_node = node;
+
+ update_digit_node(digit_entry, i);
+ }
+}
+
+static bool next_selected_digit(digit_entry_t* digit_entry)
+{
+ JADE_ASSERT(digit_entry);
+ JADE_ASSERT(digit_entry->selected_digit < DIGIT_ENTRY_SIZE);
+
+ // make sure the '<' is not selected
+ JADE_ASSERT(digit_entry->current_selected_value < 10);
+
+ // copy the value
+ digit_entry->digit[digit_entry->selected_digit] = digit_entry->current_selected_value;
+
+ // set the status and update the ui
+ digit_entry->digit_status[digit_entry->selected_digit] = SET;
+ update_digit_node(digit_entry, digit_entry->selected_digit);
+ ++digit_entry->selected_digit;
+
+ // reached the last digit - cannot select next, return false
+ if (digit_entry->selected_digit >= DIGIT_ENTRY_SIZE) {
+ return false;
+ }
+
+ // set the status and update the ui
+ digit_entry->digit_status[digit_entry->selected_digit] = SELECTED;
+
+ reinitialise_current_entry_digit(digit_entry);
+ update_digit_node(digit_entry, digit_entry->selected_digit);
+
+ return true;
+}
+
+static bool prev_selected_digit(digit_entry_t* digit_entry)
+{
+ JADE_ASSERT(digit_entry);
+ JADE_ASSERT(digit_entry->selected_digit < DIGIT_ENTRY_SIZE);
+
+ // at the first digit - cannot select previous, return false
+ if (digit_entry->selected_digit == 0) {
+ return false;
+ }
+
+ // set the status and update the ui
+ digit_entry->digit_status[digit_entry->selected_digit] = EMPTY;
+ update_digit_node(digit_entry, digit_entry->selected_digit);
+
+ --digit_entry->selected_digit;
+ reinitialise_current_entry_digit(digit_entry);
+
+ // set the status and update the ui
+ digit_entry->digit_status[digit_entry->selected_digit] = SELECTED;
+ update_digit_node(digit_entry, digit_entry->selected_digit);
+
+ return true;
+}
+
+// Returns true if number entry completes and digit_entry->digit is valid,
+// and false if number entry abandoned and digit_entry->digit is not to be used.
+bool run_digit_entry_loop(digit_entry_t* digit_entry)
+{
+ JADE_ASSERT(digit_entry);
+ JADE_ASSERT(digit_entry->activity);
+
+ int32_t ev_id;
+ while (true) {
+ // wait for a GUI event
+ gui_activity_wait_event(digit_entry->activity, GUI_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
+ if (entry_invert_navigation()) {
+ // Swap left/right wheel events
+ if (ev_id == GUI_WHEEL_LEFT_EVENT) {
+ ev_id = GUI_WHEEL_RIGHT_EVENT;
+ } else if (ev_id == GUI_WHEEL_RIGHT_EVENT) {
+ ev_id = GUI_WHEEL_LEFT_EVENT;
+ }
+ }
+
+ switch (ev_id) {
+ case GUI_WHEEL_LEFT_EVENT:
+ digit_entry->current_selected_value
+ = (digit_entry->current_selected_value + get_max_digit_entry_char(digit_entry) - 1)
+ % get_max_digit_entry_char(digit_entry);
+ update_digit_node(digit_entry, digit_entry->selected_digit);
+ break;
+ case GUI_WHEEL_RIGHT_EVENT:
+ digit_entry->current_selected_value
+ = (digit_entry->current_selected_value + 1) % get_max_digit_entry_char(digit_entry);
+ update_digit_node(digit_entry, digit_entry->selected_digit);
+ break;
+
+ default:
+ if (ev_id == gui_get_click_event()) {
+ switch (get_current_digit_entry_char(digit_entry)) {
+ case CHAR_BACKSPACE:
+ if (!prev_selected_digit(digit_entry)) {
+ // Returns false when click 'backspace' on first digit (cannot move to previous)
+ return false; // number entry abandoned
+ }
+ break;
+ case CHAR_ENTER:
+ // only valid for digit_entry_type == DIGIT_ENTRY_INDEX
+ JADE_ASSERT(digit_entry->entry_type == DIGIT_ENTRY_INDEX);
+ // If enter clicked on first digit, abandon entry
+ if (digit_entry->selected_digit == 0) {
+ return false; // number entry abandoned
+ }
+ return true; // number entry complete
+ default:
+ if (!next_selected_digit(digit_entry)) {
+ // Returns false when click number on last digit (cannot move to next)
+ return true; // number entry complete
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+void reset_digit_entry(digit_entry_t* digit_entry, const char* title)
+{
+ JADE_ASSERT(digit_entry);
+ // title is optional
+
+ // Select and re-randomise first digit
+ digit_entry->selected_digit = 0;
+ reinitialise_current_entry_digit(digit_entry);
+
+ // Mark all digits as unset
+ for (size_t i = 0; i < DIGIT_ENTRY_SIZE; ++i) {
+ digit_entry->digit[i] = 0xFF;
+ digit_entry->digit_status[i] = i == 0 ? SELECTED : EMPTY;
+ update_digit_node(digit_entry, i);
+ }
+
+ // Update title if passed
+ if (title) {
+ gui_update_text(digit_entry->title, title);
+ }
+}
+
+size_t get_entry_as_number(const digit_entry_t* digit_entry)
+{
+ JADE_ASSERT(digit_entry);
+ if (digit_entry->entry_type == DIGIT_ENTRY_INDEX) {
+ JADE_ASSERT(digit_entry->selected_digit > 0 && digit_entry->selected_digit <= DIGIT_ENTRY_SIZE); // entry valid
+ } else {
+ JADE_ASSERT(digit_entry->selected_digit == DIGIT_ENTRY_SIZE); // entry complete
+ }
+
+ size_t val = 0;
+ for (uint8_t i = 0; i < digit_entry->selected_digit; ++i) {
+ JADE_ASSERT(digit_entry->digit_status[i] == SET);
+ JADE_ASSERT(digit_entry->digit[i] < get_num_digit_entry_chars(digit_entry));
+
+ const size_t digit = digit_entry->digit[i];
+ const uint8_t exponent = digit_entry->selected_digit - i - 1;
+ val += (digit * pow(10, exponent));
+ }
+
+ return val;
+}
+#endif // AMALGAMATED_BUILD
diff --git a/main/ui/pin.c b/main/ui/pin.c
deleted file mode 100644
index 72075d7..0000000
--- a/main/ui/pin.c
+++ /dev/null
@@ -1,282 +0,0 @@
-#ifndef AMALGAMATED_BUILD
-#include "../button_events.h"
-#include "../jade_assert.h"
-#include "../random.h"
-#include "../ui.h"
-
-#include <math.h>
-
-static const char CHAR_BACKSPACE = '|';
-static const char PIN_CHARS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', CHAR_BACKSPACE };
-static const uint32_t NUM_PIN_CHARS = sizeof(PIN_CHARS) / sizeof(PIN_CHARS[0]);
-static const uint32_t NUM_PIN_VALUES = NUM_PIN_CHARS - 1; // ie. not including backspace
-
-static inline bool pin_entry_invert_navigation(void)
-{
-#if defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3)
- // TTGO boards need to locally invert navigation so PIN entry matches the rest of the UI.
- return true;
-#else
- return false;
-#endif
-}
-
-static inline char get_pin_value(size_t index)
-{
- JADE_ASSERT(index < NUM_PIN_CHARS);
- return PIN_CHARS[index];
-}
-
-static void reinitialise_current_pin_digit(pin_insert_t* pin_insert)
-{
- JADE_ASSERT(pin_insert);
-
- switch (pin_insert->initial_state) {
- case ZERO:
- pin_insert->current_selected_value = 0;
- break;
- case POSITION:
- pin_insert->current_selected_value = pin_insert->selected_digit;
- break;
- default:
- pin_insert->current_selected_value = get_uniform_random_byte(NUM_PIN_VALUES);
- break;
- }
-}
-
-static void update_digit_node(pin_insert_t* pin_insert, uint8_t i)
-{
- JADE_ASSERT(pin_insert);
- JADE_ASSERT(i < PIN_SIZE);
-
- char strdigit[] = { '\0', '\0' };
- switch (pin_insert->digit_status[i]) {
- case EMPTY:
- gui_set_color(pin_insert->pin_digit_nodes[i].fill_node, TFT_BLACK);
- gui_set_borders(pin_insert->pin_digit_nodes[i].fill_node, TFT_LIGHTGREY, 2, GUI_BORDER_ALL);
- gui_update_text(pin_insert->pin_digit_nodes[i].up_arrow_node, "");
- gui_update_text(pin_insert->pin_digit_nodes[i].down_arrow_node, "");
- break;
- case SELECTED:
- gui_set_color(pin_insert->pin_digit_nodes[i].fill_node, gui_get_highlight_color());
- gui_set_borders(pin_insert->pin_digit_nodes[i].fill_node, gui_get_highlight_color(), 2, GUI_BORDER_ALL);
- gui_update_text(pin_insert->pin_digit_nodes[i].up_arrow_node, "K");
- gui_update_text(pin_insert->pin_digit_nodes[i].down_arrow_node, "L");
- strdigit[0] = PIN_CHARS[pin_insert->current_selected_value];
- break;
- case SET:
- gui_set_color(pin_insert->pin_digit_nodes[i].fill_node, TFT_BLACK);
- gui_set_borders(pin_insert->pin_digit_nodes[i].fill_node, gui_get_highlight_color(), 2, GUI_BORDER_ALL);
- gui_update_text(pin_insert->pin_digit_nodes[i].up_arrow_node, "");
- gui_update_text(pin_insert->pin_digit_nodes[i].down_arrow_node, "");
- strdigit[0] = pin_insert->pin_digits_shown ? PIN_CHARS[pin_insert->pin[i]] : '*';
- break;
- }
- gui_update_text(pin_insert->pin_digit_nodes[i].digit_node, strdigit);
- gui_repaint(pin_insert->pin_digit_nodes[i].fill_node);
-}
-
-void make_pin_insert_activity(pin_insert_t* pin_insert, const char* title, const char* message)
-{
- JADE_ASSERT(pin_insert);
-
- pin_insert->activity = gui_make_activity();
- gui_view_node_t* parent = add_title_bar(pin_insert->activity, title, NULL, 0, &pin_insert->title);
- gui_view_node_t* node;
-
- gui_view_node_t* vsplit;
- if (message) {
- gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 2, 25, 75);
- gui_make_text(&node, message, TFT_WHITE);
- gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
- } else {
- gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 3, 10, 75, 15);
- gui_make_fill(&node, TFT_BLACK, FILL_PLAIN, NULL);
- }
- gui_set_parent(vsplit, parent);
- gui_set_parent(node, vsplit);
-
- const size_t toppad = CONFIG_DISPLAY_HEIGHT > 200 ? 20 : CONFIG_DISPLAY_HEIGHT > 160 ? 12 : 4;
- const size_t lrpad = (CONFIG_DISPLAY_WIDTH - (6 * 35)) / 2;
- gui_view_node_t* hsplit;
- gui_make_hsplit(&hsplit, GUI_SPLIT_ABSOLUTE, 6, 35, 35, 35, 35, 35, 35);
- gui_set_margins(hsplit, GUI_MARGIN_ALL_DIFFERENT, toppad, lrpad, toppad + 8, lrpad);
- gui_set_parent(hsplit, vsplit);
-
- reinitialise_current_pin_digit(pin_insert);
-
- for (size_t i = 0; i < PIN_SIZE; ++i) {
- pin_insert->pin[i] = 0xFF;
- pin_insert->digit_status[i] = i == 0 ? SELECTED : EMPTY;
-
- gui_make_fill(&node, TFT_BLACK, FILL_PLAIN, hsplit);
- pin_insert->pin_digit_nodes[i].fill_node = node;
-
- gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 3, 25, 50, 25);
- gui_set_parent(vsplit, node);
- // no need to store the vsplit
-
- // Up arrow
- gui_make_text_font(&node, "K", TFT_WHITE, JADE_SYMBOLS_16x16_FONT);
- gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
- gui_set_parent(node, vsplit);
- pin_insert->pin_digit_nodes[i].up_arrow_node = node;
-
- // Digit
- gui_make_text_font(&node, "", TFT_WHITE, DEJAVU24_FONT);
- gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
- gui_set_parent(node, vsplit);
- gui_set_padding(node, GUI_MARGIN_ALL_DIFFERENT, 5, 0, 0, 0);
- pin_insert->pin_digit_nodes[i].digit_node = node;
-
- // Down arrow
- gui_make_text_font(&node, "L", TFT_WHITE, JADE_SYMBOLS_16x16_FONT);
- gui_set_align(node, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
- gui_set_parent(node, vsplit);
- pin_insert->pin_digit_nodes[i].down_arrow_node = node;
-
- update_digit_node(pin_insert, i);
- }
-}
-
-static bool next_selected_digit(pin_insert_t* pin_insert)
-{
- JADE_ASSERT(pin_insert);
- JADE_ASSERT(pin_insert->selected_digit < PIN_SIZE);
-
- // make sure the '<' is not selected
- JADE_ASSERT(pin_insert->current_selected_value < 10);
-
- // copy the value
- pin_insert->pin[pin_insert->selected_digit] = pin_insert->current_selected_value;
-
- // set the status and update the ui
- pin_insert->digit_status[pin_insert->selected_digit] = SET;
- update_digit_node(pin_insert, pin_insert->selected_digit);
- ++pin_insert->selected_digit;
-
- // reached the last digit - cannot select next, return false
- if (pin_insert->selected_digit >= PIN_SIZE) {
- return false;
- }
-
- // set the status and update the ui
- pin_insert->digit_status[pin_insert->selected_digit] = SELECTED;
-
- reinitialise_current_pin_digit(pin_insert);
- update_digit_node(pin_insert, pin_insert->selected_digit);
-
- return true;
-}
-
-static bool prev_selected_digit(pin_insert_t* pin_insert)
-{
- JADE_ASSERT(pin_insert);
- JADE_ASSERT(pin_insert->selected_digit < PIN_SIZE);
-
- // at the first digit - cannot select previous, return false
- if (pin_insert->selected_digit == 0) {
- return false;
- }
-
- // set the status and update the ui
- pin_insert->digit_status[pin_insert->selected_digit] = EMPTY;
- update_digit_node(pin_insert, pin_insert->selected_digit);
-
- --pin_insert->selected_digit;
- reinitialise_current_pin_digit(pin_insert);
-
- // set the status and update the ui
- pin_insert->digit_status[pin_insert->selected_digit] = SELECTED;
- update_digit_node(pin_insert, pin_insert->selected_digit);
-
- return true;
-}
-
-// Returns true if pin entry completes and pin_insert->pin is valid,
-// and false if pin entry abandoned and pin_insert->pin is not to be used.
-bool run_pin_entry_loop(pin_insert_t* pin_insert)
-{
- JADE_ASSERT(pin_insert);
- JADE_ASSERT(pin_insert->activity);
-
- int32_t ev_id;
- while (true) {
- // wait for a GUI event
- gui_activity_wait_event(pin_insert->activity, GUI_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
- if (pin_entry_invert_navigation()) {
- // Swap left/right wheel events
- if (ev_id == GUI_WHEEL_LEFT_EVENT) {
- ev_id = GUI_WHEEL_RIGHT_EVENT;
- } else if (ev_id == GUI_WHEEL_RIGHT_EVENT) {
- ev_id = GUI_WHEEL_LEFT_EVENT;
- }
- }
-
- switch (ev_id) {
- case GUI_WHEEL_LEFT_EVENT:
- pin_insert->current_selected_value
- = (pin_insert->current_selected_value + NUM_PIN_CHARS - 1) % NUM_PIN_CHARS;
- update_digit_node(pin_insert, pin_insert->selected_digit);
- break;
- case GUI_WHEEL_RIGHT_EVENT:
- pin_insert->current_selected_value = (pin_insert->current_selected_value + 1) % NUM_PIN_CHARS;
- update_digit_node(pin_insert, pin_insert->selected_digit);
- break;
-
- default:
- if (ev_id == gui_get_click_event()) {
- if (get_pin_value(pin_insert->current_selected_value) == CHAR_BACKSPACE) {
- if (!prev_selected_digit(pin_insert)) {
- // Returns false when click 'backspace' on first digit (cannot move to previous)
- return false; // pin entry abandoned
- }
- } else if (!next_selected_digit(pin_insert)) {
- // Returns false when click number on last digit (cannot move to next)
- return true; // pin entry complete
- }
- }
- }
- }
-}
-
-void reset_pin(pin_insert_t* pin_insert, const char* title)
-{
- JADE_ASSERT(pin_insert);
- // title is optional
-
- // Select and re-randomise first digit
- pin_insert->selected_digit = 0;
- reinitialise_current_pin_digit(pin_insert);
-
- // Mark all digits as unset
- for (size_t i = 0; i < PIN_SIZE; ++i) {
- pin_insert->pin[i] = 0xFF;
- pin_insert->digit_status[i] = i == 0 ? SELECTED : EMPTY;
- update_digit_node(pin_insert, i);
- }
-
- // Update title if passed
- if (title) {
- gui_update_text(pin_insert->title, title);
- }
-}
-
-size_t get_pin_as_number(const pin_insert_t* pin_insert)
-{
- JADE_ASSERT(pin_insert);
- JADE_ASSERT(pin_insert->selected_digit == PIN_SIZE); // entry complete
-
- size_t val = 0;
- for (uint8_t i = 0; i < PIN_SIZE; ++i) {
- JADE_ASSERT(pin_insert->digit_status[i] == SET);
- JADE_ASSERT(pin_insert->pin[i] < NUM_PIN_VALUES);
-
- const size_t digit = pin_insert->pin[i];
- const uint8_t exponent = PIN_SIZE - i - 1;
- val += (digit * pow(10, exponent));
- }
-
- return val;
-}
-#endif // AMALGAMATED_BUILD
Why this scored 20/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.