feat: Add password input for screen unlock

This commit is contained in:
3urobeat
2026-09-06 16:25:03 +02:00
parent 06e1dcdbfc
commit b710910225
3 changed files with 116 additions and 21 deletions
@@ -63,3 +63,15 @@ Fully autonomous, independent of Auto-lock and of any key combo:
- **Magnet near (cover closed)** — locks and blanks the display immediately, no wake grace.
- **Magnet away (cover opened)** — unlocks and wakes the display right away.
---
### Lock-screen password
Enable **LockPass** in **Settings › Display** to require a password when unlocking the device.
Upon enabling the setting the keyboard will show, requiring you to submit a new password.
Attempting to unlock the device will then require the same password to be typed and submitted to reach the home screen.
Submitting an incorrect password clears the keyboard input.
Disabling the setting clears the stored password and allows an instant unlock again.
+92 -20
View File
@@ -2212,6 +2212,73 @@ void UITask::syncLockToHome() {
if (home) static_cast<HomeScreen*>(home)->setLocked(_locked);
}
bool UITask::passwordLockEnabled() const {
// Is lock_screen_password setting not empty?
return _node_prefs && _node_prefs->lock_screen_password[0] != '\0';
}
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);
_kb.clearPlaceholders();
_lock_wake_until = millis() + 5000; // keep the display on while typing
_next_refresh = 0;
}
void UITask::cancelUnlockPrompt() {
_unlock_kb = false;
_next_refresh = 100;
}
void UITask::handleUnlockKey(char c) {
auto res = _kb.handleInput(c);
if (res == KeyboardWidget::DONE) { // Process input on submit
if (_node_prefs && strcmp(_kb.buf, _node_prefs->lock_screen_password) == 0) {
// Match: Unlock
_unlock_kb = false;
_locked = false;
if (_display && !_display->isOn()) _display->turnOn();
uint32_t aoff = autoOffMillis();
if (aoff > 0) _auto_off = millis() + aoff;
syncLockToHome();
} else {
// Invalid: Clear input and reset keyboard
_kb.len = 0;
_kb.buf[0] = '\0';
_kb.cursor_pos = 0;
_kb.page = _kb.row = _kb.col = 0;
_kb.caps = _kb.caps_lock = false;
_next_refresh = 0;
}
} else if (res == KeyboardWidget::CANCELLED) {
cancelUnlockPrompt();
}
_lock_wake_until = millis() + 5000; // keep the display on while typing
}
void UITask::toggleLock() {
if (_unlock_kb) {
cancelUnlockPrompt();
return;
}
if (_locked) {
if (passwordLockEnabled()) { // Prompt for password
beginUnlockPrompt();
} else { // ...or unlock instantly
_locked = false;
if (_display && !_display->isOn()) _display->turnOn();
uint32_t aoff = autoOffMillis();
if (aoff > 0) _auto_off = millis() + aoff;
}
} else { // Device is currently unlocked -> lock it
_locked = true;
_lock_wake_until = millis() + 2000; // Briefly show lockscreen before blanking
}
syncLockToHome();
_next_refresh = 0;
}
bool UITask::savePrefsIfDirty(bool& dirty) {
if (!dirty) return false;
the_mesh.savePrefs();
@@ -2561,15 +2628,7 @@ void UITask::pollCardKB() {
// two-key combo, so it doesn't need the physical combo's extra 3x
// repetition to guard against accidental triggering.
if (_display && !_display->isOn()) _display->turnOn();
_locked = !_locked;
if (_locked) {
_lock_wake_until = millis() + 2000;
} else {
uint32_t aoff = autoOffMillis();
if (aoff > 0) _auto_off = millis() + aoff;
}
syncLockToHome();
_next_refresh = 0;
toggleLock();
return;
} else if (raw >= 0x80 && raw <= 0xAF) { // Fn+<letter> -- open its accent popup
char base = CARDKB_FN_BASE[raw - 0x80];
@@ -2623,6 +2682,7 @@ void UITask::pollHallSensor() {
_hall_magnet_present = present;
if (present) { // cover closed
cancelUnlockPrompt(); // a cover can't be over the password prompt
_locked = true;
syncLockToHome();
_lock_wake_until = 0;
@@ -2663,15 +2723,7 @@ void UITask::loop() {
if (_lock_seq_count >= 3) {
_lock_seq_count = 0;
_lock_seq_used = true; // suppress Back release click
_locked = !_locked;
if (_locked) {
_lock_wake_until = millis() + 2000;
} else {
if (_display && !_display->isOn()) _display->turnOn();
uint32_t aoff = autoOffMillis();
if (aoff > 0) _auto_off = millis() + aoff;
}
syncLockToHome();
toggleLock();
}
// eat the Enter — don't pass to curr
} else {
@@ -2864,7 +2916,12 @@ void UITask::loop() {
}
if (_kq_head != _kq_tail) {
if (!_locked && curr) {
if (_unlock_kb) {
// Lock-screen password keyboard: Consume every key press
char k;
while (dequeueKey(k)) handleUnlockKey(k);
_next_refresh = 100; // redraw immediately after key press
} else if (!_locked && curr) {
// Apply the whole queued burst, then redraw once — N taps captured during
// a blocking refresh become N navigation steps at the cost of one refresh.
char k;
@@ -2901,8 +2958,22 @@ void UITask::loop() {
tickClockTools();
if (_display != NULL && _display->isOn()) {
if (_locked && (int32_t)(millis() - _lock_wake_until) >= 0) {
// Lock-screen password prompt
if (_locked && _unlock_kb && (int32_t)(millis() - _lock_wake_until) >= 0) {
cancelUnlockPrompt(); // Cancel unlock attempt on idle
_next_refresh = 0;
}
if (_locked && !_unlock_kb && (int32_t)(millis() - _lock_wake_until) >= 0) {
_display->turnOff();
} else if (_locked && _unlock_kb && millis() >= _next_refresh) {
// While the prompt is up the password keyboard replaces the lockscreen view
_display->startFrame();
_kb.beginFrame();
int delay_millis = _kb.render(*_display);
// Alert overlay on top // TODO: Is this necessary here?
// if (millis() < _alert_expiry) renderAlertOverlay();
_display->endFrame();
_next_refresh = millis() + delay_millis;
} else if (_locked && millis() >= _next_refresh && home) {
_display->startFrame();
home->render(*_display);
@@ -2950,6 +3021,7 @@ void UITask::loop() {
digitalWrite(PIN_LED, LOW); // turn off status LED with display to save power
#endif
if (_node_prefs && _node_prefs->auto_lock) {
cancelUnlockPrompt(); // idle-lock isn't a password prompt
_locked = true;
_lock_wake_until = 0;
syncLockToHome();
+12 -1
View File
@@ -58,6 +58,8 @@ class UITask : public AbstractUITask {
int _lock_seq_count; // Enter presses while Back held (lock/unlock sequence)
unsigned long _lock_seq_ms; // millis() of last lock-sequence press (for timeout)
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;
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
@@ -288,6 +290,15 @@ private:
// dedicated lock-screen code path in loop().
void syncLockToHome();
// Whether a lockscreen password is set
bool passwordLockEnabled() const;
// Handles (un)locking and requesting password input when one is set
void toggleLock();
void beginUnlockPrompt();
void cancelUnlockPrompt();
// Handles shortcuts during lockscreen password input
void handleUnlockKey(char c);
// Centred alert overlay (the showAlert() box). Wraps long text to up to
// three lines inside the box instead of letting it overflow the border.
// Shared by the normal render path and the lock screen (so a ringing
@@ -566,7 +577,7 @@ public:
#endif
}
bool isBuzzerQuiet() {
bool isBuzzerQuiet() {
#ifdef PIN_BUZZER
return buzzer.isQuiet();
#else