Files
wadamesh/test/test_screen_idle_clock.cpp
Kaj SchittecatandClaude Opus 5.5 7a9bad7236 touch: the screen timeout waits for the boot splash, a T-Deck Pro lock never keeps the logo (#762)
begin() stamps the last input when it starts building the UI. A boot that
takes longer than the screen timeout (30 s by default; a full chat history on
internal flash with no SD card can do it) timed out on the first loop pass,
with the boot splash still covering the screen. On the T-Deck Pro and Max the
timeout is a lock, and the lock paints its frame and powers the e-paper down:
the splash sits on lv_layer_sys above the lock view, so the frame it kept was
the logo, and nothing redrew the panel afterwards. The device ran on (ui ready,
splash done) while the glass stayed on the logo, on every boot.

The idle clock now stands still while the splash is up (ScreenIdleClock.h,
host-tested), so the timeout counts from when the UI is shown. And the Pro's
lock takes the splash down before it paints, so a lock during the boot (the
BOOT button) shows the lock view, not the logo.

Reproduced in the host build with a 31 s begin(): before, the timeout fired on
the first pass with the splash up; after, 30 s after "splash done".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:18:02 +01:00

88 lines
3.8 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
//
// Host test for the screen timeout's idle clock (#762): src/ui-touch/ScreenIdleClock.h,
// the rule UITask::loop applies before it dims (or, on the T-Deck Pro, locks) the screen.
//
// c++ -std=c++17 -Wall -Wextra -I src test/test_screen_idle_clock.cpp \
// -o /tmp/test_screen_idle_clock && /tmp/test_screen_idle_clock
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include "ui-touch/ScreenIdleClock.h"
using namespace ScreenIdleClock;
// One boot, as #762's log reads it (ms since the UI's begin() stamped the last
// input): the history load and the rest of the build take ~30 s, "ui ready", the
// first loop pass, then the splash's one still frame until "splash done" 8.3 s after
// it was built. Loop passes every 20 ms. Returns the first pass the timeout fires on
// (0 = never before t_end). `hold` = the fix.
struct Boot {
uint32_t stamp = 0; // begin(): _last_input_ms = millis()
uint32_t first_pass = 31000; // first UITask::loop pass, splash just built
uint32_t splash_end = 39300; // "[BOOT] splash done"
};
static uint32_t firstTimeout(const Boot& b, uint32_t timeout_ms, bool hold, uint32_t t_end,
uint32_t input_at = 0) {
unsigned long last_input = b.stamp; // the member is unsigned long (64-bit on the host)
for (uint32_t now = b.first_pass; now <= t_end; now += 20) {
if (input_at && now == input_at) last_input = now; // a key press
if (hold) holdWhileSplash(last_input, now, now < b.splash_end);
if (due(now, (uint32_t)last_input, timeout_ms)) return now;
}
return 0;
}
int main() {
const uint32_t kTimeout = 30000; // DEFAULT_SCREEN_TIMEOUT_S
Boot slow;
// ---- the bug: the boot counted as idle ----
// Before the fix the very first pass timed out, with the splash still on screen:
// on the Pro that is lockScreen() committing the splash frame to the e-paper.
assert(firstTimeout(slow, kTimeout, false, 120000) == slow.first_pass);
// ---- the fix: the clock starts when the splash goes ----
const uint32_t fired = firstTimeout(slow, kTimeout, true, 120000);
// (The clock starts from the last pass that still had the splash, one pass early.)
assert(fired + 20 >= slow.splash_end + kTimeout);
assert(fired < slow.splash_end + kTimeout + 40); // and on time, the next pass or so
// Never while the splash is up, however slow the boot.
{
Boot glacial;
glacial.first_pass = 95000; // a minute and a half to the first pass
glacial.splash_end = 110000; // the splash's own 15 s wall-clock cap
const uint32_t f = firstTimeout(glacial, kTimeout, true, 200000);
assert(f + 20 >= glacial.splash_end + kTimeout);
}
// ---- a fast boot: the full wait still runs from when the UI shows ----
{
Boot fast;
fast.first_pass = 3000;
fast.splash_end = 6000;
const uint32_t f = firstTimeout(fast, kTimeout, true, 120000);
assert(f + 20 >= fast.splash_end + kTimeout && f < fast.splash_end + kTimeout + 40);
}
// ---- input after the splash restarts the wait ----
{
const uint32_t key = slow.splash_end + 20000; // a key 20 s after the UI shows
const uint32_t f = firstTimeout(slow, kTimeout, true, 200000, key);
assert(f >= key + kTimeout && f < key + kTimeout + 40);
}
// ---- timeout "never" (0) never fires ----
assert(firstTimeout(slow, 0, true, 300000) == 0);
assert(firstTimeout(slow, 0, false, 300000) == 0);
// ---- the signed compare: an input stamped just after the pass's `now` ----
assert(!due(1000, 1005, kTimeout)); // 5 ms "in the future" = just now
assert(!due(0xFFFFFFF0u, 0x00000010u, kTimeout)); // across the millis() wrap
assert(due(0x00000010u + kTimeout, 0x00000010u, kTimeout));
printf("test_screen_idle_clock: all passed\n");
return 0;
}