diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 704319f4..55b4dbf2 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -47,6 +47,8 @@ class GfxRenderer { // recording to the (non-const) FontCacheManager. Same pragmatic compromise // as before, concentrated in a single pointer instead of four fields. mutable FontCacheManager* fontCacheManager_ = nullptr; + mutable bool useNextRefreshOverride = false; + mutable HalDisplay::RefreshMode nextRefreshOverride = HalDisplay::FAST_REFRESH; void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState, EpdFontFamily::Style style) const; @@ -92,6 +94,7 @@ class GfxRenderer { int getScreenWidth() const; int getScreenHeight() const; void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const; + void setNextDisplayRefreshMode(HalDisplay::RefreshMode refreshMode) const; // EXPERIMENTAL: Windowed update - display only a rectangular region // void displayWindow(int x, int y, int width, int height) const; void invertScreen() const; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 6f0eebfc..21ef9566 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -48,6 +48,10 @@ int clampPercent(int percent) { void EpubReaderActivity::onEnter() { Activity::onEnter(); + // Drop any input events that arrived from the activity that launched us (e.g. a wake-up power + // button hold) before they reach detectPageTurn() — see ReaderUtils::InputDrainGuard. + inputDrainGuard.arm(); + if (!epub) { return; } @@ -118,6 +122,10 @@ void EpubReaderActivity::loop() { return; } + if (inputDrainGuard.shouldDrain(mappedInput)) { + return; + } + if (automaticPageTurnActive) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || mappedInput.wasReleased(MappedInputManager::Button::Back)) { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index f284d228..ebe93e8f 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -6,6 +6,7 @@ #include #include "EpubReaderMenuActivity.h" +#include "ReaderUtils.h" #include "activities/Activity.h" class EpubReaderActivity final : public Activity { @@ -45,6 +46,7 @@ class EpubReaderActivity final : public Activity { uint16_t pendingParagraphIndex = 0; bool pendingScreenshot = false; bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit + ReaderUtils::InputDrainGuard inputDrainGuard; bool automaticPageTurnActive = false; std::string deferredSyncEpubPath; // -1 means use global SETTINGS value. diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index aa55bf7e..db874e05 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -29,6 +29,33 @@ inline void applyOrientation(GfxRenderer& renderer, const uint8_t orientation) { } } +// Suppresses input processing on activity entry until the user has released all buttons and a +// clean frame (no pending press/release events) has been observed. Without this, the power-button +// hold used to wake the device leaks into detectPageTurn() and triggers a page turn or, with +// longPressChapterSkip enabled, a chapter skip (the wake-hold easily exceeds skipChapterMs). +// Each reader holds an instance, calls arm() in onEnter(), and calls shouldDrain() at the top +// of loop() — returning early when it returns true. +struct InputDrainGuard { + bool active = false; + + void arm() { active = true; } + + bool shouldDrain(const MappedInputManager& input) { + if (!active) { + return false; + } + using B = MappedInputManager::Button; + const bool anyHeld = input.isPressed(B::Back) || input.isPressed(B::Confirm) || input.isPressed(B::Left) || + input.isPressed(B::Right) || input.isPressed(B::Up) || input.isPressed(B::Down) || + input.isPressed(B::Power) || input.isPressed(B::PageBack) || input.isPressed(B::PageForward); + if (anyHeld || input.wasAnyPressed() || input.wasAnyReleased()) { + return true; + } + active = false; + return false; + } +}; + struct PageTurnResult { bool prev; bool next; @@ -61,8 +88,9 @@ inline void displayWithRefreshCycle(const GfxRenderer& renderer, int& pagesUntil inline void enforceExitFullRefresh(const GfxRenderer& renderer) { // Reader exits can leave visible ghosting when the next screen is rendered with a fast LUT. - // Force one full waveform pass before leaving the reader stack. - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + // Schedule the next displayed screen to use a full refresh, rather than refreshing + // the current reader screen as it closes. + renderer.setNextDisplayRefreshMode(HalDisplay::FULL_REFRESH); } // Grayscale anti-aliasing pass. Renders content twice (LSB + MSB) to build diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index a62576ce..bef5ffaf 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -84,6 +84,10 @@ size_t parseAndWrapLines(const uint8_t* buffer, size_t chunkSize, size_t fileOff void TxtReaderActivity::onEnter() { Activity::onEnter(); + // See ReaderUtils::InputDrainGuard — prevents wake-up power-button hold from leaking into + // the first detectPageTurn() call as a page turn or chapter skip. + inputDrainGuard.arm(); + if (!txt) { return; } @@ -117,6 +121,10 @@ void TxtReaderActivity::onExit() { } void TxtReaderActivity::loop() { + if (inputDrainGuard.shouldDrain(mappedInput)) { + return; + } + // Long press BACK (1s+) goes to home screen if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= ReaderUtils::GO_HOME_MS) { ReaderUtils::enforceExitFullRefresh(renderer); diff --git a/src/activities/reader/TxtReaderActivity.h b/src/activities/reader/TxtReaderActivity.h index 21e20c48..bef54ce4 100644 --- a/src/activities/reader/TxtReaderActivity.h +++ b/src/activities/reader/TxtReaderActivity.h @@ -5,6 +5,7 @@ #include #include "CrossPointSettings.h" +#include "ReaderUtils.h" #include "activities/Activity.h" class TxtReaderActivity final : public Activity { @@ -13,6 +14,7 @@ class TxtReaderActivity final : public Activity { int currentPage = 0; int totalPages = 1; int pagesUntilFullRefresh = 0; + ReaderUtils::InputDrainGuard inputDrainGuard; // Streaming text reader - stores file offsets for each page std::vector pageOffsets; // File offset for start of each page diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index d4f64a3a..80fa9a5f 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -29,6 +29,10 @@ constexpr unsigned long goHomeMs = 1000; void XtcReaderActivity::onEnter() { Activity::onEnter(); + // See ReaderUtils::InputDrainGuard — prevents wake-up power-button hold from leaking into + // the first detectPageTurn() call as a page turn or chapter skip. + inputDrainGuard.arm(); + if (!xtc) { return; } @@ -58,6 +62,10 @@ void XtcReaderActivity::onExit() { } void XtcReaderActivity::loop() { + if (inputDrainGuard.shouldDrain(mappedInput)) { + return; + } + // Enter chapter selection activity if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) { diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 85bd973d..46fcabf1 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -9,6 +9,7 @@ #include +#include "ReaderUtils.h" #include "activities/Activity.h" class XtcReaderActivity final : public Activity { @@ -16,6 +17,7 @@ class XtcReaderActivity final : public Activity { uint32_t currentPage = 0; int pagesUntilFullRefresh = 0; + ReaderUtils::InputDrainGuard inputDrainGuard; void renderPage(); void saveProgress() const; diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index cf47a96d..38ea7f9c 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -123,8 +123,7 @@ void BmpViewerActivity::onExit() { saveDitherSettingsIfNeeded(); #endif Activity::onExit(); - renderer.clearScreen(); - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + ReaderUtils::enforceExitFullRefresh(renderer); } bool BmpViewerActivity::renderBmpImage(const bool showControls) {