consortium_hmi_input/source.rs
1// Copyright 2026 Ethan Wu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17//! The pull-based [`InputSource`] contract shared by every event source.
18
19use crate::InputEvent;
20
21/// A non-blocking, drain-style source of [`InputEvent`]s.
22///
23/// The contract is deliberately poll-based rather than async or
24/// callback-driven so one trait serves a bare-metal frame loop and a Linux
25/// host alike. A display/event-loop thread drains the source once per frame
26/// and folds the events into its backend, honoring the single-threaded UI
27/// rule: the UI guest is never called from a device callback or worker
28/// thread, only at a frame boundary.
29///
30/// Expected implementors:
31/// - **Linux**: a `libinput`/`evdev` reader thread pushing into a bounded
32/// channel; `poll_event` pops from the channel.
33/// - **Bare metal, local**: an `embedded-hal` touch/GPIO/encoder driver
34/// decoded into events on the same core.
35/// - **Bare metal, over IPC**: a Consortium IPC receiver whose payloads are
36/// codec-decoded [`InputEvent`]s produced by a real-time core.
37pub trait InputSource {
38 /// Returns the next pending event, or `None` when the source is drained
39 /// for this poll. Must not block.
40 fn poll_event(&mut self) -> Option<InputEvent>;
41}
42
43/// An [`InputSource`] that replays a fixed slice of events once.
44///
45/// Useful for deterministic tests and for scripted headless playback without
46/// requiring `alloc`.
47pub struct SliceSource<'a> {
48 remaining: core::slice::Iter<'a, InputEvent>,
49}
50
51impl<'a> SliceSource<'a> {
52 /// Creates a source that yields `events` in order, then `None`.
53 pub fn new(events: &'a [InputEvent]) -> Self {
54 Self {
55 remaining: events.iter(),
56 }
57 }
58}
59
60impl InputSource for SliceSource<'_> {
61 fn poll_event(&mut self) -> Option<InputEvent> {
62 self.remaining.next().copied()
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn slice_source_drains_in_order_then_ends() {
72 let events = [
73 InputEvent::Pointer { x: 1, y: 2 },
74 InputEvent::Key {
75 scancode: 5,
76 pressed: true,
77 },
78 ];
79 let mut source = SliceSource::new(&events);
80 assert_eq!(source.poll_event(), Some(events[0]));
81 assert_eq!(source.poll_event(), Some(events[1]));
82 assert_eq!(source.poll_event(), None);
83 assert_eq!(source.poll_event(), None);
84 }
85}