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:
@@ -4,7 +4,7 @@
|
||||
// - lock screen (Settings > Display & power > Lock screen, NodePrefs::auto_lock):
|
||||
// once the screen turns off, waking shows a clock card and nothing reacts
|
||||
// until "slide to unlock" -- against touches in a pocket; with a screen PIN
|
||||
// (NVS, lvport::loadPin) the card always comes up, also after a reboot, and
|
||||
// (NodePrefs, ui-core/ScreenLock.h) the card always comes up, also after a reboot, and
|
||||
// asks for the PIN on a keypad instead of the slider;
|
||||
// - favourites dial (Home > Favourites, NodePrefs::favourite_contacts): six
|
||||
// slots holding a contact, room or channel; tap opens it, hold changes /
|
||||
@@ -24,8 +24,6 @@ static lv_obj_t* s_lock_slider = nullptr;
|
||||
static lv_obj_t* s_pin_dots = nullptr; // lock card / setup popup: one dot per digit typed
|
||||
static lv_obj_t* s_pin_msg = nullptr; // "Wrong PIN" / "Try again in 30 s" / setup step
|
||||
static const uint8_t PIN_MIN = 4, PIN_MAX = 8;
|
||||
static const uint8_t PIN_TRIES = 5; // misses before a pause
|
||||
static const uint32_t PIN_PAUSE_MS = 30000;
|
||||
static const char* const PIN_MAP[] = { "1", "2", "3", "\n", "4", "5", "6", "\n", "7", "8", "9", "\n",
|
||||
LV_SYMBOL_BACKSPACE, "0", LV_SYMBOL_OK, "" };
|
||||
|
||||
@@ -174,7 +172,7 @@ void UITask::lockScreen() {
|
||||
lv_obj_set_flex_align(s_lock, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_top(s_lock, 18, 0);
|
||||
lv_obj_set_style_pad_row(s_lock, 4, 0);
|
||||
if (_pin[0]) { // compact card: the time and unread on one line, the keypad below
|
||||
if (pinSet()) { // compact card: the time and unread on one line, the keypad below
|
||||
lv_obj_set_style_pad_top(s_lock, 4, 0);
|
||||
lv_obj_set_style_pad_row(s_lock, 2, 0);
|
||||
lv_obj_t* top = lv_obj_create(s_lock);
|
||||
@@ -297,9 +295,9 @@ void UITask::refreshLock() {
|
||||
if (unread > 0) lv_label_set_text_fmt(s_lock_unread, LV_SYMBOL_ENVELOPE " %d", unread);
|
||||
else lv_label_set_text(s_lock_unread, "");
|
||||
if (!s_pin_msg) return;
|
||||
int32_t left = (int32_t)(_pin_block_until - millis());
|
||||
if (_pin_block_until && left > 0) lv_label_set_text_fmt(s_pin_msg, "Too many tries - wait %ld s", (long)((left + 999) / 1000));
|
||||
else if (_pin_block_until) { _pin_block_until = 0; lv_label_set_text(s_pin_msg, "Enter PIN"); }
|
||||
bool was = _pin_tries.block_until != 0;
|
||||
if (_pin_tries.blocked()) lv_label_set_text_fmt(s_pin_msg, "Too many tries - wait %lu s", (unsigned long)_pin_tries.secondsLeft());
|
||||
else if (was) lv_label_set_text(s_pin_msg, "Enter PIN");
|
||||
else if (!lv_label_get_text(s_pin_msg)[0]) lv_label_set_text(s_pin_msg, "Enter PIN");
|
||||
return;
|
||||
}
|
||||
@@ -309,28 +307,28 @@ void UITask::refreshLock() {
|
||||
|
||||
// ── Screen PIN ────────────────────────────────────────────────────────────────
|
||||
|
||||
// Lock card keypad: the PIN unlocks as soon as it's complete (OK checks a
|
||||
// shorter entry); five misses pause entry for 30 s.
|
||||
// Lock card keypad: the PIN unlocks as soon as it matches (from 4 digits on;
|
||||
// only its hash is kept, so not its length); OK or a full 8 digits that
|
||||
// don't match count a miss, and five misses pause entry for 30 s.
|
||||
void UITask::pinKey(const char* key) {
|
||||
using namespace devview;
|
||||
if (!s_lock || !_pin[0]) return;
|
||||
if (_pin_block_until && (int32_t)(_pin_block_until - millis()) > 0) return;
|
||||
if (!s_lock || !pinSet()) return;
|
||||
if (_pin_tries.blocked()) return;
|
||||
bool ok = pinEdit(_pin_entry, key);
|
||||
size_t n = strlen(_pin_entry);
|
||||
if (!ok && n < strlen(_pin)) {
|
||||
if (n >= PIN_MIN && screenlock::check(*_prefs, _pin_entry)) { _pin_tries.ok(); unlockScreen(); return; }
|
||||
if (!ok && n < PIN_MAX) {
|
||||
if (n && s_pin_msg) lv_label_set_text(s_pin_msg, "Enter PIN");
|
||||
return;
|
||||
}
|
||||
if (!n) return;
|
||||
if (!strcmp(_pin_entry, _pin)) { _pin_fails = 0; unlockScreen(); return; }
|
||||
_pin_entry[0] = '\0';
|
||||
showDots(_pin_entry);
|
||||
if (++_pin_fails >= PIN_TRIES) {
|
||||
_pin_fails = 0;
|
||||
_pin_block_until = (millis() + PIN_PAUSE_MS) | 1;
|
||||
if (_pin_tries.miss()) {
|
||||
refreshLock();
|
||||
} else if (s_pin_msg) {
|
||||
lv_label_set_text_fmt(s_pin_msg, "Wrong PIN - %d tr%s left", PIN_TRIES - _pin_fails, PIN_TRIES - _pin_fails == 1 ? "y" : "ies");
|
||||
int left = _pin_tries.left();
|
||||
lv_label_set_text_fmt(s_pin_msg, "Wrong PIN - %d tr%s left", left, left == 1 ? "y" : "ies");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,10 +337,10 @@ void UITask::pinKey(const char* key) {
|
||||
void UITask::pinSetupPopup() {
|
||||
using namespace devview;
|
||||
_pin_entry[0] = _pin_new[0] = '\0';
|
||||
lv_obj_t* panel = navPopupPanel(_pin[0] ? "Change PIN" : "Set screen PIN", true);
|
||||
lv_obj_t* panel = navPopupPanel(pinSet() ? "Change PIN" : "Set screen PIN", true);
|
||||
lv_obj_set_flex_align(panel, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_row(panel, 2, 0);
|
||||
if (_pin[0]) { // next to the close button
|
||||
if (pinSet()) { // next to the close button
|
||||
lv_obj_t* l;
|
||||
headerButton(lv_obj_get_child(panel, 0), LV_SYMBOL_TRASH " Remove", onPinRemove, 44, &l);
|
||||
lv_obj_set_style_text_color(l, lv_color_hex(theme::FAIL), 0);
|
||||
@@ -369,8 +367,10 @@ void UITask::pinSetupKey(const char* key) {
|
||||
lv_label_set_text(s_pin_msg, "Didn't match - new PIN again");
|
||||
return;
|
||||
}
|
||||
strcpy(_pin, _pin_new);
|
||||
lvport::savePin(_pin);
|
||||
if (_prefs) {
|
||||
screenlock::set(*_prefs, _pin_new, the_mesh.getRNG());
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
_pin_new[0] = _pin_entry[0] = '\0';
|
||||
navClosePopup();
|
||||
showToast("PIN set - asked each time the screen wakes", 3000);
|
||||
@@ -378,8 +378,10 @@ void UITask::pinSetupKey(const char* key) {
|
||||
}
|
||||
|
||||
void UITask::pinRemove() {
|
||||
_pin[0] = '\0';
|
||||
lvport::savePin("");
|
||||
if (_prefs) {
|
||||
screenlock::clear(*_prefs);
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
navClosePopup();
|
||||
showToast("PIN removed");
|
||||
pinRowRefresh();
|
||||
|
||||
@@ -680,6 +680,7 @@ static int getU8(const char* ns, const char* key, int def) { return getInt(ns,
|
||||
static void putU8(const char* ns, const char* key, int v) { putInt(ns, key, v); }
|
||||
static void getStr(const char* ns, const char* key, char* out, size_t n) { Kv* k = find(ns, key, false); snprintf(out, n, "%s", k ? k->val : ""); }
|
||||
static void putStr(const char* ns, const char* key, const char* v) { if (Kv* k = find(ns, key, true)) snprintf(k->val, sizeof(k->val), "%s", v); }
|
||||
static void remove(const char* ns, const char* key) { if (Kv* k = find(ns, key, false)) k->val[0] = '\0'; }
|
||||
} // namespace nvs
|
||||
|
||||
static bool s_swallow = false;
|
||||
@@ -848,8 +849,12 @@ static bool vectorOn() { return nvs::getBool("mc_ui", "vector", false); }
|
||||
static void setVectorOn(bool on) { nvs::putBool("mc_ui", "vector", on); }
|
||||
|
||||
// Screen-lock PIN (Settings > Display & power > Screen PIN): digits, "" = none.
|
||||
static void loadPin(char* out, size_t n) { nvs::getStr("mc_lock", "pin", out, n); }
|
||||
static void savePin(const char* pin) { nvs::putStr("mc_lock", "pin", pin); }
|
||||
// The screen PIN as older builds kept it (plain text): read once and removed;
|
||||
// it lives hashed in NodePrefs now (ui-core/ScreenLock.h).
|
||||
static void takeOldPin(char* out, size_t n) {
|
||||
nvs::getStr("mc_lock", "pin", out, n);
|
||||
if (out[0]) nvs::remove("mc_lock", "pin");
|
||||
}
|
||||
|
||||
// Accent colour (Settings > Display & power): an index into theme::ACCENTS.
|
||||
static int loadAccent() { return nvs::getU8("mc_ui", "accent", 0); }
|
||||
|
||||
@@ -898,8 +898,15 @@ void UITask::begin(DisplayDriver* display_drv, SensorManager* sensors, NodePrefs
|
||||
buildStatusBar();
|
||||
applyDisplayPrefs(); // a slider percentage overrides the level main.cpp set
|
||||
showHome();
|
||||
lvport::loadPin(_pin, sizeof(_pin));
|
||||
if (_pin[0]) lockScreen(); // a reboot doesn't get round the PIN
|
||||
// Before the PIN moved into NodePrefs it was kept in NVS in plain text.
|
||||
char old_pin[9];
|
||||
lvport::takeOldPin(old_pin, sizeof(old_pin));
|
||||
if (old_pin[0] && _prefs && !pinSet()) {
|
||||
screenlock::set(*_prefs, old_pin, the_mesh.getRNG());
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
memset(old_pin, 0, sizeof(old_pin));
|
||||
if (pinSet()) lockScreen(); // a reboot doesn't get round the PIN
|
||||
showSplash(); // over both; fades out by itself
|
||||
}
|
||||
|
||||
@@ -1175,7 +1182,7 @@ void UITask::sleep() {
|
||||
if (_display) _display->turnOff();
|
||||
prefsSaveSoon(0); // nothing to stall now
|
||||
lvport::powerSave(true, _tap_wake);
|
||||
if ((_prefs && _prefs->auto_lock) || _pin[0]) lockScreen(); // Lock screen, or a screen PIN
|
||||
if ((_prefs && _prefs->auto_lock) || pinSet()) lockScreen(); // Lock screen, or a screen PIN
|
||||
}
|
||||
|
||||
void UITask::wake() {
|
||||
@@ -3212,7 +3219,7 @@ void UITask::buildSchemaSettings() {
|
||||
lv_obj_add_event_cb(tw, onTapWake, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
g = group(body, "LOCK");
|
||||
schemaRow(g, SETTING(auto_lock));
|
||||
listRow(g, "Screen PIN", _pin[0] ? "On - asked when the screen wakes" : "Off", onPinSetup, NULL);
|
||||
listRow(g, "Screen PIN", pinSet() ? "On - asked when the screen wakes" : "Off", onPinSetup, NULL);
|
||||
accentRow(group(body, "LOOK")); // DeviceScreen.h
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../AbstractUITask.h"
|
||||
#include "../NodePrefs.h"
|
||||
#include "../ui-core/UiCoreHost.h"
|
||||
#include "../ui-core/ScreenLock.h"
|
||||
|
||||
class UiCore;
|
||||
class NearbyModel;
|
||||
@@ -458,13 +459,12 @@ private:
|
||||
uint16_t _batt_mv = 0; // smoothed, read every 8 s: the status bar and the low-battery shutdown
|
||||
uint32_t _next_batt_ms = 0;
|
||||
lv_obj_t* _prune_lbl = nullptr;
|
||||
// Screen PIN (DeviceScreen.h); stored in NVS (lvport::loadPin)
|
||||
char _pin[9] = ""; // "" = no PIN
|
||||
// Screen PIN (DeviceScreen.h): a salted hash in NodePrefs (ui-core/ScreenLock.h)
|
||||
bool pinSet() const { return _prefs && screenlock::isSet(*_prefs); }
|
||||
bool _tap_wake = true; // a touch wakes the dark screen (NVS)
|
||||
char _pin_entry[9] = ""; // digits typed so far
|
||||
char _pin_new[9] = ""; // setup: the first entry, waiting for its confirmation
|
||||
uint8_t _pin_fails = 0;
|
||||
uint32_t _pin_block_until = 0; // millis() until which entry is refused after 5 misses
|
||||
screenlock::Attempts _pin_tries;
|
||||
|
||||
// Open conversation (SCR_THREAD)
|
||||
bool _thread_is_channel = false;
|
||||
|
||||
Reference in New Issue
Block a user