From fe92ac1153140d258ab9c7d267c3a19c7efd9f4d Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:06:46 +0200 Subject: [PATCH] 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 --- docs/development/l2-roadmap.md | 9 +++++--- examples/companion_radio/ui-lvgl/LvglPort.h | 18 ++++++++++++++++ examples/companion_radio/ui-lvgl/UITask.cpp | 23 +++++++++++++++++---- examples/companion_radio/ui-lvgl/UITask.h | 2 ++ 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/docs/development/l2-roadmap.md b/docs/development/l2-roadmap.md index 055896d1..bad2e352 100644 --- a/docs/development/l2-roadmap.md +++ b/docs/development/l2-roadmap.md @@ -5,8 +5,10 @@ for what comes next, in order. Tick items off as they land. Decisions already made: -- **Buttons:** WAKE (side, expander P00) turns the screen off / on. USER/BOOT - goes back; holding it mutes / unmutes the device. +- **Buttons:** WAKE (top, expander P00) turns the screen off / on. USER/BOOT + (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; 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 @@ -251,7 +253,8 @@ User list, second batch (2026-09-26): the MeshCore title font (the wordmark's letters plus l, v, d, digits, '.', '-' drawn to match; Noto for a line with anything else). - [ ] 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. - [ ] WiFi indicator icon in the status bar. diff --git a/examples/companion_radio/ui-lvgl/LvglPort.h b/examples/companion_radio/ui-lvgl/LvglPort.h index eead73c9..45991762 100644 --- a/examples/companion_radio/ui-lvgl/LvglPort.h +++ b/examples/companion_radio/ui-lvgl/LvglPort.h @@ -226,6 +226,21 @@ static void saveLiveCap(int idx) { p.putChar("ltcap", (int8_t)idx); 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 // 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. @@ -514,6 +529,9 @@ static void saveHistKeep(int idx) { s_hist_keep = idx; } static int s_live_cap = -1; static int loadLiveCap() { return s_live_cap; } 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 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; } diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 36cb6533..3647494c 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -222,6 +222,7 @@ void UITask::begin(DisplayDriver* display_drv, SensorManager* sensors, NodePrefs s_archive.restore(_core->history); _core->history.setArchive(&s_archive); } + _tap_wake = lvport::loadTapWake(); msgtext::seedQuick(node_prefs); // "OK" in quick message 1 on first boot _nearby = new NearbyModel(); _nearby->bindModel(_core, node_prefs); @@ -274,8 +275,9 @@ void UITask::loop() { pollConnection(); drainCoreEvents(); - // USER (BOOT) button: back; held, mutes / unmutes. WAKE button: screen off / - // on. Either one silences a ringing alarm first. + // USER (BOOT, side) button: back; held, mutes / unmutes, also with the + // 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; #ifdef PIN_USER_BTN int ev = user_btn.check(); @@ -298,10 +300,10 @@ void UITask::loop() { if ((btn_click || btn_hold || wake_press) && _core->clock.isRinging()) dismissRing(); else if (wake_press) { if (_asleep) wake(); else sleep(); } 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 (lvport::touched()) { lvport::swallowTouch(); wake(); } + if (lvport::touched()) { lvport::swallowTouch(); if (_tap_wake) wake(); } // off: the top (WAKE) button only } else { uint32_t aoff = autoOffMillis(); 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(); } +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() { lv_obj_t* body = newScreen(settings::pageTitle(_settings_page), true); _prune_lbl = nullptr; @@ -2390,6 +2401,10 @@ void UITask::buildSchemaSettings() { lv_obj_center(_prune_lbl); } 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"); listRow(body, LV_SYMBOL_EYE_CLOSE " Screen PIN", _pin[0] ? "On - asked when the screen wakes" : "Off", onPinSetup, NULL); diff --git a/examples/companion_radio/ui-lvgl/UITask.h b/examples/companion_radio/ui-lvgl/UITask.h index e6ebb996..88f799d4 100644 --- a/examples/companion_radio/ui-lvgl/UITask.h +++ b/examples/companion_radio/ui-lvgl/UITask.h @@ -64,6 +64,7 @@ public: void mapPan(int dx, int dy); void mapOpenMarker(int idx); void mapDownloadPopup(); + void setTapWake(bool on); void mapDownloadClose(); void mapDownloadStart(); void mapDownloadStop(); @@ -404,6 +405,7 @@ private: lv_obj_t* _prune_lbl = nullptr; // Screen PIN (DeviceScreen.h); stored in NVS (lvport::loadPin) 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_new[9] = ""; // setup: the first entry, waiting for its confirmation uint8_t _pin_fails = 0;