From a7aa4c55d89a616bbc81d9542f45ff24bfacfef5 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Wed, 20 May 2026 18:46:11 +0200 Subject: [PATCH 01/14] fix: Prefer epub format over derived formats when downloading from opds server (#1480) ## Summary * **What is the goal of this PR?** Prefer epub format over kepub or other formats offered from an OPDS server * **What changes are included?** ## Additional Context Should address #1419 --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< NO >**_ --------- Co-authored-by: Arthur Tazhitdinov --- lib/OpdsParser/OpdsParser.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/OpdsParser/OpdsParser.cpp b/lib/OpdsParser/OpdsParser.cpp index ef769f11..af619d1c 100644 --- a/lib/OpdsParser/OpdsParser.cpp +++ b/lib/OpdsParser/OpdsParser.cpp @@ -110,8 +110,16 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons if (self->inEntry) { if (rel && type && strstr(rel, "opds-spec.org/acquisition") != nullptr && strcmp(type, "application/epub+zip") == 0) { - self->currentEntry.type = OpdsEntryType::BOOK; - self->currentEntry.href = href; + // Prefer plain EPUB links over derived formats when multiple + // acquisition links are present for one entry. + const bool isPlainEpub = strstr(href, ".epub") != nullptr || strstr(href, "/epub/") != nullptr; + const bool alreadyHasPlainEpub = self->currentEntry.type == OpdsEntryType::BOOK && + (self->currentEntry.href.find(".epub") != std::string::npos || + self->currentEntry.href.find("/epub/") != std::string::npos); + if (self->currentEntry.type != OpdsEntryType::BOOK || (isPlainEpub && !alreadyHasPlainEpub)) { + self->currentEntry.type = OpdsEntryType::BOOK; + self->currentEntry.href = href; + } } else if (type && strstr(type, "application/atom+xml") != nullptr) { if (self->currentEntry.type != OpdsEntryType::BOOK) { self->currentEntry.type = OpdsEntryType::NAVIGATION; From d9aa5b4de1d3e2858110192bcbc031493f009622 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 20 May 2026 21:48:06 +0200 Subject: [PATCH 02/14] fix: take orientation into account for border generation in ScreenshotUtil (#1977) ## Summary Previously `ScreenshotUtil` used physical display size to draw a border around the screen contents. Because of this, in landscape orientation the border was shown as a broken square. This PR changes border drawing to use logical screen size instead of a physical display size to take orientation into account. ## Additional Context * Tested on X4 in all 4 reading orientations. Behavior is now correct, however it doesn't look perfect on my X4: the border is much closer to the physical top side of the display than to the other sides. This might be related to assembly variation during manufacturing, but it might as well be related to the way a eink controller is connected to the display (controller supports bigger display sizes, so an offset may be present). --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- src/util/ScreenshotUtil.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/util/ScreenshotUtil.cpp b/src/util/ScreenshotUtil.cpp index df8704f7..e478c3b7 100644 --- a/src/util/ScreenshotUtil.cpp +++ b/src/util/ScreenshotUtil.cpp @@ -88,7 +88,12 @@ void ScreenshotUtil::takeScreenshot(GfxRenderer& renderer) { // Display a border around the screen to indicate a screenshot was taken if (renderer.storeBwBuffer()) { - renderer.drawRect(6, 6, renderer.getDisplayHeight() - 12, renderer.getDisplayWidth() - 12, 2, true); + int marginTop, marginRight, marginBottom, marginLeft; + renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft); + int width = renderer.getScreenWidth() - marginLeft - marginRight - 1; + int height = renderer.getScreenHeight() - marginTop - marginBottom - 1; + // Add extra margin to the border to make it more visible + renderer.drawRect(marginLeft + 1, marginTop + 1, width - 2, height - 2, 2, true); renderer.displayBuffer(); delay(1000); renderer.restoreBwBuffer(); From c44555007b096180cb6420cdec0b1f848fdd4d88 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 20 May 2026 22:43:48 -0400 Subject: [PATCH 03/14] feat(settings): modify "Page as Sleep Screen" to "Quick Resume" options (#2089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary **What is the goal of this PR?** Adds a clearer Quick Resume sleep-screen flow. The previous “Page as Sleep Screen” behavior is now exposed as a dedicated `Sleep Screen > Quick Resume `option, with the timeout-only behavior controlled by a renamed `Quick Resume on Timeout `setting. **What changes are included?** - Adds `Quick Resume` as a new `Sleep Screen` option. - Renames the old `Page as Sleep Screen` setting to `Quick Resume on Timeout`. - Changes that setting’s choices from `Never / After Timeout / Always` to `OFF / ON`. - Makes `Quick Resume on Timeout = ON` equivalent to the old `After Timeout` behavior. - Makes `Sleep Screen > Quick Resume` equivalent to the old `Always` behavior. - Automatically forces `Quick Resume on Timeout` to `ON` when `Sleep Screen` is set to `Quick Resume`. - Renames internal setting references from `seamlessSleepScreen` to `quickResumeSleepScreen`. - Updates translations for the renamed setting label. **Additional Context** - This is mostly a settings/labeling restructure around existing behavior, not a new rendering path. - The runtime quick-resume behavior still uses the existing saved framebuffer / last-screen sleep flow. - Review focus areas: - Sleep entry behavior from manual sleep vs timeout sleep. - The automatic dependency where selecting `Sleep Screen > Quick Resume` sets `Quick Resume on Timeout` to `ON`. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES >**_ --- **New `Quick Resume` option for `Sleep Screen` will automatically set `Quick Resume on Timeout` to `ON`**: quick resume **Example where a different sleep screen setting like `Cover` can be used in combination with the `Quick Resume on Timeout` setting**: cover + quick resume --- lib/I18n/translations/belarusian.yaml | 3 ++- lib/I18n/translations/catalan.yaml | 3 ++- lib/I18n/translations/czech.yaml | 3 ++- lib/I18n/translations/danish.yaml | 3 ++- lib/I18n/translations/dutch.yaml | 5 +++-- lib/I18n/translations/english.yaml | 3 ++- lib/I18n/translations/finnish.yaml | 3 ++- lib/I18n/translations/french.yaml | 3 ++- lib/I18n/translations/german.yaml | 3 ++- lib/I18n/translations/hungarian.yaml | 3 ++- lib/I18n/translations/italian.yaml | 3 ++- lib/I18n/translations/kazakh.yaml | 3 ++- lib/I18n/translations/lithuanian.yaml | 3 ++- lib/I18n/translations/polish.yaml | 3 ++- lib/I18n/translations/portuguese.yaml | 3 ++- lib/I18n/translations/romanian.yaml | 3 ++- lib/I18n/translations/russian.yaml | 3 ++- lib/I18n/translations/slovenian.yaml | 3 ++- lib/I18n/translations/spanish.yaml | 3 ++- lib/I18n/translations/swedish.yaml | 3 ++- lib/I18n/translations/turkish.yaml | 3 ++- lib/I18n/translations/ukrainian.yaml | 3 ++- platformio.ini | 2 +- src/CrossPointSettings.cpp | 6 ++++++ src/CrossPointSettings.h | 15 ++++++++------- src/JsonSettingsIO.cpp | 4 ++++ src/SettingsList.h | 6 +++--- src/activities/boot_sleep/SleepActivity.cpp | 8 ++++---- src/activities/settings/SettingsActivity.cpp | 1 + src/main.cpp | 11 ++++++----- src/network/CrossPointWebServer.cpp | 1 + 31 files changed, 79 insertions(+), 43 deletions(-) diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index cecb40de..3a10e1e3 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -206,11 +206,12 @@ STR_THEME_CLASSIC: "Класічная" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Кампенсацыя выцвітання" -STR_SEAMLESS_SLEEP: "Старонка як экран сну" +STR_QUICK_RESUME_TIMEOUT: "Хуткае узнаўленне пасля таймаўту" STR_AFTER_TIMEOUT: "Пасля таймаўту" STR_REMAP_FRONT_BUTTONS: "Пераназначыць пярэднія кнопкі" STR_OPDS_BROWSER: "OPDS браўзер" STR_COVER_CUSTOM: "Вокладка + Свой" +STR_QUICK_RESUME: "Хуткае узнаўленне" STR_MENU_RECENT_BOOKS: "Нядаўнія кнігі" STR_REMOVE_FROM_RECENTS: "Выдаліць з нядаўніх кніг?" STR_NO_RECENT_BOOKS: "Няма нядаўніх кніг" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 08b8f148..feb270ba 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -234,11 +234,12 @@ STR_THEME_CLASSIC: "Clàssic" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Ampliat" STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol" -STR_SEAMLESS_SLEEP: "Pàgina com a repòs" +STR_QUICK_RESUME_TIMEOUT: "Represa ràpida després del temps" STR_AFTER_TIMEOUT: "Després del temps" STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals" STR_OPDS_BROWSER: "Navegador OPDS" STR_COVER_CUSTOM: "Portada + Personalitzat" +STR_QUICK_RESUME: "Represa ràpida" STR_MENU_RECENT_BOOKS: "Llibres recents" STR_REMOVE_FROM_RECENTS: "Voleu suprimir-lo de Llibres recents?" STR_NO_RECENT_BOOKS: "No hi ha llibres recents" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index 7363a98c..1cd2a584 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -212,11 +212,12 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Oprava blednutí na slunci" -STR_SEAMLESS_SLEEP: "Stránka jako spánek" +STR_QUICK_RESUME_TIMEOUT: "Rychlé navázání po vypršení" STR_AFTER_TIMEOUT: "Po vypršení" STR_REMAP_FRONT_BUTTONS: "Přemapovat přední tlačítka" STR_OPDS_BROWSER: "Prohlížeč OPDS" STR_COVER_CUSTOM: "Obálka + Vlastní" +STR_QUICK_RESUME: "Rychlé navázání" STR_MENU_RECENT_BOOKS: "Nedávné knihy" STR_REMOVE_FROM_RECENTS: "Odebrat z nedávných knih?" STR_NO_RECENT_BOOKS: "Žádné nedávné knihy" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index 0126aec8..c3d27d64 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -234,11 +234,12 @@ STR_THEME_CLASSIC: "Klassisk" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Sollysfading-rettelse" -STR_SEAMLESS_SLEEP: "Side som dvaleskærm" +STR_QUICK_RESUME_TIMEOUT: "Hurtig genoptagelse ved timeout" STR_AFTER_TIMEOUT: "Efter timeout" STR_REMAP_FRONT_BUTTONS: "Omtildel frontknapper" STR_OPDS_BROWSER: "OPDS Browser" STR_COVER_CUSTOM: "Omslag + Brugerdefineret" +STR_QUICK_RESUME: "Hurtig genoptagelse" STR_MENU_RECENT_BOOKS: "Seneste bøger" STR_REMOVE_FROM_RECENTS: "Fjern fra Seneste bøger?" STR_NO_RECENT_BOOKS: "Ingen seneste bøger" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index 917ac475..a9542def 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -234,11 +234,12 @@ STR_THEME_CLASSIC: "Klassiek" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Uitgebreid" STR_SUNLIGHT_FADING_FIX: "Zonlicht vervaging fix" -STR_SEAMLESS_SLEEP: "Pagina als slaapscherm" +STR_QUICK_RESUME_TIMEOUT: "Snel hervatten bij timeout" STR_AFTER_TIMEOUT: "Na timeout" STR_REMAP_FRONT_BUTTONS: "Knoppen voorzijde wijzigen" STR_OPDS_BROWSER: "OPDS-browser" STR_COVER_CUSTOM: "Omslag + Aangepast" +STR_QUICK_RESUME: "Snel hervatten" STR_MENU_RECENT_BOOKS: "Recente boeken" STR_REMOVE_FROM_RECENTS: "Verwijderen uit Recente boeken?" STR_NO_RECENT_BOOKS: "Geen recente boeken" @@ -302,4 +303,4 @@ STR_LINK: "[link]" STR_SCREENSHOT_BUTTON: "Screenshot maken" STR_AUTO_TURN_ENABLED: "Automatisch omslaan ingeschakeld: " STR_AUTO_TURN_PAGES_PER_MIN: "Autom. omslaan (pagina's per minuut)" -STR_TILT_PAGE_TURN: "Kantel om te bladeren" \ No newline at end of file +STR_TILT_PAGE_TURN: "Kantel om te bladeren" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index c10420fc..d18c7390 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -62,7 +62,7 @@ STR_CAT_READER: "Reader" STR_CAT_CONTROLS: "Controls" STR_CAT_SYSTEM: "System" STR_SLEEP_SCREEN: "Sleep Screen" -STR_SEAMLESS_SLEEP: "Page as Sleep Screen" +STR_QUICK_RESUME_TIMEOUT: "Quick Resume on Timeout" STR_AFTER_TIMEOUT: "After Timeout" STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode" STR_HIDE_BATTERY: "Hide Battery %" @@ -263,6 +263,7 @@ STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons" STR_OPDS_BROWSER: "OPDS Browser" STR_SEARCH: "Search" STR_COVER_CUSTOM: "Cover + Custom" +STR_QUICK_RESUME: "Quick Resume" STR_MENU_RECENT_BOOKS: "Recent Books" STR_REMOVE_FROM_RECENTS: "Remove from Recent Books?" STR_NO_RECENT_BOOKS: "No recent books" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index cd7b3b6d..94e9a7ea 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -211,11 +211,12 @@ STR_THEME_CLASSIC: "Klassinen" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Auringonvalon haalistumiskorjaus" -STR_SEAMLESS_SLEEP: "Sivu lepotilassa" +STR_QUICK_RESUME_TIMEOUT: "Pikajatko aikakatkaisulla" STR_AFTER_TIMEOUT: "Aikakatkon jälkeen" STR_REMAP_FRONT_BUTTONS: "Uudelleenmääritä etupainikkeet" STR_OPDS_BROWSER: "OPDS-selain" STR_COVER_CUSTOM: "Kansi + mukautettu" +STR_QUICK_RESUME: "Pikajatko" STR_MENU_RECENT_BOOKS: "Viimeisimmät kirjat" STR_REMOVE_FROM_RECENTS: "Poista viimeisimmistä kirjoista?" STR_NO_RECENT_BOOKS: "Ei viimeisimpiä kirjoja" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 0cfc4b55..8cad934c 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -235,11 +235,12 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Correction lisibilité au soleil" -STR_SEAMLESS_SLEEP: "Page comme veille" +STR_QUICK_RESUME_TIMEOUT: "Reprise rapide après délai" STR_AFTER_TIMEOUT: "Après délai" STR_REMAP_FRONT_BUTTONS: "Configurer boutons façade" STR_OPDS_BROWSER: "Navigateur OPDS" STR_COVER_CUSTOM: "Couverture + Perso" +STR_QUICK_RESUME: "Reprise rapide" STR_MENU_RECENT_BOOKS: "Livres récents" STR_REMOVE_FROM_RECENTS: "Retirer des Livres récents ?" STR_NO_RECENT_BOOKS: "Aucun livre récent" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 9215635e..d0e8ee23 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -233,11 +233,12 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Anti-Verblassen" -STR_SEAMLESS_SLEEP: "Seite als Ruhebild" +STR_QUICK_RESUME_TIMEOUT: "Schnelles Fortsetzen nach Timeout" STR_AFTER_TIMEOUT: "Nach Timeout" STR_REMAP_FRONT_BUTTONS: "Vordere Tasten belegen" STR_OPDS_BROWSER: "OPDS-Browser" STR_COVER_CUSTOM: "Umschlag + Eigenes" +STR_QUICK_RESUME: "Schnelles Fortsetzen" STR_MENU_RECENT_BOOKS: "Zuletzt gelesen" STR_REMOVE_FROM_RECENTS: "Aus Zuletzt gelesen entfernen?" STR_NO_RECENT_BOOKS: "Keine Bücher" diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 166e4b00..583c1734 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -231,11 +231,12 @@ STR_THEME_CLASSIC: "Klasszikus" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Napfény halványulás javítás" -STR_SEAMLESS_SLEEP: "Oldal alvóképernyő" +STR_QUICK_RESUME_TIMEOUT: "Gyors folytatás időtúllépéskor" STR_AFTER_TIMEOUT: "Időtúllépés után" STR_REMAP_FRONT_BUTTONS: "Elülső gombok átállítása" STR_OPDS_BROWSER: "OPDS böngésző" STR_COVER_CUSTOM: "Borító + Egyéni" +STR_QUICK_RESUME: "Gyors folytatás" STR_MENU_RECENT_BOOKS: "Legutóbbi könyvek" STR_REMOVE_FROM_RECENTS: "Eltávolítás a legutóbbi könyvek közül?" STR_NO_RECENT_BOOKS: "Nincsenek legutóbbi könyvek" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 4ccdacc7..7bf3ddd8 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -242,12 +242,13 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra esteso" STR_SUNLIGHT_FADING_FIX: "Correzione luce solare" -STR_SEAMLESS_SLEEP: "Pagina come standby" +STR_QUICK_RESUME_TIMEOUT: "Ripresa rapida dopo timeout" STR_AFTER_TIMEOUT: "Dopo timeout" STR_REMAP_FRONT_BUTTONS: "Rimappa pulsanti frontali" STR_OPDS_BROWSER: "Browser OPDS" STR_SEARCH: "Cerca" STR_COVER_CUSTOM: "Copertina + Wallpaper" +STR_QUICK_RESUME: "Ripresa rapida" STR_MENU_RECENT_BOOKS: "Libri recenti" STR_REMOVE_FROM_RECENTS: "Rimuovere da Libri recenti?" STR_NO_RECENT_BOOKS: "Nessun libro recente" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index 9c7417c2..0c28011b 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -207,11 +207,12 @@ STR_THEME_CLASSIC: "Классикалық" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra кеңейтілген" STR_SUNLIGHT_FADING_FIX: "Күн сәулесінен солу түзету" -STR_SEAMLESS_SLEEP: "Бет – ұйқы экраны" +STR_QUICK_RESUME_TIMEOUT: "Таймауттан кейін жылдам жалғастыру" STR_AFTER_TIMEOUT: "Таймауттан кейін" STR_REMAP_FRONT_BUTTONS: "Алдыңғы түймелерді қайта баптау" STR_OPDS_BROWSER: "OPDS шолғышы" STR_COVER_CUSTOM: "Мұқаба + Өзгертілген" +STR_QUICK_RESUME: "Жылдам жалғастыру" STR_MENU_RECENT_BOOKS: "Жуырда оқылған кітаптар" STR_REMOVE_FROM_RECENTS: "Жуырда оқылған кітаптардан жою?" STR_NO_RECENT_BOOKS: "Жуырда оқылған кітаптар жоқ" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index 154669e0..79a98d32 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -231,11 +231,12 @@ STR_THEME_CLASSIC: "Klasikinė" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Ext." STR_SUNLIGHT_FADING_FIX: "Blyškumo pataisa" -STR_SEAMLESS_SLEEP: "Puslapis miego ekrane" +STR_QUICK_RESUME_TIMEOUT: "Greitas tęsimas po skirtojo laiko" STR_AFTER_TIMEOUT: "Po skirtojo laiko" STR_REMAP_FRONT_BUTTONS: "Keisti mygtukus" STR_OPDS_BROWSER: "OPDS naršyklė" STR_COVER_CUSTOM: "Viršelis + Kita" +STR_QUICK_RESUME: "Greitas tęsimas" STR_MENU_RECENT_BOOKS: "Paskutinės" STR_REMOVE_FROM_RECENTS: "Pašalinti iš paskutinių?" STR_NO_RECENT_BOOKS: "Paskutinių nėra" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 3b85b70e..0b74011e 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -242,12 +242,13 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Przeciwdziałanie blaknięciu od słońca" -STR_SEAMLESS_SLEEP: "Strona jako ekran snu" +STR_QUICK_RESUME_TIMEOUT: "Szybkie wznawianie po czasie" STR_AFTER_TIMEOUT: "Po upływie czasu" STR_REMAP_FRONT_BUTTONS: "Skonfiguruj przednie przyciski" STR_OPDS_BROWSER: "OPDS Browser" STR_SEARCH: "Szukaj" STR_COVER_CUSTOM: "Okładka + Własne" +STR_QUICK_RESUME: "Szybkie wznawianie" STR_MENU_RECENT_BOOKS: "Ostatnio czytane" STR_REMOVE_FROM_RECENTS: "Usunąć z ostatnio czytanych?" STR_NO_RECENT_BOOKS: "Brak ostatnio czytanych" diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml index 1d13d16d..e36683b0 100644 --- a/lib/I18n/translations/portuguese.yaml +++ b/lib/I18n/translations/portuguese.yaml @@ -212,11 +212,12 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Ajuste desbotamento ao sol" -STR_SEAMLESS_SLEEP: "Página como repouso" +STR_QUICK_RESUME_TIMEOUT: "Retomada rápida após tempo limite" STR_AFTER_TIMEOUT: "Após tempo limite" STR_REMAP_FRONT_BUTTONS: "Remapear botões frontais" STR_OPDS_BROWSER: "Navegador OPDS" STR_COVER_CUSTOM: "Capa + personalizado" +STR_QUICK_RESUME: "Retomada rápida" STR_MENU_RECENT_BOOKS: "Livros recentes" STR_REMOVE_FROM_RECENTS: "Remover dos Livros recentes?" STR_NO_RECENT_BOOKS: "Sem livros recentes" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index e8ffe123..07fb3ac1 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -234,11 +234,12 @@ STR_THEME_CLASSIC: "Clasic" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Corecţie estompare lumină" -STR_SEAMLESS_SLEEP: "Pagină ca repaus" +STR_QUICK_RESUME_TIMEOUT: "Reluare rapidă la timeout" STR_AFTER_TIMEOUT: "După timeout" STR_REMAP_FRONT_BUTTONS: "Remapare butoane frontale" STR_OPDS_BROWSER: "Browser OPDS" STR_COVER_CUSTOM: "Copertă + Personalizat" +STR_QUICK_RESUME: "Reluare rapidă" STR_MENU_RECENT_BOOKS: "Cărţi recente" STR_REMOVE_FROM_RECENTS: "Eliminați din Cărţi recente?" STR_NO_RECENT_BOOKS: "Nicio carte recentă" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index b74d8fa2..e95c62c6 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -242,12 +242,13 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Компенсация выцветания" -STR_SEAMLESS_SLEEP: "Страница как экран сна" +STR_QUICK_RESUME_TIMEOUT: "Быстрое возобновление по таймауту" STR_AFTER_TIMEOUT: "По таймауту" STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки" STR_OPDS_BROWSER: "OPDS браузер" STR_SEARCH: "Поиск" STR_COVER_CUSTOM: "Обложка + Свой" +STR_QUICK_RESUME: "Быстрое возобновление" STR_MENU_RECENT_BOOKS: "Недавние книги" STR_REMOVE_FROM_RECENTS: "Удалить из недавних книг?" STR_NO_RECENT_BOOKS: "Нет недавних книг" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index eddfd5c9..1fae8e4b 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -231,11 +231,12 @@ STR_THEME_CLASSIC: "Klasična" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra razširjena" STR_SUNLIGHT_FADING_FIX: "Popravek bledenja na soncu" -STR_SEAMLESS_SLEEP: "Stran kot zaslon sna" +STR_QUICK_RESUME_TIMEOUT: "Hitro nadaljevanje po izteku" STR_AFTER_TIMEOUT: "Po izteku" STR_REMAP_FRONT_BUTTONS: "Prenastavi sprednje gumbe" STR_OPDS_BROWSER: "OPDS brskalnik" STR_COVER_CUSTOM: "Naslovnica + po meri" +STR_QUICK_RESUME: "Hitro nadaljevanje" STR_MENU_RECENT_BOOKS: "Zadnje knjige" STR_REMOVE_FROM_RECENTS: "Odstrani iz zadnjih knjig?" STR_NO_RECENT_BOOKS: "Ni zadnjih knjig" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 65990dc2..99fe33b0 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -242,12 +242,13 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extendido" STR_SUNLIGHT_FADING_FIX: "Corrección de desvanecimiento" -STR_SEAMLESS_SLEEP: "Página como reposo" +STR_QUICK_RESUME_TIMEOUT: "Reanudación rápida tras tiempo" STR_AFTER_TIMEOUT: "Tras tiempo" STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales" STR_OPDS_BROWSER: "Navegador OPDS" STR_SEARCH: "Buscar" STR_COVER_CUSTOM: "Portada + Pers." +STR_QUICK_RESUME: "Reanudación rápida" STR_MENU_RECENT_BOOKS: "Libros recientes" STR_REMOVE_FROM_RECENTS: "¿Eliminar de Libros recientes?" STR_NO_RECENT_BOOKS: "No hay libros recientes" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index 1c311be5..103dcda7 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -242,12 +242,13 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra utökad" STR_SUNLIGHT_FADING_FIX: "Fix för solskensmattning" -STR_SEAMLESS_SLEEP: "Sida som viloskärm" +STR_QUICK_RESUME_TIMEOUT: "Snabb återupptagning efter timeout" STR_AFTER_TIMEOUT: "Efter timeout" STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar" STR_OPDS_BROWSER: "OPDS-webbläsare" STR_SEARCH: "Sök" STR_COVER_CUSTOM: "Omslag + Valfri" +STR_QUICK_RESUME: "Snabb återupptagning" STR_MENU_RECENT_BOOKS: "Senaste böckerna" STR_REMOVE_FROM_RECENTS: "Ta bort från Senaste böckerna?" STR_NO_RECENT_BOOKS: "Inga senaste böcker" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index 5fa44cc1..a3280f00 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -211,11 +211,12 @@ STR_THEME_CLASSIC: "Klasik" STR_THEME_LYRA: "Lyra" STR_THEME_LYRA_EXTENDED: "Lyra Genişletilmiş" STR_SUNLIGHT_FADING_FIX: "Güneş Işığı Solma Düzeltmesi" -STR_SEAMLESS_SLEEP: "Sayfa uyku ekranı" +STR_QUICK_RESUME_TIMEOUT: "Zaman aşımında Hızlı Devam" STR_AFTER_TIMEOUT: "Zaman aşımında" STR_REMAP_FRONT_BUTTONS: "Ön Tuşları Yeniden Ata" STR_OPDS_BROWSER: "OPDS Tarayıcı" STR_COVER_CUSTOM: "Kapak + Özel" +STR_QUICK_RESUME: "Hızlı Devam" STR_MENU_RECENT_BOOKS: "Son Kitaplar" STR_REMOVE_FROM_RECENTS: "Son Kitaplar listesinden kaldırılsın mı?" STR_NO_RECENT_BOOKS: "Son okunan kitap yok" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index a23d33f8..117d1a4c 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -242,12 +242,13 @@ STR_THEME_LYRA: "Lyra" STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Виправлення вицвітання на сонці" -STR_SEAMLESS_SLEEP: "Сторінка як екран сну" +STR_QUICK_RESUME_TIMEOUT: "Швидке продовження після таймауту" STR_AFTER_TIMEOUT: "Після таймауту" STR_REMAP_FRONT_BUTTONS: "Налаштувати передні кнопки" STR_OPDS_BROWSER: "Браузер OPDS" STR_SEARCH: "Пошук" STR_COVER_CUSTOM: "Обкл. + власне" +STR_QUICK_RESUME: "Швидке продовження" STR_MENU_RECENT_BOOKS: "Останні книги" STR_REMOVE_FROM_RECENTS: "Видалити з останніх книг?" STR_NO_RECENT_BOOKS: "Немає останніх книг" diff --git a/platformio.ini b/platformio.ini index 39a526d0..213b5f49 100644 --- a/platformio.ini +++ b/platformio.ini @@ -89,7 +89,7 @@ build_flags = ${base.build_flags} -DCROSSPOINT_VERSION=\"${crosspoint.version}-rc+${sysenv.CROSSPOINT_RC_HASH}\" -DENABLE_SERIAL_LOG - -DLOG_LEVEL=1 ; Set log level to info for release candidate builds + -DLOG_LEVEL=1 ; Set log level to info for release candidate builds [env:slim] extends = base diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 7dd9051d..abe879f2 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -79,6 +79,12 @@ void CrossPointSettings::validateFrontButtonMapping(CrossPointSettings& settings } } +void CrossPointSettings::normalizeDependentSettings(CrossPointSettings& settings) { + if (settings.sleepScreen == SLEEP_SCREEN_MODE::QUICK_RESUME) { + settings.quickResumeSleepScreen = QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT; + } +} + bool CrossPointSettings::saveToFile() const { Storage.mkdir("/.crosspoint"); return JsonSettingsIO::saveSettings(*this, SETTINGS_FILE_JSON); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 38d7c1c4..86b379d7 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -24,6 +24,7 @@ class CrossPointSettings { COVER = 3, BLANK = 4, COVER_CUSTOM = 5, + QUICK_RESUME = 6, SLEEP_SCREEN_MODE_COUNT }; enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT }; @@ -155,11 +156,10 @@ class CrossPointSettings { enum TILT_PAGE_TURN { TILT_OFF = 0, TILT_NORMAL = 1, TILT_NVERTED = 2, TILT_PAGE_TURN_COUNT }; - enum SEAMLESS_SLEEP_SCREEN { - SEAMLESS_NEVER = 0, - SEAMLESS_AFTER_TIMEOUT = 1, - SEAMLESS_ALWAYS = 2, - SEAMLESS_SLEEP_SCREEN_COUNT + enum QUICK_RESUME_SLEEP_SCREEN { + QUICK_RESUME_NEVER = 0, + QUICK_RESUME_AFTER_TIMEOUT = 1, + QUICK_RESUME_SLEEP_SCREEN_COUNT }; // Sleep screen settings @@ -249,8 +249,8 @@ class CrossPointSettings { uint8_t tiltPageTurn = TILT_OFF; // Language setting (Language enum index, default 0 = EN) uint8_t language = 0; - // Seamless sleep: keep current content visible with moon icon instead of showing sleep screen - uint8_t seamlessSleepScreen = SEAMLESS_NEVER; + // Quick Resume: keep current content visible with moon icon instead of showing a static sleep screen. + uint8_t quickResumeSleepScreen = QUICK_RESUME_NEVER; ~CrossPointSettings() = default; @@ -275,6 +275,7 @@ class CrossPointSettings { bool loadFromFile(); static void validateFrontButtonMapping(CrossPointSettings& settings); + static void normalizeDependentSettings(CrossPointSettings& settings); private: bool loadFromBinaryFile(); diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index 065c6e51..ebe52ff1 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -223,6 +223,10 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool* } } + const uint8_t quickResumeBeforeNormalize = s.quickResumeSleepScreen; + CrossPointSettings::normalizeDependentSettings(s); + if (s.quickResumeSleepScreen != quickResumeBeforeNormalize && needsResave) *needsResave = true; + // Front button remap — managed by RemapFrontButtons sub-activity, not in SettingsList. using S = CrossPointSettings; s.frontButtonBack = diff --git a/src/SettingsList.h b/src/SettingsList.h index 1f3e9f78..8ccb8614 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -106,15 +106,15 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist // --- Display --- SettingInfo::Enum(StrId::STR_SLEEP_SCREEN, &CrossPointSettings::sleepScreen, {StrId::STR_DARK, StrId::STR_LIGHT, StrId::STR_CUSTOM, StrId::STR_COVER, StrId::STR_NONE_OPT, - StrId::STR_COVER_CUSTOM}, + StrId::STR_COVER_CUSTOM, StrId::STR_QUICK_RESUME}, "sleepScreen", StrId::STR_CAT_DISPLAY), SettingInfo::Enum(StrId::STR_SLEEP_COVER_MODE, &CrossPointSettings::sleepScreenCoverMode, {StrId::STR_FIT, StrId::STR_CROP}, "sleepScreenCoverMode", StrId::STR_CAT_DISPLAY), SettingInfo::Enum(StrId::STR_SLEEP_COVER_FILTER, &CrossPointSettings::sleepScreenCoverFilter, {StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED}, "sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY), - SettingInfo::Enum(StrId::STR_SEAMLESS_SLEEP, &CrossPointSettings::seamlessSleepScreen, - {StrId::STR_NEVER, StrId::STR_AFTER_TIMEOUT, StrId::STR_ALWAYS}, "seamlessSleepScreen", + SettingInfo::Enum(StrId::STR_QUICK_RESUME_TIMEOUT, &CrossPointSettings::quickResumeSleepScreen, + {StrId::STR_STATE_OFF, StrId::STR_STATE_ON}, "quickResumeSleepScreen", StrId::STR_CAT_DISPLAY), SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage, {StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage", diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 5ea25653..797db488 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -19,12 +19,12 @@ void SleepActivity::onEnter() { Activity::onEnter(); - const bool renderSeamless = - SETTINGS.seamlessSleepScreen == CrossPointSettings::SEAMLESS_SLEEP_SCREEN::SEAMLESS_ALWAYS || + const bool renderQuickResume = + SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::QUICK_RESUME || (fromTimeout && - SETTINGS.seamlessSleepScreen == CrossPointSettings::SEAMLESS_SLEEP_SCREEN::SEAMLESS_AFTER_TIMEOUT); + SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT); - if (renderSeamless) { + if (renderQuickResume) { return renderLastScreenSleepScreen(); } diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index afd707c6..834e338d 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -253,6 +253,7 @@ void SettingsActivity::toggleCurrentSetting() { return; } + CrossPointSettings::normalizeDependentSettings(SETTINGS); SETTINGS.saveToFile(); } diff --git a/src/main.cpp b/src/main.cpp index 2a93a1cd..7fc557dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -243,16 +243,17 @@ void enterDeepSleep(bool fromTimeout = false) { HalPowerManager::Lock powerLock; // Ensure we are at normal CPU frequency for sleep preparation APP_STATE.lastSleepFromReader = activityManager.isReaderActivity(); - const bool isSeamless = SETTINGS.seamlessSleepScreen == CrossPointSettings::SEAMLESS_SLEEP_SCREEN::SEAMLESS_ALWAYS || - (fromTimeout && SETTINGS.seamlessSleepScreen == - CrossPointSettings::SEAMLESS_SLEEP_SCREEN::SEAMLESS_AFTER_TIMEOUT); - APP_STATE.showBootScreen = !isSeamless; + const bool isQuickResumeSleep = + SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::QUICK_RESUME || + (fromTimeout && + SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT); + APP_STATE.showBootScreen = !isQuickResumeSleep; APP_STATE.saveToFile(); activityManager.goToSleep(fromTimeout); - if (isSeamless) { + if (isQuickResumeSleep) { saveSleepFrameBuffer(); } diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index aeaad11e..1631746d 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -1274,6 +1274,7 @@ void CrossPointWebServer::handlePostSettings() { } } + CrossPointSettings::normalizeDependentSettings(SETTINGS); SETTINGS.saveToFile(); LOG_DBG("WEB", "Applied %d setting(s)", applied); From 4ffc2a7e7e102d366eccb48f662a21d9033ee096 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Wed, 20 May 2026 21:03:46 -0700 Subject: [PATCH 04/14] fix: sleep from a WiFi activity instead of silent-rebooting (#2092) Unify the two splash-skip signals (RTC silent-reboot flag, SD seamless-sleep flag) into one BootResume enum driving a single switch. Storage unchanged; behavior-preserving apart from the fix. Holding power to sleep from a WiFi activity (Font Download, OPDS, web server, Calibre, KOReader sync) rebooted to home instead of sleeping. goToSleep() runs the outgoing activity's onExit(), and those activities call silentRestart() to clear heap fragmentation, so the heap-defrag reboot fired before deep sleep could start. enterDeepSleep() now latches deepSleepInProgress before goToSleep(); silentRestart()/silentRestartToReader() no-op while it's set. Deep sleep is a full chip reset on wake, so it already clears the fragmentation the reboot existed for. Did you use AI tools to help write this code? partial --- src/main.cpp | 75 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7fc557dd..37af16bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,7 +142,25 @@ constexpr uint32_t SILENT_REBOOT_MAGIC = 0xC1EAB007; constexpr uint32_t SILENT_REBOOT_TARGET_HOME = 0; constexpr uint32_t SILENT_REBOOT_TARGET_READER = 1; +// How the device is coming back to life, resolved once at boot. Both resume +// flows suppress the splash and leave the panel holding its pre-boot frame; a +// plain boot shows the splash. See setup() for the resolution. +enum class BootResume : uint8_t { + Splash, // cold boot, flash, panic, or plain reboot + Silent, // heap-defrag ESP.restart() (RTC flag; lost on power loss) + QuickResume, // wake from a quick-resume deep sleep (SD flag; survives power loss) +}; + +// Latched true once enterDeepSleep() commits to sleeping, before it tears down +// the current activity. WiFi activities call silentRestart() in onExit() to +// clear heap fragmentation on the way out, but deep sleep is a full chip reset +// on wake and already clears the heap, so rebooting here would just power the +// device back up against the user's sleep gesture. Never cleared: +// startDeepSleep() does not return, so a set latch only ends at the wakeup reset. +static bool deepSleepInProgress = false; + void silentRestart() { + if (deepSleepInProgress) return; // sleeping supersedes the heap-defrag reboot silentRebootTarget = SILENT_REBOOT_TARGET_HOME; silentRebootMagic = SILENT_REBOOT_MAGIC; LOG_DBG("MAIN", "Silent restart (target=home)"); @@ -156,6 +174,7 @@ void silentRestart() { } void silentRestartToReader() { + if (deepSleepInProgress) return; // sleeping supersedes the heap-defrag reboot silentRebootTarget = SILENT_REBOOT_TARGET_READER; silentRebootMagic = SILENT_REBOOT_MAGIC; LOG_DBG("MAIN", "Silent restart (target=reader)"); @@ -251,6 +270,9 @@ void enterDeepSleep(bool fromTimeout = false) { APP_STATE.saveToFile(); + // Commit to sleeping before goToSleep() runs the outgoing activity's onExit(): + // a WiFi activity would otherwise silentRestart() here and reboot instead. + deepSleepInProgress = true; activityManager.goToSleep(fromTimeout); if (isQuickResumeSleep) { @@ -400,27 +422,39 @@ void setup() { // First serial output only here to avoid timing inconsistencies for power button press duration verification LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); - setupDisplayAndFonts(isSilentReboot || /*seamless=*/!APP_STATE.showBootScreen); + // Resolve the single boot-presentation decision. Skipping the splash also + // skips the panel-clearing pass and the X3 initial-full-sync arming (see + // HalDisplay::begin), so the first paint is FAST_REFRESH (~500ms) over the + // retained frame and input dispatches against a visible UI. + const BootResume resume = isSilentReboot ? BootResume::Silent + : !APP_STATE.showBootScreen ? BootResume::QuickResume + : BootResume::Splash; - // Silent reboot suppresses the boot splash and the X3 initial-full-sync - // arming (see HalDisplay::begin), so the first Home paint is FAST_REFRESH - // (~500ms) and input dispatches against the visible menu. - if (!isSilentReboot) { - if (APP_STATE.showBootScreen) { - activityManager.goToBoot(); - } else if (loadSleepFrameBuffer()) { - // Seamless wake: buffer restored, replace moon icon with loading icon - const auto pageHeight = renderer.getScreenHeight(); - renderer.drawImage(LoadingIcon, 0, pageHeight - LOADINGICON_HEIGHT, LOADINGICON_WIDTH, LOADINGICON_HEIGHT); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - APP_STATE.showBootScreen = true; - APP_STATE.saveToFile(); - } else { - // Frame buffer file missing — fall back to normal boot screen + setupDisplayAndFonts(resume != BootResume::Splash); + + switch (resume) { + case BootResume::Silent: + // Splash skipped: the routing block below picks the target activity; the + // panel keeps showing the pre-reboot popup until that first paint lands. + break; + case BootResume::QuickResume: + // One-shot flag: re-arm the splash for the next non-quick-resume boot. Save + // before any painting so a hang in the blocking paint path can't strand + // us in a quick-resume-with-no-frame loop on the next boot. APP_STATE.showBootScreen = true; APP_STATE.saveToFile(); + if (loadSleepFrameBuffer()) { + // Frame restored: swap the sleep moon for the loading icon. + const auto pageHeight = renderer.getScreenHeight(); + renderer.drawImage(LoadingIcon, 0, pageHeight - LOADINGICON_HEIGHT, LOADINGICON_WIDTH, LOADINGICON_HEIGHT); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } else { + activityManager.goToBoot(); // frame file missing, fall back to the splash + } + break; + case BootResume::Splash: activityManager.goToBoot(); - } + break; } if (recoveryFirmwareMode) { @@ -430,9 +464,10 @@ void setup() { } else if (HalSystem::isRebootFromPanic()) { // If we rebooted from a panic, go to crash report screen to show the panic info activityManager.goToCrashReport(); - } else if (isSilentReboot && snapshotTarget == SILENT_REBOOT_TARGET_READER && !APP_STATE.openEpubPath.empty()) { + } else if (resume == BootResume::Silent && snapshotTarget == SILENT_REBOOT_TARGET_READER && + !APP_STATE.openEpubPath.empty()) { activityManager.goToReader(APP_STATE.openEpubPath); - } else if (isSilentReboot) { + } else if (resume == BootResume::Silent) { // target == home (or reader with no open book): land on home — don't fall // through to the sleep-wake "resume reader" logic, which fires on stale // openEpubPath + lastSleepFromReader from a prior session. @@ -451,7 +486,7 @@ void setup() { activityManager.goToReader(path); } - if (isSilentReboot) { + if (resume == BootResume::Silent) { // Block until the first paint physically completes. refreshDisplay() // waits on the panel BUSY pin so when this returns the user can see the // new activity. Without the wait, an edge captured by gpio.update() From dc404b41c7aa54868d005a3234d608b533aab724 Mon Sep 17 00:00:00 2001 From: Matteo Scopel Date: Thu, 21 May 2026 12:08:50 +0200 Subject: [PATCH 05/14] chore: fix the Italian translation (#2095) --- lib/I18n/translations/italian.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 7bf3ddd8..4695fa09 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -122,7 +122,7 @@ STR_CLEAR_CACHE_FAILED: "Impossibile svuotare la cache" STR_CHECK_SERIAL_OUTPUT: "Controllare l'output seriale per dettagli" STR_DARK: "Scuro" STR_LIGHT: "Chiaro" -STR_CUSTOM: "Wallpaper" +STR_CUSTOM: "Sfondo" STR_COVER: "Copertina" STR_NONE_OPT: "Nessuno" STR_FIT: "Adatta" @@ -147,7 +147,7 @@ STR_SMALL: "Piccolo" STR_MEDIUM: "Medio" STR_LARGE: "Grande" STR_X_LARGE: "Molto grande" -STR_TIGHT: "Stretto" +STR_TIGHT: "Compatto" STR_NORMAL: "Normale" STR_WIDE: "Largo" STR_JUSTIFY: "Giustificato" @@ -175,13 +175,13 @@ STR_NO_UPDATE: "Nessun aggiornamento disponibile" STR_UPDATE_FAILED: "Aggiornamento non riuscito" STR_UPDATE_COMPLETE: "Aggiornamento completato" STR_POWER_ON_HINT: "Tenere premuto il tasto di accensione per riavviare" -STR_RESTARTING_HINT: "Riavvio... Se il dispositivo non si accende, tenere premuto il tasto per qualche secondo." +STR_RESTARTING_HINT: "Riavvio... se il dispositivo non si accende, tenere premuto il tasto per qualche secondo." STR_NO_ENTRIES: "Nessuna voce trovata" STR_DOWNLOADING: "Download..." STR_DOWNLOAD_FAILED: "Download non riuscito" STR_ERROR_MSG: "Errore:" STR_UNNAMED: "Senza nome" -STR_NO_SERVER_URL: "Nessun Server configurato" +STR_NO_SERVER_URL: "Nessun server configurato" STR_FETCH_FEED_FAILED: "Impossibile recuperare il feed" STR_PARSE_FEED_FAILED: "Impossibile analizzare il feed" STR_NEXT_PAGE: "Pag. successiva »" @@ -350,10 +350,10 @@ STR_KB_HINT_EDIT_ENTRY: "Tieni premuto SU per modificare la voce" STR_KB_TIPS: "Suggerimenti:" STR_KB_HINT_RETURN_KEYBOARD: "GIÙ per tornare alla tastiera" STR_KB_HINT_EXIT_URL_MODE: "ABC per uscire dalla modalità URL" -STR_KB_HINT_CLEAR_TEXT: "Tieni spinto CANC per cancellare tutto" -STR_KB_HINT_SECONDARY_CHAR: "Tieni spinto SELEZ. per carattere secondario" -STR_KB_HINT_UPPER_SECONDARY: "Tieni spinto SELEZ. per MAIUS. o car. secondario" -STR_KB_HINT_LOWER_SECONDARY: "Tieni spinto SELEZ. per minus. o car. secondario" +STR_KB_HINT_CLEAR_TEXT: "Tieni premuto CANC per cancellare tutto" +STR_KB_HINT_SECONDARY_CHAR: "Tieni premuto SELEZ. per carattere secondario" +STR_KB_HINT_UPPER_SECONDARY: "Tieni premuto SELEZ. per MAIUS. o car. secondario" +STR_KB_HINT_LOWER_SECONDARY: "Tieni premuto SELEZ. per minus. o car. secondario" STR_KB_HINT_URL_SNIPPETS: "Premi l'URL per le anteprime" STR_SD_FIRMWARE_UPDATE: "Aggiornamento firmware da SD" STR_SELECT_FIRMWARE_FILE: "Seleziona il firmware (.bin)" From 2dd491b62e14f8cb3acc9c0359b45c3d478d5f98 Mon Sep 17 00:00:00 2001 From: WuTofu <5987870+WuTofu@users.noreply.github.com> Date: Thu, 21 May 2026 19:44:10 +0800 Subject: [PATCH 06/14] refactor: unify book cache clearing for epub, txt, and xtc files (#1875) --- lib/Txt/Txt.cpp | 15 ++++++++ lib/Txt/Txt.h | 1 + .../browser/OpdsBookBrowserActivity.cpp | 4 +-- .../settings/ClearCacheActivity.cpp | 5 +-- src/network/CrossPointWebServer.cpp | 23 ++++--------- src/network/WebDAVHandler.cpp | 16 +++------ src/network/WebDAVHandler.h | 1 - src/util/BookCacheUtils.cpp | 34 +++++++++++++++++++ src/util/BookCacheUtils.h | 10 ++++++ 9 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 src/util/BookCacheUtils.cpp create mode 100644 src/util/BookCacheUtils.h diff --git a/lib/Txt/Txt.cpp b/lib/Txt/Txt.cpp index 0209923a..b5d22259 100644 --- a/lib/Txt/Txt.cpp +++ b/lib/Txt/Txt.cpp @@ -155,6 +155,21 @@ bool Txt::generateCoverBmp() const { return false; } +bool Txt::clearCache() const { + if (!Storage.exists(cachePath.c_str())) { + LOG_DBG("TXT", "Cache does not exist, no action needed"); + return true; + } + + if (!Storage.removeDir(cachePath.c_str())) { + LOG_ERR("TXT", "Failed to clear cache"); + return false; + } + + LOG_DBG("TXT", "Cache cleared successfully"); + return true; +} + bool Txt::readContent(uint8_t* buffer, size_t offset, size_t length) const { if (!loaded) { return false; diff --git a/lib/Txt/Txt.h b/lib/Txt/Txt.h index b342ca88..859ae23b 100644 --- a/lib/Txt/Txt.h +++ b/lib/Txt/Txt.h @@ -22,6 +22,7 @@ class Txt { [[nodiscard]] size_t getFileSize() const { return fileSize; } void setupCacheDir() const; + bool clearCache() const; // Cover image support - looks for cover.bmp/jpg/jpeg/png in same folder as txt file [[nodiscard]] std::string getCoverBmpPath() const; diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index 40eada07..6ff5b719 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -1,6 +1,5 @@ #include "OpdsBookBrowserActivity.h" -#include #include #include #include @@ -14,6 +13,7 @@ #include "components/UITheme.h" #include "fontIds.h" #include "network/HttpDownloader.h" +#include "util/BookCacheUtils.h" #include "util/StringUtils.h" #include "util/UrlUtils.h" @@ -283,7 +283,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) { nullptr, server.username, server.password); if (result == HttpDownloader::OK) { - Epub(filename, "/.crosspoint").clearCache(); + clearBookCache(filename); state = BrowserState::BROWSING; } else { state = BrowserState::ERROR; diff --git a/src/activities/settings/ClearCacheActivity.cpp b/src/activities/settings/ClearCacheActivity.cpp index c4fc4347..b0dcbfbb 100644 --- a/src/activities/settings/ClearCacheActivity.cpp +++ b/src/activities/settings/ClearCacheActivity.cpp @@ -8,6 +8,7 @@ #include "MappedInputManager.h" #include "components/UITheme.h" #include "fontIds.h" +#include "util/BookCacheUtils.h" void ClearCacheActivity::onEnter() { Activity::onEnter(); @@ -94,8 +95,8 @@ void ClearCacheActivity::clearCache() { file.getName(name, sizeof(name)); String itemName(name); - // Only delete directories starting with epub_ or xtc_ - if (file.isDirectory() && (itemName.startsWith("epub_") || itemName.startsWith("xtc_"))) { + // Only delete directories matching known book cache names. + if (file.isDirectory() && isBookCacheDirectoryName(itemName.c_str())) { String fullPath = "/.crosspoint/" + itemName; LOG_DBG("CLEAR_CACHE", "Removing cache: %s", fullPath.c_str()); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 1631746d..87210b11 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -1,7 +1,6 @@ #include "CrossPointWebServer.h" #include -#include #include #include #include @@ -23,6 +22,7 @@ #include "html/HomePageHtml.generated.h" #include "html/SettingsPageHtml.generated.h" #include "html/js/jszip_minJs.generated.h" +#include "util/BookCacheUtils.h" namespace { // Folders/files to hide from the web interface file browser @@ -48,15 +48,6 @@ String wsLastCompleteName; size_t wsLastCompleteSize = 0; unsigned long wsLastCompleteAt = 0; -// Helper function to clear epub cache after upload -void clearEpubCacheIfNeeded(const String& filePath) { - // Only clear cache for .epub files - if (FsHelpers::hasEpubExtension(filePath)) { - Epub(filePath.c_str(), "/.crosspoint").clearCache(); - LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str()); - } -} - String normalizeWebPath(const String& inputPath) { if (inputPath.isEmpty() || inputPath == "/") { return "/"; @@ -732,7 +723,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { String filePath = state.path; if (!filePath.endsWith("/")) filePath += "/"; filePath += state.fileName; - clearEpubCacheIfNeeded(filePath); + clearBookCache(filePath.c_str()); } } } else if (upload.status == UPLOAD_FILE_ABORTED) { @@ -878,7 +869,7 @@ void CrossPointWebServer::handleRename() const { return; } - clearEpubCacheIfNeeded(itemPath); + clearBookCache(itemPath.c_str()); const bool success = file.rename(newPath.c_str()); file.close(); @@ -971,7 +962,7 @@ void CrossPointWebServer::handleMove() const { return; } - clearEpubCacheIfNeeded(itemPath); + clearBookCache(itemPath.c_str()); const bool success = file.rename(newPath.c_str()); file.close(); @@ -1090,7 +1081,7 @@ void CrossPointWebServer::handleDelete() const { // It's a file (or couldn't open as dir) — remove file if (f) f.close(); success = Storage.remove(itemPath.c_str()); - clearEpubCacheIfNeeded(itemPath); + clearBookCache(itemPath.c_str()); } if (!success) { @@ -1636,7 +1627,7 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* wsLastCompleteSize = 0; wsLastCompleteAt = millis(); LOG_DBG("WS", "Zero-byte upload complete: %s", filePath.c_str()); - clearEpubCacheIfNeeded(filePath); + clearBookCache(filePath.c_str()); wsServer->sendTXT(num, "DONE"); wsLastProgressSent = 0; break; @@ -1705,7 +1696,7 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* String filePath = wsUploadPath; if (!filePath.endsWith("/")) filePath += "/"; filePath += wsUploadFileName; - clearEpubCacheIfNeeded(filePath); + clearBookCache(filePath.c_str()); wsServer->sendTXT(num, "DONE"); wsLastProgressSent = 0; diff --git a/src/network/WebDAVHandler.cpp b/src/network/WebDAVHandler.cpp index f20e8261..0fafb680 100644 --- a/src/network/WebDAVHandler.cpp +++ b/src/network/WebDAVHandler.cpp @@ -1,11 +1,12 @@ #include "WebDAVHandler.h" -#include #include #include #include #include +#include "util/BookCacheUtils.h" + namespace { constexpr const char* HIDDEN_ITEMS[] = {"System Volume Information", "XTCache"}; @@ -384,7 +385,7 @@ void WebDAVHandler::handlePut(WebServer& s) { return; } - clearEpubCacheIfNeeded(path); + clearBookCache(path.c_str()); s.send(_putExisted ? 204 : 201); LOG_DBG("DAV", "PUT complete: %s", path.c_str()); } @@ -433,7 +434,7 @@ void WebDAVHandler::handleDelete(WebServer& s) { } } else { file.close(); - clearEpubCacheIfNeeded(path); + clearBookCache(path.c_str()); if (Storage.remove(path.c_str())) { s.send(204); } else { @@ -542,7 +543,7 @@ void WebDAVHandler::handleMove(WebServer& s) { return; } - clearEpubCacheIfNeeded(srcPath); + clearBookCache(srcPath.c_str()); bool success = file.rename(dstPath.c_str()); file.close(); @@ -797,13 +798,6 @@ bool WebDAVHandler::getOverwrite(WebServer& s) const { return true; // Default is T } -void WebDAVHandler::clearEpubCacheIfNeeded(const String& path) const { - if (FsHelpers::hasEpubExtension(path)) { - Epub(path.c_str(), "/.crosspoint").clearCache(); - LOG_DBG("DAV", "Cleared epub cache for: %s", path.c_str()); - } -} - String WebDAVHandler::getMimeType(const String& path) const { if (FsHelpers::hasEpubExtension(path)) return "application/epub+zip"; if (FsHelpers::checkFileExtension(path, ".pdf")) return "application/pdf"; diff --git a/src/network/WebDAVHandler.h b/src/network/WebDAVHandler.h index 5911203f..e11e184b 100644 --- a/src/network/WebDAVHandler.h +++ b/src/network/WebDAVHandler.h @@ -38,7 +38,6 @@ class WebDAVHandler : public RequestHandler { bool isProtectedPath(const String& path) const; int getDepth(WebServer& s) const; bool getOverwrite(WebServer& s) const; - void clearEpubCacheIfNeeded(const String& path) const; void sendPropEntry(WebServer& s, const String& href, bool isDir, size_t size, const String& lastModified) const; String getMimeType(const String& path) const; }; diff --git a/src/util/BookCacheUtils.cpp b/src/util/BookCacheUtils.cpp new file mode 100644 index 00000000..a539c053 --- /dev/null +++ b/src/util/BookCacheUtils.cpp @@ -0,0 +1,34 @@ +#include "BookCacheUtils.h" + +#include +#include +#include +#include +#include + +bool isBookCacheDirectoryName(const char* name) { + if (!name) { + return false; + } + + constexpr char EPUB_PREFIX[] = "epub_"; + constexpr char TXT_PREFIX[] = "txt_"; + constexpr char XTC_PREFIX[] = "xtc_"; + + return strncmp(name, EPUB_PREFIX, std::size(EPUB_PREFIX) - 1) == 0 || + strncmp(name, TXT_PREFIX, std::size(TXT_PREFIX) - 1) == 0 || + strncmp(name, XTC_PREFIX, std::size(XTC_PREFIX) - 1) == 0; +} + +void clearBookCache(const std::string& path) { + if (FsHelpers::hasEpubExtension(path)) { + Epub(path, "/.crosspoint").clearCache(); + } else if (FsHelpers::hasXtcExtension(path)) { + Xtc(path, "/.crosspoint").clearCache(); + } else if (FsHelpers::hasTxtExtension(path)) { + Txt(path, "/.crosspoint").clearCache(); + } else { + return; + } + LOG_DBG("BookCache", "Done checking metadata cache for: %s", path.c_str()); +} diff --git a/src/util/BookCacheUtils.h b/src/util/BookCacheUtils.h new file mode 100644 index 00000000..c10c8a22 --- /dev/null +++ b/src/util/BookCacheUtils.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +// Clears the reading cache for a book file if its extension is recognised +// (EPUB, XTC, or TXT). Does nothing for other file types. +void clearBookCache(const std::string& path); + +// Returns true if the directory name matches a book cache entry. +bool isBookCacheDirectoryName(const char* name); From f39ba7037f6657041523a0484b64a1ca5d2594ec Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 21 May 2026 21:06:51 -0400 Subject: [PATCH 07/14] fix(settings): preserve quick resume timeout preference (#2101) ## Summary ### **What is the goal of this PR?** This fixes an unintended settings side effect when cycling the `Sleep Screen` option through `Quick Resume`. Previously, selecting `Sleep Screen = Quick Resume` globally forced `Quick Resume on Timeout = ON` and left it enabled even after the user toggled `Sleep Screen` to another option within the same settings session. Now the auto-enable behavior is scoped to the Settings screen session: - If `Quick Resume on Timeout` was already `ON` when entering Settings, it stays `ON`. - If it was `OFF`, selecting `Sleep Screen = Quick Resume` temporarily turns it `ON`. - If the user then switches away from `Quick Resume`, it turns back `OFF`. ### **What changes are included?** - Removes the global logic that permanently forced `Quick Resume on Timeout` to `ON` whenever `Sleep Screen` was set to `Quick Resume`, even if it was just due to toggling through the options. - Adds Settings-screen session tracking so `Quick Resume on Timeout` is only auto-enabled while the user has `Sleep Screen = Quick Resume`. - Restores `Quick Resume on Timeout` back to `OFF` when the user switches away, but only if it was `OFF` when they entered Settings. - Preserves existing `ON` timeout preferences. - Same behavior applies to the web settings ## Additional Context - Tested this on device and via the settings UI --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES >**_ --- src/CrossPointSettings.cpp | 6 -- src/CrossPointSettings.h | 1 - src/JsonSettingsIO.cpp | 4 - src/activities/settings/SettingsActivity.cpp | 31 +++++- src/activities/settings/SettingsActivity.h | 4 + src/network/CrossPointWebServer.cpp | 1 - src/network/html/SettingsPage.html | 108 ++++++++++++++++++- 7 files changed, 138 insertions(+), 17 deletions(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index abe879f2..7dd9051d 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -79,12 +79,6 @@ void CrossPointSettings::validateFrontButtonMapping(CrossPointSettings& settings } } -void CrossPointSettings::normalizeDependentSettings(CrossPointSettings& settings) { - if (settings.sleepScreen == SLEEP_SCREEN_MODE::QUICK_RESUME) { - settings.quickResumeSleepScreen = QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT; - } -} - bool CrossPointSettings::saveToFile() const { Storage.mkdir("/.crosspoint"); return JsonSettingsIO::saveSettings(*this, SETTINGS_FILE_JSON); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 86b379d7..ba12a473 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -275,7 +275,6 @@ class CrossPointSettings { bool loadFromFile(); static void validateFrontButtonMapping(CrossPointSettings& settings); - static void normalizeDependentSettings(CrossPointSettings& settings); private: bool loadFromBinaryFile(); diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index ebe52ff1..065c6e51 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -223,10 +223,6 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool* } } - const uint8_t quickResumeBeforeNormalize = s.quickResumeSleepScreen; - CrossPointSettings::normalizeDependentSettings(s); - if (s.quickResumeSleepScreen != quickResumeBeforeNormalize && needsResave) *needsResave = true; - // Front button remap — managed by RemapFrontButtons sub-activity, not in SettingsList. using S = CrossPointSettings; s.frontButtonBack = diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 834e338d..8a5888d9 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -86,6 +86,10 @@ void SettingsActivity::onEnter() { // Reset selection to first category selectedCategoryIndex = 0; selectedSettingIndex = 0; + preserveQuickResumeTimeoutOn = + SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT; + quickResumeTimeoutAutoEnabled = false; + syncQuickResumeTimeoutForSleepScreen(/*sleepScreenChanged=*/true, /*quickResumeTimeoutChanged=*/false); rebuildSettingsLists(); @@ -176,6 +180,8 @@ void SettingsActivity::toggleCurrentSetting() { } const auto& setting = (*currentSettings)[selectedSetting]; + const bool sleepScreenChanged = setting.valuePtr == &CrossPointSettings::sleepScreen; + const bool quickResumeTimeoutChanged = setting.valuePtr == &CrossPointSettings::quickResumeSleepScreen; if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) { // Toggle the boolean value using the member pointer @@ -253,10 +259,33 @@ void SettingsActivity::toggleCurrentSetting() { return; } - CrossPointSettings::normalizeDependentSettings(SETTINGS); + syncQuickResumeTimeoutForSleepScreen(sleepScreenChanged, quickResumeTimeoutChanged); SETTINGS.saveToFile(); } +void SettingsActivity::syncQuickResumeTimeoutForSleepScreen(bool sleepScreenChanged, bool quickResumeTimeoutChanged) { + if (quickResumeTimeoutChanged) { + preserveQuickResumeTimeoutOn = + SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT; + quickResumeTimeoutAutoEnabled = false; + } + + if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::QUICK_RESUME) { + if (SETTINGS.quickResumeSleepScreen != CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT) { + SETTINGS.quickResumeSleepScreen = CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT; + quickResumeTimeoutAutoEnabled = !preserveQuickResumeTimeoutOn; + } else if (sleepScreenChanged && !preserveQuickResumeTimeoutOn) { + quickResumeTimeoutAutoEnabled = true; + } + return; + } + + if (sleepScreenChanged && quickResumeTimeoutAutoEnabled && !preserveQuickResumeTimeoutOn) { + SETTINGS.quickResumeSleepScreen = CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_NEVER; + quickResumeTimeoutAutoEnabled = false; + } +} + void SettingsActivity::render(RenderLock&&) { renderer.clearScreen(); diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 1767272c..44a22ea9 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -156,12 +156,16 @@ class SettingsActivity final : public Activity { std::vector systemSettings; const std::vector* currentSettings = nullptr; + bool preserveQuickResumeTimeoutOn = false; + bool quickResumeTimeoutAutoEnabled = false; + static constexpr int categoryCount = 4; static const StrId categoryNames[categoryCount]; void enterCategory(int categoryIndex); void toggleCurrentSetting(); void rebuildSettingsLists(); + void syncQuickResumeTimeoutForSleepScreen(bool sleepScreenChanged, bool quickResumeTimeoutChanged); public: explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 87210b11..41606f8d 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -1265,7 +1265,6 @@ void CrossPointWebServer::handlePostSettings() { } } - CrossPointSettings::normalizeDependentSettings(SETTINGS); SETTINGS.saveToFile(); LOG_DBG("WEB", "Applied %d setting(s)", applied); diff --git a/src/network/html/SettingsPage.html b/src/network/html/SettingsPage.html index 0a73f9bd..4291a21b 100644 --- a/src/network/html/SettingsPage.html +++ b/src/network/html/SettingsPage.html @@ -312,6 +312,11 @@