mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-21 21:12:03 +00:00
Merge PR #37: lock screen PIN (3urobeat), adapted
The contributor's feature -- a PIN asked before the screen unlocks, on
boot, and when the magnet cover opens, stored as a salted SHA-256 -- on
top of this branch, with:
- ui-core/ScreenLock.h: set / check / isSet and the tries limit, shared by
L1 and L2. isSet looks at every byte (the PR tested only the first, so one
PIN in 256 silently disabled the lock). 5 misses pause entry for 30 s.
- NodePrefs: the fields at the tail after quiet hours, sentinel 0xC0DE0031,
sizeof 2880; an older file's bytes there are cleared. A sentinel bump no
longer resets display_brightness_pct from a file that already has it.
- DataStore: no AGPL header (the project is MIT), no whitespace churn.
- Keyboard: the number pad is a widget flag (pin_mode) with a prompt in the
preview ("New PIN", "Again", "PIN"), not keyboard_type = 2 written into the
prefs and restored after.
- L1 Settings: "Lock PIN" right after the schema's "Lock screen" row, typed
twice, at least 4 characters, saved at once (a power-off right after
setting it kept no PIN). Home is told about a boot-time lock once it
exists (also fixes booting with the cover closed).
- L2: the screen PIN moves from plain text in NVS to the same hash in
NodePrefs; the old one is migrated on first boot and removed. The pad
unlocks as soon as the hash matches from 4 digits on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,9 @@ class SettingsScreen : public UIScreen {
|
||||
SCHEMA_MESSAGES,
|
||||
MSG_SLOT_0, MSG_SLOT_1, MSG_SLOT_2, MSG_SLOT_3, MSG_SLOT_4,
|
||||
MSG_SLOT_5, MSG_SLOT_6, MSG_SLOT_7, MSG_SLOT_8, MSG_SLOT_9,
|
||||
Count
|
||||
Count,
|
||||
// Not walked from the enum: placed right after the schema's "Lock screen" row.
|
||||
LOCK_PIN
|
||||
};
|
||||
// A schema setting's row: SCHEMA_ITEM + its index in settings::ALL.
|
||||
static const int SCHEMA_ITEM = 128;
|
||||
@@ -161,8 +163,11 @@ class SettingsScreen : public UIScreen {
|
||||
_sec_count[cur] = 0;
|
||||
} else if (cur >= 0 && isSchemaGroup(i)) {
|
||||
for (int k = 0; k < settings::COUNT && _sec_count[cur] < MAX_PER_SEC; k++)
|
||||
if (schemaIn(i, settings::ALL[k].section) && schemaShown(settings::ALL[k]))
|
||||
if (schemaIn(i, settings::ALL[k].section) && schemaShown(settings::ALL[k])) {
|
||||
_sec_items[cur][_sec_count[cur]++] = (uint8_t)(SCHEMA_ITEM + k);
|
||||
if (settings::ALL[k].offset == offsetof(NodePrefs, auto_lock) && _sec_count[cur] < MAX_PER_SEC)
|
||||
_sec_items[cur][_sec_count[cur]++] = (uint8_t)LOCK_PIN;
|
||||
}
|
||||
} else if (cur >= 0 && _sec_count[cur] < MAX_PER_SEC) {
|
||||
_sec_items[cur][_sec_count[cur]++] = (uint8_t)i;
|
||||
}
|
||||
@@ -213,6 +218,11 @@ class SettingsScreen : public UIScreen {
|
||||
// A schema setting: its short label, the value in the value column (a
|
||||
// switch as ON / OFF, brightness and volume as bars). A value too long for
|
||||
// the column starts further left, clear of the label, else scrolls.
|
||||
void beginPinEntry(const char* prompt) {
|
||||
_kb->beginPin("", KeyboardWidget::PIN_MAX_LEN, false, prompt);
|
||||
_kb->clearPlaceholders(); // a PIN is literal, not a template message
|
||||
}
|
||||
|
||||
int renderSchema(DisplayDriver& display, const settings::Setting& st, NodePrefs* p, int y, bool sel) {
|
||||
const char* label = settings::shortLabel(st);
|
||||
display.print(label);
|
||||
@@ -545,6 +555,10 @@ class SettingsScreen : public UIScreen {
|
||||
int r = display.drawTextEllipsized(vx, y, display.width() - vx - _reserve,
|
||||
sl.name(sl.default_idx), sel);
|
||||
if (sel && r > 0) mq_delay = r;
|
||||
} else if (item == LOCK_PIN) {
|
||||
display.print("Lock PIN");
|
||||
display.setCursor(valCol(display), y);
|
||||
display.print(_task->passwordLockEnabled() ? "ON" : "OFF");
|
||||
} else if (item == DEVICE_NAME) {
|
||||
display.print("Name");
|
||||
int vx = valCol(display);
|
||||
@@ -620,6 +634,8 @@ class SettingsScreen : public UIScreen {
|
||||
// Keyboard state for editing message slots
|
||||
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; // setting the screen PIN via the keyboard
|
||||
char _lock_pass_first[KeyboardWidget::PIN_MAX_LEN + 1] = ""; // the first entry, awaiting its repeat
|
||||
KeyboardWidget* _kb;
|
||||
|
||||
// Scope list management (SCOPE_NAME row -> a full-screen add/rename/
|
||||
@@ -691,6 +707,8 @@ public:
|
||||
_scope_action_menu.active = false;
|
||||
_scope_delete_confirm_active = false;
|
||||
_prune_confirm.active = false;
|
||||
_edit_lock_pass = false;
|
||||
_lock_pass_first[0] = '\0';
|
||||
resetList();
|
||||
_editor.freq.active = false;
|
||||
}
|
||||
@@ -698,7 +716,7 @@ public:
|
||||
int render(DisplayDriver& display) override {
|
||||
display.setTextSize(1);
|
||||
|
||||
if (_edit_slot >= 0 || _edit_name || _scope_rename_idx != -2 || _picker.saving) {
|
||||
if (_edit_slot >= 0 || _edit_name || _edit_lock_pass || _scope_rename_idx != -2 || _picker.saving) {
|
||||
return _kb->render(display);
|
||||
}
|
||||
|
||||
@@ -766,6 +784,36 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keyboard editing mode for the lock-screen password
|
||||
if (_edit_lock_pass) {
|
||||
auto res = _kb->handleInput(c);
|
||||
if (res == KeyboardWidget::DONE) {
|
||||
// Entered twice: a typo here would lock the owner out.
|
||||
if (strlen(_kb->buf) < 4) {
|
||||
_kb->pin_prompt = "Min 4";
|
||||
} else if (!_lock_pass_first[0]) {
|
||||
strncpy(_lock_pass_first, _kb->buf, sizeof(_lock_pass_first) - 1);
|
||||
_lock_pass_first[sizeof(_lock_pass_first) - 1] = '\0';
|
||||
beginPinEntry("Again");
|
||||
} else if (strcmp(_lock_pass_first, _kb->buf) != 0) {
|
||||
_lock_pass_first[0] = '\0';
|
||||
beginPinEntry("No match");
|
||||
} else {
|
||||
if (p) {
|
||||
_task->setNodeLockPassword(_kb->buf);
|
||||
the_mesh.savePrefs(); // now, not on leaving: a PIN must survive a power-off right after
|
||||
}
|
||||
_edit_lock_pass = false;
|
||||
_lock_pass_first[0] = '\0';
|
||||
_task->showAlert("PIN set", 900);
|
||||
}
|
||||
} else if (res == KeyboardWidget::CANCELLED) {
|
||||
_edit_lock_pass = false;
|
||||
_lock_pass_first[0] = '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keyboard editing mode for adding/renaming a scope-list entry
|
||||
if (_scope_rename_idx != -2) {
|
||||
auto res = _kb->handleInput(c);
|
||||
@@ -974,6 +1022,19 @@ public:
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
// Lock PIN: Enter sets one, or clears the one set
|
||||
if (_selected == LOCK_PIN && p && enter) {
|
||||
if (_task->passwordLockEnabled()) {
|
||||
_task->setNodeLockPassword("");
|
||||
the_mesh.savePrefs();
|
||||
_task->showAlert("PIN cleared", 900);
|
||||
} else {
|
||||
_edit_lock_pass = true;
|
||||
_lock_pass_first[0] = '\0';
|
||||
beginPinEntry("New PIN");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (_selected == DEVICE_NAME && p && enter) {
|
||||
_edit_name = true;
|
||||
_kb->begin(the_mesh.getNodeName(), (int)sizeof(p->node_name) - 1);
|
||||
|
||||
Reference in New Issue
Block a user