mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 03:06:39 +00:00
feat: Add PIN input keyboard and use it on lockscreen
This commit is contained in:
@@ -214,7 +214,8 @@ struct NodePrefs { // persisted to file
|
||||
static const uint8_t PAGE_ORDER_MAGIC = 0xA5;
|
||||
// On-screen keyboard layout, shared across every text-entry screen (Settings >
|
||||
// Keyboard). 0=ABC grid, alphabetical order (default), 1=T9 multi-tap
|
||||
// (phone-keypad groups, cycled with repeated Enter presses — see KeyboardWidget.h).
|
||||
// (phone-keypad groups, cycled with repeated Enter presses — see KeyboardWidget.h),
|
||||
// 3=PIN keyboard.
|
||||
uint8_t keyboard_type;
|
||||
// Additional (non-Latin) keyboard alphabet, orthogonal to keyboard_type above
|
||||
// — either layout style (ABC grid or T9) can show any alphabet's characters.
|
||||
|
||||
@@ -43,6 +43,17 @@ static const char* const KB_T9_GROUPS[KB_PAGES][9] = {
|
||||
{ "@#&", "*()", "-_+", "=/\\", ":;'\"", "<>[]", "{}|~", "^$%`", ",." }, // page 1 — symbols
|
||||
};
|
||||
|
||||
// PIN input layout: Classic number pad
|
||||
static const int KB_PIN_ROWS = 4;
|
||||
static const int KB_PIN_COLS = 3;
|
||||
static const int KB_PIN_SPECIAL = 3; // backspace, abc kb switch & submit
|
||||
static const char KB_PIN_DIGITS[KB_PIN_ROWS][KB_PIN_COLS] = {
|
||||
{'1','2','3'},
|
||||
{'4','5','6'},
|
||||
{'7','8','9'},
|
||||
{' ','0',' '},
|
||||
};
|
||||
|
||||
// Non-Latin keyboard scripts. NodePrefs::keyboard_main_alphabet/
|
||||
// keyboard_alt_alphabet (Settings > Keyboard's Main/Additional rows) pick
|
||||
// which script occupies page 0 (the keyboard's default/opening page) and
|
||||
@@ -325,6 +336,7 @@ struct KeyboardWidget {
|
||||
// to ABC and mainScript()/altScript() default to Latin-only.
|
||||
NodePrefs* prefs = nullptr;
|
||||
bool isT9() const { return prefs && prefs->keyboard_type == 1; }
|
||||
bool isPin() const { return prefs && prefs->keyboard_type == 2; }
|
||||
// Which script occupies page 0 (the keyboard's default/opening page) and
|
||||
// which occupies page 1 (reached by the #@/abc cycle key) -- Settings >
|
||||
// Keyboard's Main/Additional rows. Additional equal to Main collapses to no
|
||||
@@ -346,8 +358,8 @@ struct KeyboardWidget {
|
||||
// lowercase regardless of Shift.
|
||||
bool t9_caps = false;
|
||||
|
||||
int gridRows() const { return isT9() ? KB_T9_ROWS : KB_ROWS_CHAR; }
|
||||
int gridCols() const { return isT9() ? KB_T9_COLS : KB_COLS_CHAR; }
|
||||
int gridRows() const { return isPin() ? KB_PIN_ROWS : (isT9() ? KB_T9_ROWS : KB_ROWS_CHAR); }
|
||||
int gridCols() const { return isPin() ? KB_PIN_COLS : (isT9() ? KB_T9_COLS : KB_COLS_CHAR); }
|
||||
|
||||
// ── Page model ────────────────────────────────────────────────────────────
|
||||
// Logical page order: 0 = mainScript(), [1 = altScript(), if it differs],
|
||||
@@ -438,6 +450,12 @@ struct KeyboardWidget {
|
||||
addPlaceholder("{time}");
|
||||
}
|
||||
|
||||
// Open keyboard in PIN mode
|
||||
void beginPin(const char* initial = "", int max = KB_MAX_LEN) {
|
||||
if (prefs) prefs->keyboard_type = 2; // Force number input
|
||||
begin(initial, max);
|
||||
}
|
||||
|
||||
// Insert one UTF-8 codepoint (a grid cell's own glyph, or a picked accent
|
||||
// variant) at cursor_pos, applying Shift/caps-lock the same way every cell
|
||||
// commit does. Shared by the plain-Latin-cell commit below and the accent
|
||||
@@ -628,6 +646,41 @@ struct KeyboardWidget {
|
||||
return 50;
|
||||
}
|
||||
|
||||
// PIN mode
|
||||
if (isPin()) {
|
||||
const int s = miniIconScale(display);
|
||||
for (int r = 0; r < rows; r++) {
|
||||
int y = chars_y + r * cell_h;
|
||||
for (int c = 0; c < cols; c++) {
|
||||
bool sel = (row == r && col == c);
|
||||
int cx = c * cell_w;
|
||||
display.drawSelectionRow(cx, y - 1, cell_w - 1, cell_h, sel);
|
||||
char ch = KB_PIN_DIGITS[r][c];
|
||||
if (ch == ' ') continue; // blank cells beside 0
|
||||
char ch_buf[2];
|
||||
ch_buf[0] = ch;
|
||||
ch_buf[1] = '\0';
|
||||
int tw = display.getTextWidth(ch_buf);
|
||||
display.setCursor(cx + (cell_w - tw) / 2, y);
|
||||
display.print(ch_buf);
|
||||
}
|
||||
}
|
||||
// special row with backspace, abc kb switch & submit
|
||||
const int psw = display.width() / KB_PIN_SPECIAL;
|
||||
const int icy = spec_y + (cell_h - lh) / 2;
|
||||
for (int i = 0; i < KB_PIN_SPECIAL; i++) {
|
||||
bool sel = (row == rows && col == i);
|
||||
int sx = i * psw;
|
||||
display.drawSelectionRow(sx, spec_y - 1, psw - 1, cell_h, sel);
|
||||
const MiniIcon& ic = (i == 0) ? ICON_BACKSPACE
|
||||
: (i == 1) ? ICON_KEYBOARD
|
||||
: ICON_CHECK;
|
||||
int ix = sx + (psw - ic.w * s) / 2;
|
||||
miniIconDraw(display, ix, icy, ic);
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
// Compact mode (Settings > Keyboard's "Ext. KB" row): an external-keyboard
|
||||
// typist never looks at the letter grid or special-row icons, so skip
|
||||
// drawing them entirely -- no status line either, since nothing it could
|
||||
@@ -791,7 +844,7 @@ struct KeyboardWidget {
|
||||
// just fine -- so this only checks that no other exclusive input mode
|
||||
// (popup/cursor-move) is already in progress, same as inPlainGridState().
|
||||
bool openAccentFor(char base) {
|
||||
if (!isVisible() || _ph_menu.active || cursor_mode || accent_active) return false;
|
||||
if (!isVisible() || isPin() || _ph_menu.active || cursor_mode || accent_active) return false;
|
||||
int gi = findAccentGroup(base);
|
||||
if (gi < 0) return false;
|
||||
accent_active = true;
|
||||
@@ -908,6 +961,69 @@ struct KeyboardWidget {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
// PIN mode: a dedicated numeric keypad
|
||||
if (isPin()) {
|
||||
const int rows = gridRows();
|
||||
const int cols = gridCols();
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
if (row == rows && col == 0) { // Backspace
|
||||
len = 0; buf[0] = '\0';
|
||||
cursor_pos = 0;
|
||||
t9_cell = -1;
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_UP) { // Navigation
|
||||
row = (row > 0) ? row - 1 : rows;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_DOWN) {
|
||||
row = (row < rows) ? row + 1 : 0;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_LEFT) {
|
||||
int max_col = (row == rows) ? KB_PIN_SPECIAL - 1 : cols - 1;
|
||||
col = (col > 0) ? col - 1 : max_col;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_RIGHT) {
|
||||
int max_col = (row == rows) ? KB_PIN_SPECIAL - 1 : cols - 1;
|
||||
col = (col < max_col) ? col + 1 : 0;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
if (row < rows) {
|
||||
char d = KB_PIN_DIGITS[row][col];
|
||||
if (d == ' ') return NONE; // ignore blanks
|
||||
if (len < max_len) { // Digit
|
||||
memmove(buf + cursor_pos + 1, buf + cursor_pos, len - cursor_pos);
|
||||
buf[cursor_pos] = d;
|
||||
len++; cursor_pos++;
|
||||
buf[len] = '\0';
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
// special row
|
||||
if (col == 0) {
|
||||
t9_cell = -1;
|
||||
if (cursor_pos > 0) {
|
||||
int n = kbUtf8LastCharBytes(buf, cursor_pos);
|
||||
memmove(buf + cursor_pos - n, buf + cursor_pos, len - cursor_pos);
|
||||
len -= n; cursor_pos -= n;
|
||||
buf[len] = '\0';
|
||||
}
|
||||
} else if (col == 1) {
|
||||
if (prefs) prefs->keyboard_type = 0; // ABC switch
|
||||
page = 0;
|
||||
t9_cell = -1;
|
||||
} else {
|
||||
return DONE; // Submit
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
|
||||
const int rows = gridRows();
|
||||
const int cols = gridCols();
|
||||
|
||||
|
||||
@@ -721,6 +721,7 @@ class SettingsScreen : public UIScreen {
|
||||
int _edit_slot = -1; // -1 = not editing, 0..9 = slot being edited
|
||||
bool _edit_name = false; // editing DEVICE_NAME via the keyboard
|
||||
bool _edit_lock_pass = false; // editing the lock-screen password via the keyboard
|
||||
uint8_t _lock_pass_saved_type = 0; // remember keyboard set to restore setting after pin entry
|
||||
KeyboardWidget* _kb;
|
||||
|
||||
// Scope list management (SCOPE_NAME row -> a full-screen add/rename/
|
||||
@@ -877,8 +878,10 @@ public:
|
||||
_dirty = true; // savePrefsIfDirty persists new password
|
||||
}
|
||||
_edit_lock_pass = false;
|
||||
if (p) p->keyboard_type = _lock_pass_saved_type; // restore user setting
|
||||
} else if (res == KeyboardWidget::CANCELLED) {
|
||||
_edit_lock_pass = false;
|
||||
if (p) p->keyboard_type = _lock_pass_saved_type; // restore user setting
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1140,7 +1143,8 @@ public:
|
||||
_dirty = true;
|
||||
} else {
|
||||
_edit_lock_pass = true;
|
||||
_kb->begin("", (int)sizeof(p->lock_screen_password) - 1);
|
||||
_lock_pass_saved_type = p ? p->keyboard_type : 0; // remember setting to restore after
|
||||
_kb->beginPin("", (int)sizeof(p->lock_screen_password) - 1);
|
||||
_kb->clearPlaceholders(); // a password is literal, not a template message
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -2260,7 +2260,8 @@ bool UITask::checkNodeLockPassword(const char* entered) const {
|
||||
void UITask::beginUnlockPrompt() {
|
||||
_unlock_kb = true; // Track that keyboard is visible and is waiting for input
|
||||
int max_len = _node_prefs ? (int)sizeof(_node_prefs->lock_screen_password) - 1 : 32;
|
||||
_kb.begin("", max_len);
|
||||
_lock_pin_restore_kb_type = _node_prefs ? _node_prefs->keyboard_type : 0;
|
||||
_kb.beginPin("", max_len);
|
||||
_kb.clearPlaceholders();
|
||||
_lock_wake_until = millis() + 5000; // keep the display on while typing
|
||||
_next_refresh = 0;
|
||||
@@ -2268,6 +2269,10 @@ void UITask::beginUnlockPrompt() {
|
||||
|
||||
void UITask::cancelUnlockPrompt() {
|
||||
_unlock_kb = false;
|
||||
if (_node_prefs) _node_prefs->keyboard_type = _lock_pin_restore_kb_type; // restore user setting
|
||||
_kb.buf[0] = '\0'; // clear input
|
||||
_kb.len = 0;
|
||||
_kb.cursor_pos = 0;
|
||||
_next_refresh = 100;
|
||||
}
|
||||
|
||||
@@ -2278,6 +2283,7 @@ void UITask::handleUnlockKey(char c) {
|
||||
// Match: Unlock
|
||||
_unlock_kb = false;
|
||||
_locked = false;
|
||||
if (_node_prefs) _node_prefs->keyboard_type = _lock_pin_restore_kb_type; // restore user setting
|
||||
if (_display && !_display->isOn()) _display->turnOn();
|
||||
uint32_t aoff = autoOffMillis();
|
||||
if (aoff > 0) _auto_off = millis() + aoff;
|
||||
|
||||
@@ -60,6 +60,7 @@ class UITask : public AbstractUITask {
|
||||
bool _lock_seq_used; // true = suppress next back_btn CLICK (post-sequence release)
|
||||
// True while the lock screen shows the on-screen keyboard and waits for submission
|
||||
bool _unlock_kb = false;
|
||||
uint8_t _lock_pin_restore_kb_type = 0; // remember keyboard set to restore setting after pin entry
|
||||
char _alert[80];
|
||||
char _notif_mel_buf[220]; // persistent RTTTL buffer for custom notification melodies
|
||||
// Persistent RTTTL buffer for the bot !buzz command (see botBuzz()) -- sized
|
||||
|
||||
@@ -451,6 +451,11 @@ MINI_ICON(ICON_MAP_TARGET, 5, // ⚑ flag on a pole — the active Locator/N
|
||||
packRow("#...."));
|
||||
|
||||
// Keyboard special-key glyphs.
|
||||
MINI_ICON(ICON_KEYBOARD, 7, // PIN keyboard to ABC keyboard switch icon
|
||||
packRow("#.#.#.#"),
|
||||
packRow("#######"),
|
||||
packRow("#.#.#.#"),
|
||||
packRow("#######"));
|
||||
MINI_ICON(ICON_SHIFT, 7, // ⇧ caps
|
||||
packRow("...#..."),
|
||||
packRow("..###.."),
|
||||
|
||||
Reference in New Issue
Block a user