Repository navigation
Fix missed Xteink navigation during rapid taps and redraws - #446
Merged
bmorcelli merged 6 commits intoOct 15, 2026
Merged
Conversation
LittleBidet
force-pushed
the
feat/xteink-rapid-button-input
branch
from
October 9, 2026 10:31
ba99796 to
eb0c47f
Compare
LittleBidet
force-pushed
the
feat/xteink-rapid-button-input
branch
from
October 9, 2026 16:58
eb0c47f to
b8f817b
Compare
Since the per-ladder edge detection landed, a different decoded button counted as a new press on a single sample. Releasing Right or Down drags the ADC node back up through the other buttons' bands on its way to idle, so one transient sample could fire a stray Prev/Sel/Esc/Up. The old 200 ms blanking happened to hide this; 10 ms sampling exposes it. Require the same decode on two consecutive samples before it can start a press. A stable idle rearms the ladder, which also covers the single idle-sample glitch mid-hold that the separate 20 ms release rearm was for, so that state goes away (13 statics -> 9). Costs one 10 ms sample of latency on the first press. Power gets the same LongPress bypass as the ladders.
taskInputHandler wiped every flag with resetGlobals() at the top of each poll cycle, so an event the UI had not read yet lived at most ~75 ms. On LCD boards loopTask is back at check() within milliseconds, so that never showed. On e-paper, loopTask is blocked in the panel refresh for hundreds of ms after each click, and a tap landing in that window was raised, wiped, and never seen - the "second press does nothing" complaint on the Xteink X3. Replace the direction-only replay latch from the previous commit with a simpler rule in the task loop, gated on E_PAPER_DISPLAY: while AnyKeyPress is still set (check() clears it) and LongPress is not, skip the wipe of the six button flags and only clear touch/keystroke state. Bounded at 1500 ms so an event nobody reads cannot ghost into a later screen. Not while LongPress: display.cpp, mykeyboard.cpp and launcherSelectHeld() poll the raw flags and need them to drop on release. Covers Sel and Esc too (navigate-then-confirm during a refresh was still lost), needs no replay step and therefore no mutex inside check() and resetGlobals(), and drops the board-specific XTEINK_NAV_INPUT_LATCH flag; globals.h is back to its previous form. Built xteink-x3-x4 and lilygo-t-deck-pro; xteink-x3-x4 is 464 bytes smaller than before.
…Press Two holes in the previous commit's retention rule: check() clears AnyKeyPress on the first flag it consumes, and the rule was gated on AnyKeyPress. With two flags unread (nav + page in one poll, or a tap on each side of a refresh), consuming the first made the next input cycle see AnyKeyPress == false, take the resetGlobals() path and wipe the second. Judge pending on the six flags themselves. Flags carry no order. A press stacked on an unread one was applied in whatever order the UI polls them: main menu and loopOptions both handle the move before Sel, so confirm-then-move during a refresh ran the newly highlighted row, and Right-then-Left cancelled. Hold one unread event at a time: while a flag is pending, clear whatever this poll newly raised, which is what every poll did before retention. Only newly raised flags are cleared, never restored, so a press check() consumed while InputHandler was running is not put back and cannot double-step. This also stops a latched press riding alongside a later Esc chord on hal_buttons_poll_3 (reTerminal E1001/Sticky). Handlers that assign false while idle still drop their flags, so those boards keep their pre-retention behaviour. Built xteink-x3-x4 (1,376,576 bytes), seeedstudio-reterminal-e1001 and lilygo-t-deck-pro.
Measured on an X3 with a ladder trace at the 10 ms poll: Confirm and Back repeated at 200 ms like the navigation buttons. Before the e-paper input task retained unread flags that second Sel was wiped within 75 ms; now it survives into whatever menu the first Confirm opened and selects its first row. A half-second Confirm produced two Sel events in the trace. Only Left/Right/Up/Down repeat now. Ordinary taps ran up to ~240 ms, so a first repeat at 200 ms double-stepped 2 of 20 taps. A hold now starts repeating after 400 ms (first repeat landed at exactly 400 ms on hardware), then every 200 ms. LongPress still reports live state every poll. Also confirmed on hardware with the same trace: 0 stray flags over 61 press runs on both ladders, 0 release sweeps with an intermediate band held for two consecutive samples (one Right release showed a single Back-band sample at 3854, rejected as intended), clean release with no trailing event, ladders independent while one is held. Band centres on this unit: Back 3564, Confirm 2727, Left 1500, Right 0; page 2267 / 5.
LittleBidet
marked this pull request as ready for review
October 13, 2026 22:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Quick Xteink X3/X4 navigation taps can be missed for two reasons: the board handler stopped sampling for 200 ms after any press, and the shared input task wiped every unread flag after about 75 ms, even while loopTask was blocked in an e-paper panel refresh. A tap landing in that window was raised, wiped, and never seen — the "second press does nothing" report on the X3.
Board handler (
boards/xteink-x3-x4/interface.cpp). Read both ladders and the power button on every poll. A rung counts once two consecutive samples agree: releasing Right or Down sweeps the ADC node up through the other bands on its way to idle, and a single transient sample must not fire a stray Prev/Sel/Esc/Up, nor end a hold. A stable idle rearms the ladder; a stable different button is a new press. Each ladder repeats independently at the existing 200 ms interval;LongPressbypasses the gate as before. Costs one 10 ms sample of latency on the first press.Shared input task (
src/main.cpp,E_PAPER_DISPLAYonly). While any of the six button flags is unread andLongPressis not set, skip the per-cycle wipe of those flags; touch and keystroke state are still cleared each cycle. Pending is judged on the flags themselves, notAnyKeyPress, becausecheck()dropsAnyKeyPresson the first flag it consumes and the others have to survive that. Only one unread event is held at a time: the flags carry no order, so a press stacked on an unread one would be applied in whatever order the UI polls them (move-then-confirm when the user did confirm-then-move). While something is pending, whatever a poll newly raises is cleared, as every poll did before; a flag the handler cleared is never restored, so a presscheck()consumed meanwhile cannot be put back. Bounded at 1500 ms so an event nobody reads cannot ghost into a later screen. Handlers that assignfalsewhile idle (hal_buttons_poll_3, T-Deck Pro) still drop their flags, so those boards keep their previous behaviour. Not whileLongPress:display.cpp,mykeyboard.cppandlauncherSelectHeld()poll the raw flags and need them to drop on release. LCD boards are unchanged.Main menu. On e-paper boards, consume Back/Escape as a no-op on the main menu. Otherwise its unread flag occupies the retained event slot and discards navigation or Confirm presses for up to 1500 ms.
Board handler, measured. A hold repeats only after 400 ms (then every 200 ms), and only for Left/Right/Up/Down. Confirm and Back fire once per press; their 200 ms repeat used to be wiped before the UI saw it, but with retention a half-second Confirm produced a second
Selthat landed in the menu the first one opened. Ordinary taps measured up to ~240 ms, so a 200 ms first repeat double-stepped 2 of 20 taps. Main menu consumes an unread Escape so it cannot block navigation for the retention window.Validation.
pio run -e xteink-x3-x4passes (application image 1,376,688 bytes);seeedstudio-reterminal-e1001andlilygo-t-deck-proalso build. A temporary host harness mirroring the task-loop block covered retention across 75 ms polls, a second flag surviving the firstcheck(), confirm-then-move dropping the move, acheck()racingInputHandler()not restoring the consumed press, the 1500 ms cap,LongPresslive state, idle-assigning handlers, and timer wrap.On an X3 (UC8253 panel), with a temporary trace printing both ladder ADCs on every 10 ms poll and every raised flag, over two runs and 112 press runs: correct button on every press, 0 stray flags on release, 0 release sweeps with an intermediate band held for two consecutive samples (one Right release showed a single Back-band sample at 3854, rejected as intended), hold-Right repeats at 400 ms then 200–240 ms with a clean release and no trailing event, a page tap registered once while Right kept repeating, a half-second Confirm produced exactly one
Sel, 21 taps at normal pace produced 21 events. Band centres on this unit: Back 3564, Confirm 2727, Left 1500, Right 0; page 2267 and 5. A press landing during the new screen's first refresh is applied to that screen.git diff --checkpasses. No test framework is added.Note for a separate change: this env does not define
ARDUINO_USB_CDC_ON_BOOT, soSerialis UART0 on GPIO20/21 (I2C SDA and panel CS here) and the console is unreachable over the X3's USB port. The trace above was captured with that flag set in the environment, not in the repo.Draft. Remaining UI-level checks are listed in the review thread. Known limits: a second press while one is unread is dropped (same as before this PR); multiple taps on one direction during a refresh coalesce; taps that fall entirely between polls, refreshes longer than 1500 ms, and the explicit
resetGlobals()after some redraws can still discard navigation.