feat: Seamless sleep/wake screens for displaying book pages during deep sleep (#2064)

<img width="605" height="454" alt="image"
src="https://github.com/user-attachments/assets/bfd84afe-3b58-436e-9a5d-539af3ec3d4e"
/>

Actualized "Last" sleep screen setting from previous PRs, rebranded as a
~~`Seamless Sleep`~~ `Page as Sleep Screen` option with more
improvements.


https://github.com/user-attachments/assets/59029ba6-007e-4841-abfa-f680d7e98b79

---

New option: `Page as Sleep Screen` - `Never (default)`, `After Timeout`,
`Always`

When enabled, it seamlessly sleeps on timeout or power off, making a
fast refresh for the moon icon. When waking up, we still show the last
page, instead of the boot screen, making it fully seamless.

I tried different icons such as "refresh arrow" and others, but they
looked not as nice as 3 simple dots.

With this mode, the device turns off 4 seconds faster. And has 6 seconds
less delay when turning back on. Much more responsive.

Previously, even a 10-minute timeout sometimes wasn't enough, and I'd
worry about seeing the book cover. It's now easier to use a shorter
sleep timeout: if I get distracted during a reading session but don't
want to stop, the new screen is much more inviting to come back to.

---

Did you use AI tools to help write this code? _**PARTIALLY**_.

---

Test v1.3.0 firmware.bin file
[download](https://github.com/user-attachments/files/28015193/firmware.zip)

Based on PRs #410 and #495

Closes #400, #1649

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eloren1
2026-05-19 18:45:28 -04:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 082b5f295b
commit 41e6e15229
35 changed files with 202 additions and 23 deletions
+2
View File
@@ -206,6 +206,8 @@ STR_THEME_CLASSIC: "Класічная"
STR_THEME_LYRA: "Lyra"
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
STR_SUNLIGHT_FADING_FIX: "Кампенсацыя выцвітання"
STR_SEAMLESS_SLEEP: "Старонка як экран сну"
STR_AFTER_TIMEOUT: "Пасля таймаўту"
STR_REMAP_FRONT_BUTTONS: "Пераназначыць пярэднія кнопкі"
STR_OPDS_BROWSER: "OPDS браўзер"
STR_COVER_CUSTOM: "Вокладка + Свой"
+2
View File
@@ -234,6 +234,8 @@ 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_AFTER_TIMEOUT: "Després del temps"
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
STR_OPDS_BROWSER: "Navegador OPDS"
STR_COVER_CUSTOM: "Portada + Personalitzat"
+2
View File
@@ -212,6 +212,8 @@ 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_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í"
+2
View File
@@ -234,6 +234,8 @@ 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_AFTER_TIMEOUT: "Efter timeout"
STR_REMAP_FRONT_BUTTONS: "Omtildel frontknapper"
STR_OPDS_BROWSER: "OPDS Browser"
STR_COVER_CUSTOM: "Omslag + Brugerdefineret"
+2
View File
@@ -234,6 +234,8 @@ 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_AFTER_TIMEOUT: "Na timeout"
STR_REMAP_FRONT_BUTTONS: "Knoppen voorzijde wijzigen"
STR_OPDS_BROWSER: "OPDS-browser"
STR_COVER_CUSTOM: "Omslag + Aangepast"
+2
View File
@@ -62,6 +62,8 @@ 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_AFTER_TIMEOUT: "After Timeout"
STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode"
STR_HIDE_BATTERY: "Hide Battery %"
STR_EXTRA_SPACING: "Extra Paragraph Spacing"
+2
View File
@@ -211,6 +211,8 @@ 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_AFTER_TIMEOUT: "Aikakatkon jälkeen"
STR_REMAP_FRONT_BUTTONS: "Uudelleenmääritä etupainikkeet"
STR_OPDS_BROWSER: "OPDS-selain"
STR_COVER_CUSTOM: "Kansi + mukautettu"
+2
View File
@@ -235,6 +235,8 @@ 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_AFTER_TIMEOUT: "Après délai"
STR_REMAP_FRONT_BUTTONS: "Configurer boutons façade"
STR_OPDS_BROWSER: "Navigateur OPDS"
STR_COVER_CUSTOM: "Couverture + Perso"
+2
View File
@@ -233,6 +233,8 @@ 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_AFTER_TIMEOUT: "Nach Timeout"
STR_REMAP_FRONT_BUTTONS: "Vordere Tasten belegen"
STR_OPDS_BROWSER: "OPDS-Browser"
STR_COVER_CUSTOM: "Umschlag + Eigenes"
+2
View File
@@ -231,6 +231,8 @@ 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_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"
+2
View File
@@ -242,6 +242,8 @@ 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_AFTER_TIMEOUT: "Dopo timeout"
STR_REMAP_FRONT_BUTTONS: "Rimappa pulsanti frontali"
STR_OPDS_BROWSER: "Browser OPDS"
STR_SEARCH: "Cerca"
+2
View File
@@ -207,6 +207,8 @@ STR_THEME_CLASSIC: "Классикалық"
STR_THEME_LYRA: "Lyra"
STR_THEME_LYRA_EXTENDED: "Lyra кеңейтілген"
STR_SUNLIGHT_FADING_FIX: "Күн сәулесінен солу түзету"
STR_SEAMLESS_SLEEP: "Бет – ұйқы экраны"
STR_AFTER_TIMEOUT: "Таймауттан кейін"
STR_REMAP_FRONT_BUTTONS: "Алдыңғы түймелерді қайта баптау"
STR_OPDS_BROWSER: "OPDS шолғышы"
STR_COVER_CUSTOM: "Мұқаба + Өзгертілген"
+2
View File
@@ -231,6 +231,8 @@ 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_AFTER_TIMEOUT: "Po skirtojo laiko"
STR_REMAP_FRONT_BUTTONS: "Keisti mygtukus"
STR_OPDS_BROWSER: "OPDS naršyklė"
STR_COVER_CUSTOM: "Viršelis + Kita"
+2
View File
@@ -242,6 +242,8 @@ 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_AFTER_TIMEOUT: "Po upływie czasu"
STR_REMAP_FRONT_BUTTONS: "Skonfiguruj przednie przyciski"
STR_OPDS_BROWSER: "OPDS Browser"
STR_SEARCH: "Szukaj"
+2
View File
@@ -212,6 +212,8 @@ 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_AFTER_TIMEOUT: "Após tempo limite"
STR_REMAP_FRONT_BUTTONS: "Remapear botões frontais"
STR_OPDS_BROWSER: "Navegador OPDS"
STR_COVER_CUSTOM: "Capa + personalizado"
+2
View File
@@ -234,6 +234,8 @@ 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_AFTER_TIMEOUT: "După timeout"
STR_REMAP_FRONT_BUTTONS: "Remapare butoane frontale"
STR_OPDS_BROWSER: "Browser OPDS"
STR_COVER_CUSTOM: "Copertă + Personalizat"
+2
View File
@@ -242,6 +242,8 @@ STR_THEME_LYRA: "Lyra"
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
STR_SUNLIGHT_FADING_FIX: "Компенсация выцветания"
STR_SEAMLESS_SLEEP: "Страница как экран сна"
STR_AFTER_TIMEOUT: "По таймауту"
STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки"
STR_OPDS_BROWSER: "OPDS браузер"
STR_SEARCH: "Поиск"
+2
View File
@@ -231,6 +231,8 @@ 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_AFTER_TIMEOUT: "Po izteku"
STR_REMAP_FRONT_BUTTONS: "Prenastavi sprednje gumbe"
STR_OPDS_BROWSER: "OPDS brskalnik"
STR_COVER_CUSTOM: "Naslovnica + po meri"
+2
View File
@@ -242,6 +242,8 @@ 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_AFTER_TIMEOUT: "Tras tiempo"
STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales"
STR_OPDS_BROWSER: "Navegador OPDS"
STR_SEARCH: "Buscar"
+2
View File
@@ -242,6 +242,8 @@ 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_AFTER_TIMEOUT: "Efter timeout"
STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar"
STR_OPDS_BROWSER: "OPDS-webbläsare"
STR_SEARCH: "Sök"
+2
View File
@@ -211,6 +211,8 @@ 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_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"
+2
View File
@@ -242,6 +242,8 @@ STR_THEME_LYRA: "Lyra"
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
STR_SUNLIGHT_FADING_FIX: "Виправлення вицвітання на сонці"
STR_SEAMLESS_SLEEP: "Сторінка як екран сну"
STR_AFTER_TIMEOUT: "Після таймауту"
STR_REMAP_FRONT_BUTTONS: "Налаштувати передні кнопки"
STR_OPDS_BROWSER: "Браузер OPDS"
STR_SEARCH: "Пошук"
+9 -6
View File
@@ -10,7 +10,7 @@ HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_R
HalDisplay::~HalDisplay() {}
void HalDisplay::begin() {
void HalDisplay::begin(bool seamless) {
// Set X3-specific panel mode before initializing.
if (gpio.deviceIsX3()) {
einkDisplay.setDisplayX3();
@@ -18,11 +18,14 @@ void HalDisplay::begin() {
einkDisplay.begin();
// Request resync after specific wakeup events to ensure clean display state
const auto wakeupReason = gpio.getWakeupReason();
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
wakeupReason == HalGPIO::WakeupReason::Other) {
einkDisplay.requestResync();
// Request resync after specific wakeup events to ensure clean display state.
// Skip when seamless=true so the current screen content is preserved.
if (!seamless) {
const auto wakeupReason = gpio.getWakeupReason();
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
wakeupReason == HalGPIO::WakeupReason::Other) {
einkDisplay.requestResync();
}
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ class HalDisplay {
};
// Initialize the display hardware and driver
void begin();
void begin(bool seamless = false);
// Display dimensions
static constexpr uint16_t DISPLAY_WIDTH = EInkDisplay::DISPLAY_WIDTH;
+9
View File
@@ -155,6 +155,13 @@ 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
};
// Sleep screen settings
uint8_t sleepScreen = DARK;
// Sleep screen cover mode settings
@@ -242,6 +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;
~CrossPointSettings() = default;
+1
View File
@@ -15,6 +15,7 @@ class CrossPointState {
uint8_t recentSleepFill = 0; // valid entries (0..SLEEP_RECENT_COUNT)
uint8_t readerActivityLoadCount = 0;
bool lastSleepFromReader = false;
bool showBootScreen = true;
// Returns true if idx was shown within the last checkCount picks.
// Walks backwards from the most recently written slot.
+2
View File
@@ -75,6 +75,7 @@ bool JsonSettingsIO::saveState(const CrossPointState& s, const char* path) {
doc["recentSleepFill"] = s.recentSleepFill;
doc["readerActivityLoadCount"] = s.readerActivityLoadCount;
doc["lastSleepFromReader"] = s.lastSleepFromReader;
doc["showBootScreen"] = s.showBootScreen;
String json;
serializeJson(doc, json);
@@ -109,6 +110,7 @@ bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) {
}
s.readerActivityLoadCount = doc["readerActivityLoadCount"] | static_cast<uint8_t>(0);
s.lastSleepFromReader = doc["lastSleepFromReader"] | false;
s.showBootScreen = doc["showBootScreen"] | true;
return true;
}
+3
View File
@@ -113,6 +113,9 @@ inline std::vector<SettingInfo> getSettingsList(const SdCardFontRegistry* regist
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",
StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
StrId::STR_CAT_DISPLAY),
+2 -2
View File
@@ -196,8 +196,8 @@ void ActivityManager::goToReader(std::string path) {
replaceActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
}
void ActivityManager::goToSleep() {
replaceActivity(std::make_unique<SleepActivity>(renderer, mappedInput));
void ActivityManager::goToSleep(bool fromTimeout) {
replaceActivity(std::make_unique<SleepActivity>(renderer, mappedInput, fromTimeout));
loop(); // Important: sleep screen must be rendered immediately, the caller will go to sleep right after this returns
}
+1 -1
View File
@@ -86,7 +86,7 @@ class ActivityManager {
void goToRecentBooks();
void goToBrowser();
void goToReader(std::string path);
void goToSleep();
void goToSleep(bool fromTimeout = false);
void goToBoot();
void goToFullScreenMessage(std::string message, EpdFontFamily::Style style = EpdFontFamily::REGULAR);
void goToCrashReport();
@@ -14,10 +14,20 @@
#include "components/UITheme.h"
#include "fontIds.h"
#include "images/Logo120.h"
#include "images/MoonIcon.h"
void SleepActivity::onEnter() {
Activity::onEnter();
const bool renderSeamless =
SETTINGS.seamlessSleepScreen == CrossPointSettings::SEAMLESS_SLEEP_SCREEN::SEAMLESS_ALWAYS ||
(fromTimeout &&
SETTINGS.seamlessSleepScreen == CrossPointSettings::SEAMLESS_SLEEP_SCREEN::SEAMLESS_AFTER_TIMEOUT);
if (renderSeamless) {
return renderLastScreenSleepScreen();
}
// Show popup with reader orientation only when going to sleep from reader
if (APP_STATE.lastSleepFromReader) {
ReaderUtils::applyOrientation(renderer, SETTINGS.orientation);
@@ -307,6 +317,12 @@ void SleepActivity::renderCoverSleepScreen() const {
return (this->*renderNoCoverSleepScreen)();
}
void SleepActivity::renderLastScreenSleepScreen() const {
const auto pageHeight = renderer.getScreenHeight();
renderer.drawImage(MoonIcon, 0, pageHeight - MOONICON_HEIGHT, MOONICON_WIDTH, MOONICON_HEIGHT);
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
}
void SleepActivity::renderBlankSleepScreen() const {
renderer.clearScreen();
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
+5 -2
View File
@@ -5,8 +5,8 @@ class Bitmap;
class SleepActivity final : public Activity {
public:
explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
: Activity("Sleep", renderer, mappedInput) {}
explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, bool fromTimeout = false)
: Activity("Sleep", renderer, mappedInput), fromTimeout(fromTimeout) {}
void onEnter() override;
private:
@@ -14,5 +14,8 @@ class SleepActivity final : public Activity {
void renderCustomSleepScreen() const;
void renderCoverSleepScreen() const;
void renderBitmapSleepScreen(const Bitmap& bitmap) const;
void renderLastScreenSleepScreen() const;
void renderBlankSleepScreen() const;
bool fromTimeout = false;
};
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
// Image dimensions: 48x48, diagonal dots
#define LOADINGICON_WIDTH 48
#define LOADINGICON_HEIGHT 48
static const uint8_t LoadingIcon[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
// Image dimensions: 48x48
#define MOONICON_WIDTH 48
#define MOONICON_HEIGHT 48
static const uint8_t MoonIcon[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF,
0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xF0, 0xFF, 0xFF,
0xFF, 0xC0, 0x1F, 0xF0, 0x3F, 0xFF, 0xFF, 0x00, 0x1F, 0xF8, 0x0F, 0xFF, 0xFF, 0xF0, 0x7F, 0xFC, 0x07, 0xFF,
0xFF, 0xF9, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xF9, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xF9, 0xFF, 0xFE, 0x01, 0xFF,
0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0xFF,
0xFF, 0xC7, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xC3, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xC3, 0xFF, 0xF0, 0x00, 0x7F,
0xFF, 0xC1, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xC0, 0x7F, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF,
0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF,
0xFF, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+61 -11
View File
@@ -30,6 +30,7 @@
#include "activities/settings/SdFirmwareUpdateActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "images/LoadingIcon.h"
#include "util/ButtonNavigator.h"
#include "util/ScreenshotUtil.h"
@@ -39,6 +40,7 @@ ActivityManager activityManager(renderer, mappedInputManager);
FontDecompressor fontDecompressor;
SdCardFontSystem sdFontSystem;
FontCacheManager fontCacheManager(renderer.getFontMap(), renderer.getSdCardFonts());
static unsigned long allowSleepAt = 0;
// Fonts
EpdFont notoserif14RegularFont(&notoserif_14_regular);
@@ -207,13 +209,46 @@ void waitForPowerRelease() {
}
}
constexpr char SLEEP_FRAME_FILE[] = "/.crosspoint/sleep_frame.bin";
static void saveSleepFrameBuffer() {
FsFile file;
if (!Storage.openFileForWrite("SLP", SLEEP_FRAME_FILE, file)) return;
file.write(renderer.getFrameBuffer(), renderer.getBufferSize());
file.close();
}
static bool loadSleepFrameBuffer() {
FsFile file;
if (!Storage.openFileForRead("SLP", SLEEP_FRAME_FILE, file)) return false;
const size_t bufferSize = display.getBufferSize();
const size_t bytesRead = file.read(display.getFrameBuffer(), bufferSize);
file.close();
if (bytesRead != bufferSize) {
Storage.remove(SLEEP_FRAME_FILE);
return false;
}
Storage.remove(SLEEP_FRAME_FILE);
return true;
}
// Enter deep sleep mode
void enterDeepSleep() {
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;
APP_STATE.saveToFile();
activityManager.goToSleep();
activityManager.goToSleep(fromTimeout);
if (isSeamless) {
saveSleepFrameBuffer();
}
// Tear down WiFi so the modem power domain isn't held alive across deep sleep.
// Wake from deep sleep is effectively a chip reset, so no state needs to survive.
@@ -229,8 +264,8 @@ void enterDeepSleep() {
powerManager.startDeepSleep(gpio);
}
void setupDisplayAndFonts() {
display.begin();
void setupDisplayAndFonts(bool seamless = false) {
display.begin(seamless);
renderer.begin();
activityManager.begin();
LOG_DBG("MAIN", "Display initialized");
@@ -313,6 +348,8 @@ void setup() {
HalSystem::checkPanic();
SETTINGS.loadFromFile();
APP_STATE.loadFromFile();
RECENT_BOOKS.loadFromFile();
I18N.setLanguage(static_cast<Language>(SETTINGS.language));
KOREADER_STORE.loadFromFile();
OPDS_STORE.loadFromFile();
@@ -360,17 +397,28 @@ 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();
setupDisplayAndFonts(/*seamless=*/!APP_STATE.showBootScreen);
// First paint after silent reboot is HALF_REFRESH (SDK forces it after begin()'s
// panel reset); subsequent paints FAST.
if (!isSilentReboot) {
activityManager.goToBoot();
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
APP_STATE.showBootScreen = true;
APP_STATE.saveToFile();
activityManager.goToBoot();
}
}
APP_STATE.loadFromFile();
RECENT_BOOKS.loadFromFile();
if (recoveryFirmwareMode) {
// Skip normal home/reader routing: jump straight into the SD firmware picker.
activityManager.replaceActivity(
@@ -401,6 +449,7 @@ void setup() {
// Ensure we're not still holding the power button before leaving setup
waitForPowerRelease();
allowSleepAt = millis() + 2000;
}
void loop() {
@@ -471,12 +520,13 @@ void loop() {
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
if (millis() - lastActivityTime >= sleepTimeoutMs) {
LOG_DBG("SLP", "Auto-sleep triggered after %lu ms of inactivity", sleepTimeoutMs);
enterDeepSleep();
enterDeepSleep(true);
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
return;
}
if (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getPowerButtonHeldTime() > SETTINGS.getPowerButtonDuration()) {
if (millis() >= allowSleepAt && gpio.isPressed(HalGPIO::BTN_POWER) &&
gpio.getPowerButtonHeldTime() > SETTINGS.getPowerButtonDuration()) {
// If the screenshot combination is potentially being pressed, don't sleep
if (gpio.isPressed(HalGPIO::BTN_DOWN)) {
return;