1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use alloc::collections::VecDeque;
use alloc::vec;
use alloc::vec::Vec;
use core::ffi::c_int;
use std::ffi::CString;
use std::io;
use libc::{O_NONBLOCK, O_RDONLY, close, open, read};
use super::ioctl::*;
use crate::input::event::input::InputEvent;
use crate::surface::input_state::{InputAxis, PointerState};
pub(super) struct TouchInput {
fd: c_int,
state: PointerState,
buf: Vec<u8>,
}
impl TouchInput {
pub(super) fn open(
path: &str,
width: u16,
height: u16,
off_x: u16,
off_y: u16,
) -> io::Result<Self> {
let cpath = CString::new(path)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "touch path contains NUL"))?;
// SAFETY: `cpath` is a valid NUL-terminated C string.
let fd = unsafe { open(cpath.as_ptr(), O_RDONLY | O_NONBLOCK) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
// Driver reports physical pixel coords; map the overscan'd view span
// [off, off+dim) onto the cropped [0, dim) the renderer expects.
let state = PointerState::new(
width,
height,
off_x as i32,
off_x as i32 + width as i32,
off_y as i32,
off_y as i32 + height as i32,
);
let buf =
vec![0u8; TOUCH_SAMPLE_HEADER + MAX_TOUCH_POINTS * core::mem::size_of::<TouchPoint>()];
Ok(Self { fd, state, buf })
}
pub(super) fn drain_into(&mut self, queue: &mut VecDeque<InputEvent>) {
loop {
// SAFETY: `self.buf.as_mut_ptr()` is a valid pointer to
// `self.buf.len()` bytes; kernel writes up to `len` bytes
// through it. Caller (mirui tick loop) is single-threaded.
let n = unsafe { read(self.fd, self.buf.as_mut_ptr().cast(), self.buf.len()) };
if n <= 0 {
return;
}
let n = n as usize;
if n < TOUCH_SAMPLE_HEADER {
// circbuf only releases whole samples; short read = driver oddity.
return;
}
// see ioctl::TOUCH_SAMPLE_HEADER for the npoints + padding layout.
let npoints = i32::from_ne_bytes(
self.buf[..4]
.try_into()
.expect("buf slice is 4 bytes by construction"),
) as usize;
let count = npoints.min(MAX_TOUCH_POINTS);
for i in 0..count {
let off = TOUCH_SAMPLE_HEADER + i * core::mem::size_of::<TouchPoint>();
if off + core::mem::size_of::<TouchPoint>() > n {
return;
}
// SAFETY: kernel wrote a `touch_point_s` at this offset;
// `read_unaligned` handles potentially-misaligned access
// (the buffer is `Vec<u8>`-aligned, not 8-aligned).
let p: TouchPoint = unsafe {
core::ptr::read_unaligned(self.buf.as_ptr().add(off) as *const TouchPoint)
};
self.feed(p, queue);
}
}
}
/// Axis order is load-bearing: `Slot` first (routes later axes to the
/// right contact), then position, then `TrackingId` (down/up edge),
/// finally `Sync` (flushes the slot's dirty bit into `PointerMove`).
fn feed(&mut self, p: TouchPoint, queue: &mut VecDeque<InputEvent>) {
// `id` is only trustworthy when TOUCH_ID_VALID is set; collapse to
// slot 0 otherwise so a bad id can't corrupt multi-touch slot state.
let id = if p.flags & TOUCH_ID_VALID != 0 {
p.id
} else {
0
};
self.state.process(InputAxis::Slot(id), queue);
if p.flags & TOUCH_POS_VALID != 0 {
self.state.process(InputAxis::AbsX(p.x as i32), queue);
self.state.process(InputAxis::AbsY(p.y as i32), queue);
}
if p.flags & TOUCH_DOWN != 0 {
// `TrackingId(>=0)` is the down-edge signal. NuttX doesn't
// expose a kernel-assigned tracking id; reusing the slot id
// is fine because PointerState only checks sign of value.
self.state.process(InputAxis::TrackingId(id as i32), queue);
} else if p.flags & TOUCH_UP != 0 {
self.state.process(InputAxis::TrackingId(-1), queue);
}
// Sync flushes the contact's pending PointerMove; TOUCH_MOVE needs
// no own axis since AbsX/AbsY already set the dirty bit.
self.state.process(InputAxis::Sync, queue);
}
}
impl Drop for TouchInput {
fn drop(&mut self) {
// SAFETY: `fd` was opened by `open()` and is closed once here.
unsafe { close(self.fd) };
}
}
pub(super) struct KeyInput {
fd: c_int,
buf: Vec<u8>,
}
impl KeyInput {
pub(super) fn open(path: &str) -> io::Result<Self> {
let cpath = CString::new(path)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "kbd path contains NUL"))?;
// SAFETY: `cpath` is a valid NUL-terminated C string.
let fd = unsafe { open(cpath.as_ptr(), O_RDONLY | O_NONBLOCK) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
// 32 events/read stays under one circbuf burst.
let buf = vec![0u8; 32 * core::mem::size_of::<KeyboardEvent>()];
Ok(Self { fd, buf })
}
pub(super) fn drain_into(&mut self, queue: &mut VecDeque<InputEvent>) {
loop {
// SAFETY: `self.buf.as_mut_ptr()` valid for `self.buf.len()`
// bytes; kernel writes at most that.
let n = unsafe { read(self.fd, self.buf.as_mut_ptr().cast(), self.buf.len()) };
if n <= 0 {
return;
}
let count = (n as usize) / core::mem::size_of::<KeyboardEvent>();
for i in 0..count {
let off = i * core::mem::size_of::<KeyboardEvent>();
// SAFETY: kernel wrote `KeyboardEvent` records into the
// buffer; `read_unaligned` handles non-4-aligned offsets.
let ev: KeyboardEvent = unsafe {
core::ptr::read_unaligned(self.buf.as_ptr().add(off) as *const KeyboardEvent)
};
let pressed = ev.event_type == KEYBOARD_PRESS;
queue.push_back(InputEvent::Key {
code: x11_keysym_to_mirui(ev.code),
pressed,
});
}
}
}
}
impl Drop for KeyInput {
fn drop(&mut self) {
// SAFETY: `fd` was opened by `open()` and is closed once here.
unsafe { close(self.fd) };
}
}
/// NuttX's keyboard upper half emits X11 keysyms. Unmapped keys pass
/// through as the raw keysym so user dispatch can still match literals.
fn x11_keysym_to_mirui(code: u32) -> u32 {
use crate::input::event::input::*;
match code {
0xFF0D => KEY_RETURN, // XK_Return
0xFF1B => KEY_ESCAPE, // XK_Escape
0xFF08 => KEY_BACKSPACE, // XK_BackSpace
0xFF50 => KEY_HOME, // XK_Home
0xFF51 => KEY_LEFT, // XK_Left
0xFF53 => KEY_RIGHT, // XK_Right
0xFF57 => KEY_END, // XK_End
0xFFFF => KEY_DELETE, // XK_Delete
_ => code,
}
}