From 93669ff49264b199308821b38418ac314f9e6d9b Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 7 Apr 2026 10:13:02 +0200 Subject: [PATCH 1/4] Treat .sleep and sleep as equal --- lib/I18n/translations/english.yaml | 3 + src/CrossPointSettings.h | 3 + src/SettingsList.h | 2 + src/activities/boot_sleep/SleepActivity.cpp | 219 +++++++++----------- 4 files changed, 103 insertions(+), 124 deletions(-) diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 67f65d55..109cd5ba 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -425,6 +425,9 @@ STR_AUTHOR: "Author" STR_SERIES: "Series" STR_FILE_SIZE: "Size" STR_SLEEP_COVER_OVERLAY: "Sleep Screen Info Overlay" +STR_SLEEP_IMAGE_PICK_MODE: "Sleep Image Selection" +STR_RANDOM: "Random" +STR_SEQUENTIAL: "Sequential" STR_OVERLAY_WHITE: "White" STR_OVERLAY_GRAY: "Gray" STR_OVERLAY_BLACK: "Black" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 94d110c1..7b192ac0 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -28,6 +28,7 @@ class CrossPointSettings { SLEEP_SCREEN_MODE_COUNT }; enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT }; + enum SLEEP_IMAGE_PICK_MODE { PICK_RANDOM = 0, PICK_SEQUENTIAL = 1, SLEEP_IMAGE_PICK_MODE_COUNT }; enum SLEEP_SCREEN_COVER_FILTER { NO_FILTER = 0, BLACK_AND_WHITE = 1, @@ -177,6 +178,8 @@ class CrossPointSettings { uint8_t sleepScreenCoverFilter = NO_FILTER; // Apply information overlay with reading progress on sleep cover uint8_t sleepCoverOverlay = 0; + // Sleep image pick mode (random vs sequential walk-through) + uint8_t sleepImagePickMode = PICK_RANDOM; // Status bar settings (statusBar retained for migration only) uint8_t statusBar = FULL; uint8_t statusBarChapterPageCount = 1; diff --git a/src/SettingsList.h b/src/SettingsList.h index c8a41249..753b6e4c 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -27,6 +27,8 @@ inline const std::vector& getSettingsList() { StrId::STR_SLEEP_COVER_OVERLAY, &CrossPointSettings::sleepCoverOverlay, {StrId::STR_OVERLAY_OFF, StrId::STR_OVERLAY_WHITE, StrId::STR_OVERLAY_GRAY, StrId::STR_OVERLAY_BLACK}, "sleepCoverOverlay", StrId::STR_CAT_DISPLAY), + SettingInfo::Enum(StrId::STR_SLEEP_IMAGE_PICK_MODE, &CrossPointSettings::sleepImagePickMode, + {StrId::STR_RANDOM, StrId::STR_SEQUENTIAL}, "sleepImagePickMode", 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), diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index d5b4a440..05808bb6 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "../reader/EpubReaderActivity.h" @@ -158,6 +159,70 @@ int pngOverlayDraw(PNGDRAW* pDraw) { return 1; } +// Collects full paths of valid image files from /.sleep and /sleep, with no preference between +// the two directories. BMP files are validated by parsing their headers; invalid BMPs are skipped. +// When allowPng is true, .png files are also accepted (PNG validation happens later at decode time). +std::vector collectSleepImages(bool allowPng) { + std::vector files; + for (const char* sleepDir : {"/.sleep", "/sleep"}) { + auto dir = Storage.open(sleepDir); + if (!dir || !dir.isDirectory()) { + if (dir) dir.close(); + continue; + } + char name[500]; + for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { + if (file.isDirectory()) { + file.close(); + continue; + } + file.getName(name, sizeof(name)); + auto filename = std::string(name); + if (filename[0] == '.') { + file.close(); + continue; + } + const bool isBmp = FsHelpers::hasBmpExtension(filename); + const bool isPng = allowPng && FsHelpers::hasPngExtension(filename); + if (!isBmp && !isPng) { + file.close(); + continue; + } + if (isBmp) { + Bitmap bmp(file); + if (bmp.parseHeaders() != BmpReaderError::Ok) { + LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); + file.close(); + continue; + } + } + files.emplace_back(std::string(sleepDir) + "/" + filename); + file.close(); + } + dir.close(); + } + // Sort by full path so the order is deterministic across reboots — required for sequential + // pick mode, harmless for random pick mode. + std::sort(files.begin(), files.end()); + return files; +} + +// Picks the next file index based on the user's pick mode. +// RANDOM: uniform random with single reroll to avoid immediate repeats. +// SEQUENTIAL: advances from APP_STATE.lastSleepImage, wrapping at numFiles. +size_t pickSleepImageIndex(size_t numFiles) { + if (SETTINGS.sleepImagePickMode == CrossPointSettings::SLEEP_IMAGE_PICK_MODE::PICK_SEQUENTIAL) { + const uint8_t last = APP_STATE.lastSleepImage; + if (last == UINT8_MAX || last >= numFiles) return 0; + return (static_cast(last) + 1) % numFiles; + } + size_t idx = random(numFiles); + while (numFiles > 1 && APP_STATE.lastSleepImage != UINT8_MAX && idx == APP_STATE.lastSleepImage) { + idx = random(numFiles); + } + return idx; +} + } // namespace void SleepActivity::onEnter() { @@ -202,78 +267,29 @@ void SleepActivity::renderCustomSleepScreen() const { explicitSleepFile.close(); } - // Check if we have a /.sleep (preferred) or /sleep directory - const char* sleepDir = nullptr; - auto dir = Storage.open("/.sleep"); - if (dir && dir.isDirectory()) { - sleepDir = "/.sleep"; - } else { - if (dir) dir.close(); - dir = Storage.open("/sleep"); - if (dir && dir.isDirectory()) { - sleepDir = "/sleep"; - } - } - - if (sleepDir) { - std::vector files; - char name[500]; - // collect all valid BMP files - for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { - if (file.isDirectory()) { + // Collect valid BMP files from both /.sleep and /sleep directories (no preference between them) + const auto files = collectSleepImages(/*allowPng=*/false); + const auto numFiles = files.size(); + if (numFiles > 0) { + const auto pickedIndex = pickSleepImageIndex(numFiles); + APP_STATE.lastSleepImage = pickedIndex; + APP_STATE.saveToFile(); + const auto& filename = files[pickedIndex]; + FsFile file; + if (Storage.openFileForRead("SLP", filename, file)) { + LOG_DBG("SLP", "Loading sleep image: %s", filename.c_str()); + delay(100); + Bitmap bitmap(file, true); + if (bitmap.parseHeaders() == BmpReaderError::Ok) { + const BookOverlayInfo resolvedOverlayInfo = + shouldLoadOverlayInfo ? getBookOverlayInfo(APP_STATE.openEpubPath) : overlayInfo; + renderBitmapSleepScreen(bitmap, resolvedOverlayInfo); file.close(); - continue; + return; } - file.getName(name, sizeof(name)); - auto filename = std::string(name); - if (filename[0] == '.') { - file.close(); - continue; - } - - if (!FsHelpers::hasBmpExtension(filename)) { - LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name); - file.close(); - continue; - } - Bitmap bitmap(file); - if (bitmap.parseHeaders() != BmpReaderError::Ok) { - LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); - file.close(); - continue; - } - files.emplace_back(filename); file.close(); } - const auto numFiles = files.size(); - if (numFiles > 0) { - // Generate a random number between 1 and numFiles - auto randomFileIndex = random(numFiles); - // If we picked the same image as last time, reroll - while (numFiles > 1 && APP_STATE.lastSleepImage != UINT8_MAX && randomFileIndex == APP_STATE.lastSleepImage) { - randomFileIndex = random(numFiles); - } - APP_STATE.lastSleepImage = randomFileIndex; - APP_STATE.saveToFile(); - const auto filename = std::string(sleepDir) + "/" + files[randomFileIndex]; - FsFile file; - if (Storage.openFileForRead("SLP", filename, file)) { - LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str()); - delay(100); - Bitmap bitmap(file, true); - if (bitmap.parseHeaders() == BmpReaderError::Ok) { - const BookOverlayInfo resolvedOverlayInfo = - shouldLoadOverlayInfo ? getBookOverlayInfo(APP_STATE.openEpubPath) : overlayInfo; - renderBitmapSleepScreen(bitmap, resolvedOverlayInfo); - file.close(); - dir.close(); - return; - } - file.close(); - } - } } - if (dir) dir.close(); renderDefaultSleepScreen(); } @@ -769,67 +785,22 @@ void SleepActivity::renderOverlaySleepScreen() const { return rc == PNG_SUCCESS; }; - // Try /.sleep/ (preferred) or /sleep/ directory (random selection, same as renderCustomSleepScreen). + // Collect images from both /.sleep and /sleep directories (no preference between them). // Accepts both .bmp and .png files; .bmp headers are validated during the scan. bool overlayDrawn = false; - const char* sleepDir = nullptr; - auto dir = Storage.open("/.sleep"); - if (dir && dir.isDirectory()) { - sleepDir = "/.sleep"; - } else { - if (dir) dir.close(); - dir = Storage.open("/sleep"); - if (dir && dir.isDirectory()) { - sleepDir = "/sleep"; + const auto files = collectSleepImages(/*allowPng=*/true); + const auto numFiles = files.size(); + if (numFiles > 0) { + const auto pickedIndex = pickSleepImageIndex(numFiles); + APP_STATE.lastSleepImage = pickedIndex; + APP_STATE.saveToFile(); + const std::string& selected = files[pickedIndex]; + if (FsHelpers::hasPngExtension(selected)) { + overlayDrawn = tryDrawPngOverlay(selected); + } else { + overlayDrawn = tryDrawOverlay(selected); } } - if (sleepDir) { - std::vector files; - char name[500]; - for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { - if (file.isDirectory()) { - file.close(); - continue; - } - file.getName(name, sizeof(name)); - auto filename = std::string(name); - if (filename[0] == '.') { - file.close(); - continue; - } - const bool isBmp = FsHelpers::checkFileExtension(filename, ".bmp"); - const bool isPng = FsHelpers::checkFileExtension(filename, ".png"); - if (!isBmp && !isPng) { - file.close(); - continue; - } - if (isBmp) { - Bitmap bmp(file); - if (bmp.parseHeaders() != BmpReaderError::Ok) { - file.close(); - continue; - } - } - files.emplace_back(filename); - file.close(); - } - const auto numFiles = files.size(); - if (numFiles > 0) { - auto randomFileIndex = random(numFiles); - while (numFiles > 1 && randomFileIndex == APP_STATE.lastSleepImage) { - randomFileIndex = random(numFiles); - } - APP_STATE.lastSleepImage = randomFileIndex; - APP_STATE.saveToFile(); - const std::string selected = std::string(sleepDir) + "/" + files[randomFileIndex]; - if (FsHelpers::checkFileExtension(selected, ".png")) { - overlayDrawn = tryDrawPngOverlay(selected); - } else { - overlayDrawn = tryDrawOverlay(selected); - } - } - } - if (dir) dir.close(); if (!overlayDrawn) { overlayDrawn = tryDrawOverlay("/sleep.bmp"); From e996afd760418a99d084b703b51066103ee4b2e7 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 7 Apr 2026 11:08:47 +0200 Subject: [PATCH 2/4] Address cppcheck complaints --- src/components/themes/lyra/LyraTheme.cpp | 4 ++-- src/components/themes/lyra/LyraTheme.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index ef2a8a1f..d8f454b2 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -241,7 +241,7 @@ int LyraTheme::getRecentBookProgressPercent(const RecentBook& book) { return -1; } -void LyraTheme::drawProgressBadge(GfxRenderer& renderer, Rect anchorRect, int progressPercent) { +void LyraTheme::drawProgressBadge(const GfxRenderer& renderer, Rect anchorRect, int progressPercent) { if (progressPercent < 0) { return; } @@ -640,7 +640,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: } drawProgressBadge( - renderer, + static_cast(renderer), Rect{tileX + hPaddingInSelection + coverWidth + LyraMetrics::values.verticalSpacing, tileY, tileWidth - 2 * hPaddingInSelection - coverWidth - LyraMetrics::values.verticalSpacing, tileHeight}, progressPercent); diff --git a/src/components/themes/lyra/LyraTheme.h b/src/components/themes/lyra/LyraTheme.h index 57bd5efb..c63e20d9 100644 --- a/src/components/themes/lyra/LyraTheme.h +++ b/src/components/themes/lyra/LyraTheme.h @@ -72,5 +72,5 @@ class LyraTheme : public BaseTheme { protected: static int getRecentBookProgressPercent(const RecentBook& book); - static void drawProgressBadge(GfxRenderer& renderer, Rect anchorRect, int progressPercent); + static void drawProgressBadge(const GfxRenderer& renderer, Rect anchorRect, int progressPercent); }; From 314e54e2522dfd6812d46e19c869733adb433ae0 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 7 Apr 2026 11:09:19 +0200 Subject: [PATCH 3/4] Here too --- src/components/themes/lyra/Lyra3CoversTheme.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/themes/lyra/Lyra3CoversTheme.cpp b/src/components/themes/lyra/Lyra3CoversTheme.cpp index b2ef5e44..5e8671bf 100644 --- a/src/components/themes/lyra/Lyra3CoversTheme.cpp +++ b/src/components/themes/lyra/Lyra3CoversTheme.cpp @@ -107,7 +107,7 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con cornerRadius, false, false, true, true, Color::LightGray); } - drawProgressBadge(renderer, + drawProgressBadge(static_cast(renderer), Rect{tileX + hPaddingInSelection, tileY + hPaddingInSelection, tileWidth - 2 * hPaddingInSelection, coverHeight}, progressPercent); From fb8196e946835508623c2404de23ff8afba1ed87 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 7 Apr 2026 11:12:07 +0200 Subject: [PATCH 4/4] Change index type --- src/CrossPointState.cpp | 2 +- src/CrossPointState.h | 2 +- src/JsonSettingsIO.cpp | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CrossPointState.cpp b/src/CrossPointState.cpp index af8bdda5..3d6c2b70 100644 --- a/src/CrossPointState.cpp +++ b/src/CrossPointState.cpp @@ -63,7 +63,7 @@ bool CrossPointState::loadFromBinaryFile() { if (version >= 2) { serialization::readPod(inputFile, lastSleepImage); } else { - lastSleepImage = UINT8_MAX; + lastSleepImage = SIZE_MAX; } if (version >= 3) { diff --git a/src/CrossPointState.h b/src/CrossPointState.h index 1de89838..9e7795b9 100644 --- a/src/CrossPointState.h +++ b/src/CrossPointState.h @@ -9,7 +9,7 @@ class CrossPointState { public: std::string openEpubPath; - uint8_t lastSleepImage = UINT8_MAX; // UINT8_MAX = unset sentinel + size_t lastSleepImage = SIZE_MAX; // SIZE_MAX = unset sentinel uint8_t readerActivityLoadCount = 0; bool lastSleepFromReader = false; ~CrossPointState() = default; diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index 11147884..bfa0e011 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -88,7 +88,7 @@ bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) { } s.openEpubPath = doc["openEpubPath"] | std::string(""); - s.lastSleepImage = doc["lastSleepImage"] | (uint8_t)UINT8_MAX; + s.lastSleepImage = doc["lastSleepImage"] | SIZE_MAX; s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0; s.lastSleepFromReader = doc["lastSleepFromReader"] | false; return true; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 05808bb6..f1ee331d 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -212,12 +212,12 @@ std::vector collectSleepImages(bool allowPng) { // SEQUENTIAL: advances from APP_STATE.lastSleepImage, wrapping at numFiles. size_t pickSleepImageIndex(size_t numFiles) { if (SETTINGS.sleepImagePickMode == CrossPointSettings::SLEEP_IMAGE_PICK_MODE::PICK_SEQUENTIAL) { - const uint8_t last = APP_STATE.lastSleepImage; - if (last == UINT8_MAX || last >= numFiles) return 0; - return (static_cast(last) + 1) % numFiles; + const size_t last = APP_STATE.lastSleepImage; + if (last == SIZE_MAX || last >= numFiles) return 0; + return (last + 1) % numFiles; } size_t idx = random(numFiles); - while (numFiles > 1 && APP_STATE.lastSleepImage != UINT8_MAX && idx == APP_STATE.lastSleepImage) { + while (numFiles > 1 && APP_STATE.lastSleepImage != SIZE_MAX && idx == APP_STATE.lastSleepImage) { idx = random(numFiles); } return idx;