1use super::ffi::{
5 ABS_CNT, AbsBits, AbsInfo, EV_ABS, EV_KEY, EVENT_SIZE, EVIOCGID_NR, EVIOCGKEY_NR, InputEvent,
6 InputId, KernelUlong, KeyBits, O_NONBLOCK, decode_events, evioc_read, eviocgabs_nr,
7 eviocgbit_nr, ioctl, ioctl_request,
8};
9use super::map::Probe;
10use super::{Device, Node, Poller, Source};
11use crate::GamepadEvent;
12use crate::evdev::EvdevError;
13use std::fs::{self, File, OpenOptions};
14use std::io::{self, Read};
15use std::os::fd::AsRawFd;
16use std::os::unix::fs::{DirEntryExt, OpenOptionsExt};
17use std::path::{Path, PathBuf};
18use std::time::Instant;
19
20const INPUT_DIR: &str = "/dev/input";
22
23const READ_BATCH: usize = 64;
25
26unsafe trait Plain {}
32unsafe impl Plain for InputId {}
34unsafe impl Plain for AbsInfo {}
36unsafe impl<const WORDS: usize> Plain for [KernelUlong; WORDS] {}
38
39fn read_ioctl<T: Plain>(file: &File, nr: u32, out: &mut T) -> io::Result<()> {
44 let request = ioctl_request(evioc_read(nr, size_of::<T>()));
45 let result = unsafe {
51 ioctl(
52 file.as_raw_fd(),
53 request,
54 core::ptr::from_mut(out).cast::<core::ffi::c_void>(),
55 )
56 };
57 if result < 0 {
58 Err(io::Error::last_os_error())
59 } else {
60 Ok(())
61 }
62}
63
64#[derive(Debug)]
66struct InputDir {
67 dir: PathBuf,
68}
69
70impl Source for InputDir {
71 type Device = EventNode;
72
73 fn scan(&mut self) -> io::Result<Vec<Node>> {
74 let mut nodes = Vec::new();
75 for entry in fs::read_dir(&self.dir)? {
76 let entry = entry?;
77 if entry.file_name().as_encoded_bytes().starts_with(b"event") {
78 nodes.push(Node {
79 path: entry.path(),
80 ino: entry.ino(),
81 });
82 }
83 }
84 nodes.sort();
85 Ok(nodes)
86 }
87
88 fn open(&mut self, path: &Path) -> io::Result<EventNode> {
89 let file = OpenOptions::new()
91 .read(true)
92 .custom_flags(O_NONBLOCK)
93 .open(path)?;
94 Ok(EventNode { file })
95 }
96}
97
98#[derive(Debug)]
100struct EventNode {
101 file: File,
102}
103
104impl Device for EventNode {
105 fn probe(&mut self) -> io::Result<Probe> {
106 let mut probe = Probe {
107 id: InputId::default(),
108 keys: KeyBits::EMPTY,
109 abs: AbsBits::EMPTY,
110 held: KeyBits::EMPTY,
111 axes: [AbsInfo::default(); ABS_CNT],
112 };
113 read_ioctl(&self.file, EVIOCGID_NR, &mut probe.id)?;
114 read_ioctl(&self.file, eviocgbit_nr(EV_KEY), &mut probe.keys.0)?;
115 read_ioctl(&self.file, eviocgbit_nr(EV_ABS), &mut probe.abs.0)?;
116 read_ioctl(&self.file, EVIOCGKEY_NR, &mut probe.held.0)?;
117 for code in (0..ABS_CNT as u16).filter(|&code| probe.abs.has(code)) {
118 read_ioctl(
119 &self.file,
120 eviocgabs_nr(code),
121 &mut probe.axes[usize::from(code)],
122 )?;
123 }
124 Ok(probe)
125 }
126
127 fn read(&mut self, events: &mut Vec<InputEvent>) -> io::Result<()> {
128 let mut buffer = [0; READ_BATCH * EVENT_SIZE];
129 loop {
130 match self.file.read(&mut buffer) {
131 Ok(read) => {
132 events.extend(decode_events(&buffer[..read])?);
133 if read < buffer.len() {
134 return Ok(());
135 }
136 }
137 Err(error) if error.kind() == io::ErrorKind::WouldBlock => return Ok(()),
138 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
139 Err(error) => return Err(error),
140 }
141 }
142 }
143}
144
145#[derive(Debug)]
147pub struct Evdev {
148 source: InputDir,
149 poller: Poller<EventNode>,
150}
151
152impl Evdev {
153 #[must_use]
156 pub fn new() -> Self {
157 Self {
158 source: InputDir {
159 dir: PathBuf::from(INPUT_DIR),
160 },
161 poller: Poller::default(),
162 }
163 }
164
165 pub fn poll(&mut self, mut emit: impl FnMut(GamepadEvent)) -> Result<(), EvdevError> {
175 self.poller
176 .poll(&mut self.source, Instant::now(), &mut emit)
177 }
178}
179
180impl Default for Evdev {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::evdev::ffi::{ABS_Y, EV_SYN, SYN_REPORT};
190
191 struct Scratch {
193 dir: PathBuf,
194 files: Vec<PathBuf>,
195 }
196
197 impl Scratch {
198 fn new(name: &str) -> Self {
199 let dir =
200 std::env::temp_dir().join(format!("crcbl-evdev-{name}-{}", std::process::id()));
201 fs::create_dir(&dir).expect("a fresh scratch dir");
202 Self {
203 dir,
204 files: Vec::new(),
205 }
206 }
207
208 fn file(&mut self, name: &str, bytes: &[u8]) -> PathBuf {
209 let path = self.dir.join(name);
210 fs::write(&path, bytes).expect("scratch file written");
211 self.files.push(path.clone());
212 path
213 }
214 }
215
216 impl Drop for Scratch {
217 fn drop(&mut self) {
218 for file in &self.files {
219 fs::remove_file(file).expect("scratch file removed");
220 }
221 fs::remove_dir(&self.dir).expect("scratch dir removed");
222 }
223 }
224
225 #[test]
228 fn a_scan_lists_only_event_nodes() {
229 let mut scratch = Scratch::new("scan");
230 let event1 = scratch.file("event1", b"");
231 scratch.file("js0", b"");
232 scratch.file("mouse0", b"");
233 let event0 = scratch.file("event0", b"");
234 let mut source = InputDir {
235 dir: scratch.dir.clone(),
236 };
237 let nodes = source.scan().expect("a readable dir");
238 let paths: Vec<_> = nodes.iter().map(|node| node.path.clone()).collect();
239 assert_eq!(paths, [event0, event1]);
240 assert_ne!(nodes[0].ino, nodes[1].ino);
241 }
242
243 #[test]
247 fn a_non_evdev_node_fails_its_probe_with_enotty() {
248 const ENOTTY: i32 = 25;
249 let mut scratch = Scratch::new("probe");
250 let path = scratch.file("event0", b"");
251 let mut source = InputDir {
252 dir: scratch.dir.clone(),
253 };
254 let mut node = source.open(&path).expect("a regular file opens");
255 let error = node.probe().expect_err("a regular file has no EVIOCGID");
256 assert_eq!(error.raw_os_error(), Some(ENOTTY), "{error}");
257
258 let mut events = Vec::new();
259 let result = Poller::default().poll(&mut source, Instant::now(), &mut |event| {
260 events.push(event);
261 });
262 assert!(
263 matches!(&result, Err(EvdevError::Open { path: failed, .. }) if *failed == path),
264 "{result:?}"
265 );
266 assert!(events.is_empty());
267 }
268
269 #[test]
272 fn a_read_decodes_whole_events() {
273 let mut bytes = Vec::new();
274 for event in [
275 InputEvent::new(EV_ABS, ABS_Y, 32767),
276 InputEvent::new(EV_SYN, SYN_REPORT, 0),
277 ] {
278 bytes.extend_from_slice(&[0; 2 * size_of::<KernelUlong>()]);
279 bytes.extend_from_slice(&event.kind.to_ne_bytes());
280 bytes.extend_from_slice(&event.code.to_ne_bytes());
281 bytes.extend_from_slice(&event.value.to_ne_bytes());
282 }
283 let mut scratch = Scratch::new("read");
284 let path = scratch.file("event0", &bytes);
285 let mut node = InputDir {
286 dir: scratch.dir.clone(),
287 }
288 .open(&path)
289 .expect("a regular file opens");
290 let mut events = Vec::new();
291 node.read(&mut events).expect("whole events");
292 assert_eq!(
293 events,
294 [
295 InputEvent::new(EV_ABS, ABS_Y, 32767),
296 InputEvent::new(EV_SYN, SYN_REPORT, 0),
297 ]
298 );
299 }
300}