feat(ui-lvgl): tap-to-wake switch, the side button no longer wakes

Settings > Display & power > WAKE > Tap to wake (NVS, on by default):
off, only the top WAKE button turns the screen on. The side USER button
doesn't wake the screen any more (a press in a pocket); held, it still
mutes / unmutes with the screen off. Roadmap: buttons corrected (WAKE is
the top one), USER to open the main menu page later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-26 23:06:46 +02:00
co-authored by Claude Opus 5.5
parent 44fea2fa6a
commit fe92ac1153
4 changed files with 45 additions and 7 deletions
+6 -3
View File
@@ -5,8 +5,10 @@ for what comes next, in order. Tick items off as they land.
Decisions already made: Decisions already made:
- **Buttons:** WAKE (side, expander P00) turns the screen off / on. USER/BOOT - **Buttons:** WAKE (top, expander P00) turns the screen off / on. USER/BOOT
goes back; holding it mutes / unmutes the device. (side) goes back; holding it mutes / unmutes the device, also with the
screen off; it doesn't wake the screen (a press in a pocket). Later: a
click takes you to the main menu page, once the menu is rebuilt.
- **Placeholders:** the "+" popup next to a text field is the placeholder UI; - **Placeholders:** the "+" popup next to a text field is the placeholder UI;
it must offer every placeholder and appear at every field that sends text. it must offer every placeholder and appear at every field that sends text.
- **Message history:** kept on the SD card, about 100 per conversation to - **Message history:** kept on the SD card, about 100 per conversation to
@@ -251,7 +253,8 @@ User list, second batch (2026-09-26):
the MeshCore title font (the wordmark's letters plus l, v, d, digits, the MeshCore title font (the wordmark's letters plus l, v, d, digits,
'.', '-' drawn to match; Noto for a line with anything else). '.', '-' drawn to match; Noto for a line with anything else).
- [ ] Emoji, ideally the full range (glyphs could live on the SD card). - [ ] Emoji, ideally the full range (glyphs could live on the SD card).
- [ ] Tap-to-wake toggle. - [x] Tap-to-wake toggle (Settings > Display & power > WAKE, NVS, on by
default; off: only the top button wakes it).
- [ ] Favourites as their own screen in the menu rather than a tile. - [ ] Favourites as their own screen in the menu rather than a tile.
- [ ] WiFi indicator icon in the status bar. - [ ] WiFi indicator icon in the status bar.
@@ -226,6 +226,21 @@ static void saveLiveCap(int idx) {
p.putChar("ltcap", (int8_t)idx); p.putChar("ltcap", (int8_t)idx);
p.end(); p.end();
} }
// Settings > Display & power > Tap to wake: a touch turns the dark screen on
// (off: only the side button does).
static bool loadTapWake() {
Preferences p;
if (!p.begin("mc_ui", true)) return true;
bool on = p.getBool("tapwake", true);
p.end();
return on;
}
static void saveTapWake(bool on) {
Preferences p;
if (!p.begin("mc_ui", false)) return;
p.putBool("tapwake", on);
p.end();
}
// Filesystem size and space in use (Settings > Storage), plus the card's own // Filesystem size and space in use (Settings > Storage), plus the card's own
// size -- a card whose FAT partition is small (e.g. written by a Raspberry Pi // size -- a card whose FAT partition is small (e.g. written by a Raspberry Pi
// imager) shows both. The first free-space count on a big card takes a moment. // imager) shows both. The first free-space count on a big card takes a moment.
@@ -514,6 +529,9 @@ static void saveHistKeep(int idx) { s_hist_keep = idx; }
static int s_live_cap = -1; static int s_live_cap = -1;
static int loadLiveCap() { return s_live_cap; } static int loadLiveCap() { return s_live_cap; }
static void saveLiveCap(int idx) { s_live_cap = idx; } static void saveLiveCap(int idx) { s_live_cap = idx; }
static bool s_tap_wake = true;
static bool loadTapWake() { return s_tap_wake; }
static void saveTapWake(bool on) { s_tap_wake = on; }
// The browser has no card: a nominal 32 GB, used = what the files add up to // The browser has no card: a nominal 32 GB, used = what the files add up to
// (the storage screen counts them anyway; it passes that in). // (the storage screen counts them anyway; it passes that in).
static bool sdInfo(uint64_t& total, uint64_t& used, uint64_t& card) { total = card = 32ULL << 30; used = 0; return true; } static bool sdInfo(uint64_t& total, uint64_t& used, uint64_t& card) { total = card = 32ULL << 30; used = 0; return true; }
+19 -4
View File
@@ -222,6 +222,7 @@ void UITask::begin(DisplayDriver* display_drv, SensorManager* sensors, NodePrefs
s_archive.restore(_core->history); s_archive.restore(_core->history);
_core->history.setArchive(&s_archive); _core->history.setArchive(&s_archive);
} }
_tap_wake = lvport::loadTapWake();
msgtext::seedQuick(node_prefs); // "OK" in quick message 1 on first boot msgtext::seedQuick(node_prefs); // "OK" in quick message 1 on first boot
_nearby = new NearbyModel(); _nearby = new NearbyModel();
_nearby->bindModel(_core, node_prefs); _nearby->bindModel(_core, node_prefs);
@@ -274,8 +275,9 @@ void UITask::loop() {
pollConnection(); pollConnection();
drainCoreEvents(); drainCoreEvents();
// USER (BOOT) button: back; held, mutes / unmutes. WAKE button: screen off / // USER (BOOT, side) button: back; held, mutes / unmutes, also with the
// on. Either one silences a ringing alarm first. // screen off, which it doesn't wake. WAKE (top) button: screen off / on.
// Either one silences a ringing alarm first.
bool btn_click = false, btn_hold = false, wake_press = false; bool btn_click = false, btn_hold = false, wake_press = false;
#ifdef PIN_USER_BTN #ifdef PIN_USER_BTN
int ev = user_btn.check(); int ev = user_btn.check();
@@ -298,10 +300,10 @@ void UITask::loop() {
if ((btn_click || btn_hold || wake_press) && _core->clock.isRinging()) dismissRing(); if ((btn_click || btn_hold || wake_press) && _core->clock.isRinging()) dismissRing();
else if (wake_press) { if (_asleep) wake(); else sleep(); } else if (wake_press) { if (_asleep) wake(); else sleep(); }
else if (btn_hold) toggleMute(); else if (btn_hold) toggleMute();
else if (btn_click) { if (_asleep) wake(); else if (!locked()) back(); } else if (btn_click) { if (!_asleep && !locked()) back(); } // the side button doesn't wake: pockets
if (_asleep) { if (_asleep) {
if (lvport::touched()) { lvport::swallowTouch(); wake(); } if (lvport::touched()) { lvport::swallowTouch(); if (_tap_wake) wake(); } // off: the top (WAKE) button only
} else { } else {
uint32_t aoff = autoOffMillis(); uint32_t aoff = autoOffMillis();
if (aoff > 0 && lv_display_get_inactive_time(NULL) > aoff && !_core->clock.isRinging() && !otaBusy()) sleep(); if (aoff > 0 && lv_display_get_inactive_time(NULL) > aoff && !_core->clock.isRinging() && !otaBusy()) sleep();
@@ -2374,6 +2376,15 @@ void UITask::setBrightnessPct(uint8_t pct, bool save) {
if (save) the_mesh.savePrefs(); if (save) the_mesh.savePrefs();
} }
static void onTapWake(lv_event_t* e) {
s_ui->setTapWake(lv_obj_has_state((lv_obj_t*)lv_event_get_target(e), LV_STATE_CHECKED));
}
void UITask::setTapWake(bool on) {
_tap_wake = on;
lvport::saveTapWake(on);
}
void UITask::buildSchemaSettings() { void UITask::buildSchemaSettings() {
lv_obj_t* body = newScreen(settings::pageTitle(_settings_page), true); lv_obj_t* body = newScreen(settings::pageTitle(_settings_page), true);
_prune_lbl = nullptr; _prune_lbl = nullptr;
@@ -2390,6 +2401,10 @@ void UITask::buildSchemaSettings() {
lv_obj_center(_prune_lbl); lv_obj_center(_prune_lbl);
} }
if (_settings_page == settings::PG_DEVICE) { // after Lock screen's section, in NVS not the schema if (_settings_page == settings::PG_DEVICE) { // after Lock screen's section, in NVS not the schema
sectionTitle(body, "WAKE");
lv_obj_t* tw = switchRow(body, "Tap to wake", "Off: only the top button turns it on", nullptr);
if (_tap_wake) lv_obj_add_state(tw, LV_STATE_CHECKED);
lv_obj_add_event_cb(tw, onTapWake, LV_EVENT_VALUE_CHANGED, NULL);
sectionTitle(body, "SECURITY"); sectionTitle(body, "SECURITY");
listRow(body, LV_SYMBOL_EYE_CLOSE " Screen PIN", _pin[0] ? "On - asked when the screen wakes" : "Off", listRow(body, LV_SYMBOL_EYE_CLOSE " Screen PIN", _pin[0] ? "On - asked when the screen wakes" : "Off",
onPinSetup, NULL); onPinSetup, NULL);
@@ -64,6 +64,7 @@ public:
void mapPan(int dx, int dy); void mapPan(int dx, int dy);
void mapOpenMarker(int idx); void mapOpenMarker(int idx);
void mapDownloadPopup(); void mapDownloadPopup();
void setTapWake(bool on);
void mapDownloadClose(); void mapDownloadClose();
void mapDownloadStart(); void mapDownloadStart();
void mapDownloadStop(); void mapDownloadStop();
@@ -404,6 +405,7 @@ private:
lv_obj_t* _prune_lbl = nullptr; lv_obj_t* _prune_lbl = nullptr;
// Screen PIN (DeviceScreen.h); stored in NVS (lvport::loadPin) // Screen PIN (DeviceScreen.h); stored in NVS (lvport::loadPin)
char _pin[9] = ""; // "" = no PIN char _pin[9] = ""; // "" = no PIN
bool _tap_wake = true; // a touch wakes the dark screen (NVS)
char _pin_entry[9] = ""; // digits typed so far char _pin_entry[9] = ""; // digits typed so far
char _pin_new[9] = ""; // setup: the first entry, waiting for its confirmation char _pin_new[9] = ""; // setup: the first entry, waiting for its confirmation
uint8_t _pin_fails = 0; uint8_t _pin_fails = 0;