mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat: screen lock — hold Back + 3×Enter to lock/unlock
- Long press Back starts lock sequence; 3 Enter presses within 3s toggle lock - Locked screen wakes for 5 seconds on any key, showing clock and unlock hint - Incoming messages do not wake the display while locked - Auto Lock setting in Display section: locks automatically when screen turns off - Lock state persisted in UITask; NodePrefs gains auto_lock flag Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t default_scope_key[16];
|
||||
uint8_t display_brightness; // 0=min..4=max, default 2 (medium)
|
||||
uint16_t auto_off_secs; // display auto-off: 0=never, else seconds (default 15)
|
||||
uint8_t auto_lock; // 0=disabled, 1=lock screen when display turns off
|
||||
int8_t tz_offset_hours; // timezone offset from UTC, -12..+14 (default 0)
|
||||
uint16_t low_batt_mv; // auto-shutdown threshold: 0=disabled, 3000-3500 mV
|
||||
uint8_t batt_display_mode; // 0=icon, 1=percent, 2=voltage
|
||||
|
||||
@@ -10,6 +10,7 @@ class SettingsScreen : public UIScreen {
|
||||
SECTION_DISPLAY,
|
||||
BRIGHTNESS,
|
||||
AUTO_OFF,
|
||||
AUTO_LOCK,
|
||||
BATT_DISPLAY,
|
||||
CLOCK_SECONDS,
|
||||
// Sound section
|
||||
@@ -279,6 +280,10 @@ class SettingsScreen : public UIScreen {
|
||||
display.print("AutoOff");
|
||||
display.setCursor(VAL_X, y);
|
||||
display.print(AUTO_OFF_LABELS[autoOffIndex()]);
|
||||
} else if (item == AUTO_LOCK) {
|
||||
display.print("AutoLock");
|
||||
display.setCursor(VAL_X, y);
|
||||
display.print((p && p->auto_lock) ? "ON" : "OFF");
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (item == GPS_INTERVAL) {
|
||||
display.print("GPS upd");
|
||||
@@ -452,6 +457,11 @@ public:
|
||||
if (left) idx = (idx + AUTO_OFF_COUNT - 1) % AUTO_OFF_COUNT;
|
||||
if (left || right) { p->auto_off_secs = AUTO_OFF_OPTS[idx]; _dirty = true; return true; }
|
||||
}
|
||||
if (_selected == AUTO_LOCK && p && (left || right || enter)) {
|
||||
p->auto_lock ^= 1;
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (_selected == GPS_INTERVAL && p) {
|
||||
int idx = gpsIntervalIndex();
|
||||
|
||||
@@ -1049,7 +1049,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", from_name);
|
||||
showAlert(alert_buf, 3000);
|
||||
|
||||
if (_display != NULL) {
|
||||
if (_display != NULL && !_locked) {
|
||||
if (!_display->isOn() && !hasConnection()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
@@ -1157,6 +1157,13 @@ void UITask::loop() {
|
||||
ev = back_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_CANCEL);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
if (_display != NULL && _display->isOn()) {
|
||||
_lock_seq = 1;
|
||||
_lock_seq_count = 0;
|
||||
_lock_seq_ms = millis();
|
||||
_next_refresh = 0;
|
||||
}
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
@@ -1199,10 +1206,48 @@ void UITask::loop() {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (c != 0 && curr) {
|
||||
curr->handleInput(c);
|
||||
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
// Lock sequence timeout check
|
||||
if (_lock_seq == 1 && millis() - _lock_seq_ms > 3000) {
|
||||
_lock_seq = 0;
|
||||
_lock_seq_count = 0;
|
||||
}
|
||||
|
||||
if (c != 0) {
|
||||
// Intercept Enter presses when a lock/unlock sequence is active
|
||||
if (_lock_seq == 1 && c == KEY_ENTER) {
|
||||
_lock_seq_count++;
|
||||
if (_lock_seq_count >= 3) {
|
||||
_lock_seq = 0;
|
||||
_lock_seq_count = 0;
|
||||
_locked = !_locked;
|
||||
if (_locked) {
|
||||
_lock_wake_until = millis() + 2000; // brief "locked" display, then blank
|
||||
} else {
|
||||
uint32_t aoff = autoOffMillis();
|
||||
if (aoff > 0) _auto_off = millis() + aoff;
|
||||
}
|
||||
}
|
||||
_next_refresh = 0;
|
||||
} else if (_lock_seq == 1) {
|
||||
// Non-Enter key cancels the sequence; process key normally below
|
||||
_lock_seq = 0;
|
||||
_lock_seq_count = 0;
|
||||
if (!_locked && curr) {
|
||||
curr->handleInput(c);
|
||||
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; }
|
||||
_next_refresh = 100;
|
||||
}
|
||||
} else if (!_locked && curr) {
|
||||
curr->handleInput(c);
|
||||
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
} else if (_locked) {
|
||||
// Locked: eat all keys except unlock sequence (handled above)
|
||||
// Any key extends the wake window if display is already on
|
||||
if (_display != NULL && _display->isOn())
|
||||
_lock_wake_until = millis() + 5000;
|
||||
_next_refresh = 0;
|
||||
}
|
||||
}
|
||||
|
||||
userLedHandler();
|
||||
@@ -1221,7 +1266,49 @@ void UITask::loop() {
|
||||
if (curr) curr->poll();
|
||||
|
||||
if (_display != NULL && _display->isOn()) {
|
||||
if (millis() >= _next_refresh && curr) {
|
||||
if (_locked && millis() > _lock_wake_until) {
|
||||
_display->turnOff();
|
||||
} else if (_locked && millis() >= _next_refresh) {
|
||||
_display->startFrame();
|
||||
// Lock screen: clock + unlock hint popup
|
||||
uint32_t unix_ts = rtc_clock.getCurrentTime();
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
if (unix_ts < 1000000000UL) {
|
||||
_display->setTextSize(1);
|
||||
_display->drawTextCentered(_display->width() / 2, 20, "No time sync");
|
||||
} else {
|
||||
int8_t tz = _node_prefs ? _node_prefs->tz_offset_hours : 0;
|
||||
unix_ts += (int32_t)tz * 3600;
|
||||
time_t t = (time_t)unix_ts;
|
||||
struct tm* ti = gmtime(&t);
|
||||
char buf[12];
|
||||
_display->setTextSize(2);
|
||||
sprintf(buf, "%02d:%02d", ti->tm_hour, ti->tm_min);
|
||||
_display->drawTextCentered(_display->width() / 2, 8, buf);
|
||||
_display->setTextSize(1);
|
||||
static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
||||
static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
||||
sprintf(buf, "%s %d %s", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon]);
|
||||
_display->drawTextCentered(_display->width() / 2, 26, buf);
|
||||
}
|
||||
// Hint popup at bottom (like alert style)
|
||||
_display->setTextSize(1);
|
||||
const char* hint = _lock_seq == 1
|
||||
? (_lock_seq_count == 0 ? "Enter x3 to unlock" :
|
||||
_lock_seq_count == 1 ? "Enter x2 more..." : "Enter x1 more...")
|
||||
: "Hold Back + 3xEnter";
|
||||
int p = 3;
|
||||
int hy = _display->height() - 13;
|
||||
int hw = _display->getTextWidth(hint);
|
||||
int hx = (_display->width() - hw) / 2;
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->fillRect(hx - p, hy - p, hw + p*2, 8 + p*2);
|
||||
_display->setColor(DisplayDriver::DARK);
|
||||
_display->setCursor(hx, hy);
|
||||
_display->print(hint);
|
||||
_display->endFrame();
|
||||
_next_refresh = millis() + 1000;
|
||||
} else if (!_locked && millis() >= _next_refresh && curr) {
|
||||
_display->startFrame();
|
||||
int delay_millis = curr->render(*_display);
|
||||
if (millis() < _alert_expiry && curr == home) { // render alert only on home screen
|
||||
@@ -1240,11 +1327,15 @@ void UITask::loop() {
|
||||
_display->endFrame();
|
||||
}
|
||||
#if AUTO_OFF_MILLIS > 0
|
||||
if (autoOffMillis() > 0 && millis() > _auto_off) {
|
||||
if (!_locked && autoOffMillis() > 0 && millis() > _auto_off) {
|
||||
_display->turnOff();
|
||||
#ifdef PIN_LED
|
||||
digitalWrite(PIN_LED, LOW); // turn off status LED with display to save power
|
||||
#endif
|
||||
if (_node_prefs && _node_prefs->auto_lock) {
|
||||
_locked = true;
|
||||
_lock_wake_until = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1283,9 +1374,17 @@ char UITask::checkDisplayOn(char c) {
|
||||
#ifdef PIN_LED
|
||||
digitalWrite(PIN_LED, LOW); // ensure LED is off when waking display (userLedHandler takes over)
|
||||
#endif
|
||||
if (_locked) {
|
||||
_lock_wake_until = millis() + 5000;
|
||||
_next_refresh = 0;
|
||||
return 0; // eat the waking key press
|
||||
}
|
||||
c = 0;
|
||||
}
|
||||
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
|
||||
if (!_locked) {
|
||||
uint32_t aoff = autoOffMillis();
|
||||
if (aoff > 0) _auto_off = millis() + aoff; // extend auto-off timer
|
||||
}
|
||||
_next_refresh = 0; // trigger refresh
|
||||
}
|
||||
return c;
|
||||
|
||||
@@ -33,6 +33,11 @@ class UITask : public AbstractUITask {
|
||||
#endif
|
||||
unsigned long _next_refresh, _auto_off;
|
||||
NodePrefs* _node_prefs;
|
||||
bool _locked;
|
||||
unsigned long _lock_wake_until; // when to blank screen again after locked wake (5s)
|
||||
int _lock_seq; // 0=idle, 1=active (long-Back pressed, awaiting Enters)
|
||||
int _lock_seq_count; // Enter presses so far in current sequence
|
||||
unsigned long _lock_seq_ms; // millis() when sequence started
|
||||
char _alert[80];
|
||||
char _notif_mel_buf[220]; // persistent RTTTL buffer for custom notification melodies
|
||||
unsigned long _alert_expiry;
|
||||
@@ -86,6 +91,9 @@ public:
|
||||
ui_started_at = 0;
|
||||
_batt_mv = 0;
|
||||
_msgcount = _room_unread = 0;
|
||||
_locked = false;
|
||||
_lock_wake_until = 0;
|
||||
_lock_seq = 0; _lock_seq_count = 0; _lock_seq_ms = 0;
|
||||
_last_notif_ch_idx = -1;
|
||||
_last_notif_dm_valid = false;
|
||||
memset(_last_notif_dm_prefix, 0, sizeof(_last_notif_dm_prefix));
|
||||
|
||||
Reference in New Issue
Block a user