Buffered LVGL Input: Preserve Press and Release Events Between UI Polls

A latest-state input callback can miss a complete tap that begins and ends between reads. Preserve ordered press and release records, return one record per callback, and request another read while the permitted batch still has data. When the queue is empty, report the last admitted state rather than inventing a release.

Applicability: this design is pinned to LVGL v9.3.0, a single pointer device in timer mode, and an application-owned bounded queue. It excludes hardware debouncing, coordinate calibration and multitouch. The queue contract and traces are proposed tests; no target driver was executed. Event-driven LVGL mode and historical timing require the separate treatment below.

A queued press and release are admitted in two timer-mode reads, followed by an empty read retaining the last state; a separate box records missing timestamp and event-mode boundaries.
Hypothetical input trace. Capture times illustrate a missed-tap window; queued delivery does not recreate historical hold duration in the pinned release.

1. Capture transitions instead of only the newest state

Imagine a hypothetical reader that runs at 0 and 20 milliseconds. The finger presses at 6 milliseconds and releases at 12 milliseconds at the same eligible button. A callback that samples only the current released state at 20 milliseconds has no evidence that the tap occurred. Faster polling reduces this window but does not define what happens during a delayed UI iteration.

Publish coherent records containing sequence number, pressed/released state and coordinates. A capture timestamp is useful for queue-age diagnostics even when the receiving API cannot consume it. Keep records in arrival order. Do not collapse a press followed by release into a single newest-state record.

Movement coalescing is a separate product decision. Removing intermediate points may change drag, gesture or path behavior. Begin with an uncoalesced fixture, then qualify any optimization against the interactions actually supported. Preserve the state transitions and their associated points even if a later design permits dropping redundant stationary samples.

2. Define the callback as a small state machine

The 9.3 input-device guide describes buffered reading through continue_reading. The exact v9.3.0 input header defines the callback’s state, point and continuation fields. Initialize the driver’s last-admitted state to released and its point to a valid known value before enabling input.

Callback condition Returned data Continuation
Queued record available Pop one; save and return its state and point True only if more work is admitted in this batch
Queue empty Return last admitted state and point False
Batch budget reached Return the record already popped False; schedule remaining work later

The empty case matters during a sustained press. If no new hardware sample arrives, returning released merely because the queue is empty synthesizes a finger lift. Conversely, a lost release can leave the last admitted state pressed indefinitely. The queue contract must address both cases rather than relying on a default-initialized structure.

Use an appropriate producer/consumer primitive to publish each record atomically. A volatile index alone is not a queue protocol. The input callback should not block waiting for another sample, allocate an unbounded list, or run slow device transactions while the UI is expecting a quick read.

3. Bound draining without losing order

In v9.3.0 lv_indev_read, each returned sample is processed before another callback invocation, and continuation repeats the loop in non-event mode. A continuously replenished queue can therefore keep the read loop busy. Define a bounded batch policy rather than asserting continuation forever.

One proposed design snapshots the admitted record count at the start of a UI-owned read batch and caps it to an application budget. Each callback reduces that allowance. New arrivals remain queued for the next scheduled batch. Maintain an explicit next-service mechanism; merely setting continuation false does not explain when the remainder will be consumed.

Choose queue capacity from the producer burst and the longest permitted service interruption, then verify the assumption under target load. Record high-water mark, oldest record age and overflow count. A large queue can preserve many stale interactions that are no longer appropriate for the displayed screen, so capacity alone is not a latency policy.

4. Do not infer historical timing from ordered delivery

The published 9.3 guide also describes a data->timestamp field. That statement does not match the v9.3.0 header inspected here: lv_indev_data_t has no timestamp member. The pinned implementation records press and repeat timing using current LVGL ticks. Follow the exact tagged API rather than adding the documented field to code that cannot compile against it.

Consequently, draining an old press and release immediately can preserve their order while compressing the interval LVGL observes. It does not prove faithful long-press recognition, click-streak timing or motion timing. Keep capture timestamps as driver diagnostics and classify excessive queue age as a degraded-input condition.

If faithful historical timing is required, qualify a release and API that actually support it, or design a dedicated replay harness with controlled clock advancement. Do not advance the production system’s global tick for each queued sample. That would affect timers and animations beyond the input device.

5. Give overflow an explicit recovery contract

Silent drop-oldest behavior can remove the only release separating two presses. Silent drop-newest can lose the final release. Detect sequence gaps and overflow as faults in the interaction stream, not ordinary empty reads.

A proposed conservative recovery is to invalidate the interrupted interaction, clear ambiguous backlog under the queue’s synchronization rules, and wait for an authoritative released state before admitting a fresh press. Coordinate that policy with the UI owner so cancellation cannot become an unintended click. The exact reset and suppression path needs a separate integration test; no generic queue operation alone guarantees safe cancellation.

6. Replay the admission boundary

  1. Queue the hypothetical 6-millisecond press and 12-millisecond release before the next read. Assert their ordered admission and one eligible short-tap action in the simple fixture.
  2. Admit a press, then make several empty reads while the physical state remains pressed. Assert that the callback retains pressed state until a real release is admitted.
  3. Queue two complete taps and verify four ordered transitions. Do not require historical double-click timing from immediate draining.
  4. Refill the queue while draining. Verify bounded callback count per batch, scheduled continuation later and preserved sequence order.
  5. Force overflow at each transition boundary. Verify fault visibility, cancellation policy and acceptance of a subsequent fresh interaction.

Finally repeat in LV_INDEV_MODE_EVENT as a deliberate negative control: the pinned API ignores continue_reading in that mode. Its scheduler must explicitly arrange subsequent serialized reads; switching modes does not inherit the timer-mode drain loop. Save queue traces, admitted samples, callback counts, action counters and the exact tag. Accept the driver when transition preservation and latency limitations are both visible.

Similar Posts