diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index e531f1da..a528db9f 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -18,14 +18,18 @@ void HalDisplay::begin(bool seamless) { einkDisplay.begin(); + if (seamless) { + // Defuse the SDK's X3 _x3InitialFullSyncsRemaining counter (no-op on X4) + // so the first paint isn't promoted to FULL (~770ms). Skips the wakeup- + // gated requestResync() below for the same reason. + einkDisplay.skipInitialResync(); + return; + } // 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(); - } + const auto wakeupReason = gpio.getWakeupReason(); + if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash || + wakeupReason == HalGPIO::WakeupReason::Other) { + einkDisplay.requestResync(); } } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index 4d51bdda..3d43126a 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -17,7 +17,12 @@ class HalDisplay { FAST_REFRESH // Fast refresh using custom LUT }; - // Initialize the display hardware and driver + // Pass seamless=true on any path where the panel already shows the + // content it should after begin() returns (silent reboot's popup, + // sleep-wake with a restored buffer). Skips the wakeup-gated + // requestResync() and defuses the SDK's X3 _x3InitialFullSyncsRemaining + // counter; otherwise the first two paints get promoted to FULL + // (~770ms each on X3). void begin(bool seamless = false); // Display dimensions diff --git a/open-x4-sdk b/open-x4-sdk index 4d93e09f..ff444b0f 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 4d93e09f73a8cccfd63898c11086841eba5df362 +Subproject commit ff444b0f2c9211341e8a4a6c9d8d9c6bac03f3e8 diff --git a/src/main.cpp b/src/main.cpp index 6ee382a4..2a93a1cd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -146,6 +146,11 @@ void silentRestart() { silentRebootTarget = SILENT_REBOOT_TARGET_HOME; silentRebootMagic = SILENT_REBOOT_MAGIC; LOG_DBG("MAIN", "Silent restart (target=home)"); + // E-ink retains the previous frame until Home's first paint lands (~2-3s). + // Without an overlay, users don't see the reboot and fire input through to + // Home. Select on the default selectorIndex=0 then opens the most-recent + // book, looking like a trampoline back to the reader they just exited. + GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); delay(50); ESP.restart(); } @@ -154,6 +159,7 @@ void silentRestartToReader() { silentRebootTarget = SILENT_REBOOT_TARGET_READER; silentRebootMagic = SILENT_REBOOT_MAGIC; LOG_DBG("MAIN", "Silent restart (target=reader)"); + GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); delay(50); ESP.restart(); } @@ -336,7 +342,7 @@ void setup() { // We need 6 open files concurrently when parsing a new chapter if (!Storage.begin()) { LOG_ERR("MAIN", "SD card initialization failed"); - setupDisplayAndFonts(); + setupDisplayAndFonts(isSilentReboot); activityManager.goToFullScreenMessage("SD card error", EpdFontFamily::BOLD); return; } @@ -393,10 +399,11 @@ 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(/*seamless=*/!APP_STATE.showBootScreen); + setupDisplayAndFonts(isSilentReboot || /*seamless=*/!APP_STATE.showBootScreen); - // First paint after silent reboot is HALF_REFRESH (SDK forces it after begin()'s - // panel reset); subsequent paints FAST. + // 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(); @@ -443,6 +450,23 @@ void setup() { activityManager.goToReader(path); } + if (isSilentReboot) { + // 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() + // during boot dispatches against an invisible Home and the default + // selectorIndex=0 opens the most-recent book. + activityManager.requestUpdateAndWait(); + // Absorb any button held at this point into currentState as a non-edge: + // two gpio.update() calls separated by > InputManager's 5ms debounce + // transition the held bit through lastDebounceTime into currentState + // without setting pressedEvents, so the first loop()'s own gpio.update() + // sees state == currentState and emits nothing. + gpio.update(); + delay(10); + gpio.update(); + } + // Ensure we're not still holding the power button before leaving setup waitForPowerRelease(); allowSleepAt = millis() + 2000;