1use std::ffi::c_ulong;
18use std::fs::OpenOptions;
19use std::os::fd::{AsRawFd, OwnedFd};
20
21const IOC_WRITE: c_ulong = 1;
22const UINPUT_NAME_LEN: usize = 80;
23
24const fn iow(nr: c_ulong, size: c_ulong) -> c_ulong {
26 (IOC_WRITE << 30) | (size << 16) | (b'U' as c_ulong) << 8 | nr
27}
28const fn io(nr: c_ulong) -> c_ulong {
30 ((b'U' as c_ulong) << 8) | nr
31}
32
33const UI_DEV_CREATE: c_ulong = io(1);
34const UI_DEV_DESTROY: c_ulong = io(2);
35const UI_DEV_SETUP: c_ulong = iow(3, std::mem::size_of::<UinputSetup>() as c_ulong);
36const UI_ABS_SETUP: c_ulong = iow(4, std::mem::size_of::<UinputAbsSetup>() as c_ulong);
37const UI_SET_EVBIT: c_ulong = iow(100, 4);
38const UI_SET_KEYBIT: c_ulong = iow(101, 4);
39const UI_SET_RELBIT: c_ulong = iow(102, 4);
40const UI_SET_ABSBIT: c_ulong = iow(103, 4);
41
42pub const EV_SYN: u16 = 0x00;
43pub const EV_KEY: u16 = 0x01;
44pub const EV_REL: u16 = 0x02;
45pub const EV_ABS: u16 = 0x03;
46const SYN_REPORT: u16 = 0;
47const BTN_MISC: u16 = 0x100;
48const BTN_LEFT: u16 = 0x110;
49const BTN_RIGHT: u16 = 0x111;
50const BTN_MIDDLE: u16 = 0x112;
51pub const REL_X: u16 = 0x00;
52pub const REL_Y: u16 = 0x01;
53pub const REL_HWHEEL: u16 = 0x06;
54pub const REL_WHEEL: u16 = 0x08;
55pub const ABS_X: u16 = 0x00;
56pub const ABS_Y: u16 = 0x01;
57const BUS_VIRTUAL: u16 = 0x06;
58
59pub const ABS_RANGE: i32 = 65535;
61
62#[repr(C)]
63#[derive(Default)]
64struct InputId {
65 bustype: u16,
66 vendor: u16,
67 product: u16,
68 version: u16,
69}
70
71#[repr(C)]
72struct UinputSetup {
73 id: InputId,
74 name: [u8; UINPUT_NAME_LEN],
75 ff_effects_max: u32,
76}
77
78#[repr(C)]
79#[derive(Default)]
80struct AbsInfo {
81 value: i32,
82 minimum: i32,
83 maximum: i32,
84 fuzz: i32,
85 flat: i32,
86 resolution: i32,
87}
88
89#[repr(C)]
90struct UinputAbsSetup {
91 code: u16,
92 absinfo: AbsInfo,
93}
94
95#[repr(C)]
96struct InputEvent {
97 sec: i64,
98 usec: i64,
99 kind: u16,
100 code: u16,
101 value: i32,
102}
103
104pub fn available() -> bool {
106 OpenOptions::new().write(true).open("/dev/uinput").is_ok()
107}
108
109unsafe fn set(fd: i32, request: c_ulong, value: c_ulong) -> Result<(), String> {
110 if unsafe { libc::ioctl(fd, request as _, value) } < 0 {
111 return Err(format!(
112 "uinput ioctl {request:#x}: {}",
113 std::io::Error::last_os_error()
114 ));
115 }
116 Ok(())
117}
118
119pub struct Device {
121 fd: OwnedFd,
122}
123
124impl Device {
125 fn open(
126 name: &str,
127 product: u16,
128 build: impl Fn(i32) -> Result<(), String>,
129 ) -> Result<Self, String> {
130 let file = OpenOptions::new()
131 .write(true)
132 .open("/dev/uinput")
133 .map_err(|e| format!("open /dev/uinput: {e}"))?;
134 let fd: OwnedFd = file.into();
135 let raw = fd.as_raw_fd();
136 unsafe { set(raw, UI_SET_EVBIT, EV_SYN as c_ulong)? };
137 build(raw)?;
138 let mut setup = UinputSetup {
139 id: InputId {
140 bustype: BUS_VIRTUAL,
141 vendor: 0x1d6b,
142 product,
143 version: 1,
144 },
145 name: [0; UINPUT_NAME_LEN],
146 ff_effects_max: 0,
147 };
148 let bytes = name.as_bytes();
149 let n = bytes.len().min(UINPUT_NAME_LEN - 1);
150 setup.name[..n].copy_from_slice(&bytes[..n]);
151 if unsafe { libc::ioctl(raw, UI_DEV_SETUP as _, &setup as *const _) } < 0 {
152 return Err(format!("UI_DEV_SETUP: {}", std::io::Error::last_os_error()));
153 }
154 if unsafe { libc::ioctl(raw, UI_DEV_CREATE as _) } < 0 {
155 return Err(format!(
156 "UI_DEV_CREATE: {}",
157 std::io::Error::last_os_error()
158 ));
159 }
160 Ok(Self { fd })
161 }
162
163 pub fn keyboard() -> Result<Self, String> {
166 Self::open("Selkies Virtual Keyboard", 0x0001, |fd| unsafe {
167 set(fd, UI_SET_EVBIT, EV_KEY as c_ulong)?;
168 for code in 1..BTN_MISC {
169 set(fd, UI_SET_KEYBIT, code as c_ulong)?;
170 }
171 Ok(())
172 })
173 }
174
175 pub fn pointer() -> Result<Self, String> {
178 Self::open("Selkies Virtual Pointer", 0x0002, |fd| unsafe {
179 set(fd, UI_SET_EVBIT, EV_KEY as c_ulong)?;
180 set(fd, UI_SET_EVBIT, EV_REL as c_ulong)?;
181 set(fd, UI_SET_EVBIT, EV_ABS as c_ulong)?;
182 for code in [BTN_LEFT, BTN_RIGHT, BTN_MIDDLE] {
183 set(fd, UI_SET_KEYBIT, code as c_ulong)?;
184 }
185 for code in [REL_X, REL_Y, REL_WHEEL, REL_HWHEEL] {
186 set(fd, UI_SET_RELBIT, code as c_ulong)?;
187 }
188 for code in [ABS_X, ABS_Y] {
189 set(fd, UI_SET_ABSBIT, code as c_ulong)?;
190 let abs = UinputAbsSetup {
191 code,
192 absinfo: AbsInfo {
193 maximum: ABS_RANGE,
194 ..AbsInfo::default()
195 },
196 };
197 if libc::ioctl(fd, UI_ABS_SETUP as _, &abs as *const _) < 0 {
198 return Err(format!(
199 "UI_ABS_SETUP {code}: {}",
200 std::io::Error::last_os_error()
201 ));
202 }
203 }
204 Ok(())
205 })
206 }
207
208 pub fn emit(&self, kind: u16, code: u16, value: i32) -> Result<(), String> {
210 let events = [
211 InputEvent {
212 sec: 0,
213 usec: 0,
214 kind,
215 code,
216 value,
217 },
218 InputEvent {
219 sec: 0,
220 usec: 0,
221 kind: EV_SYN,
222 code: SYN_REPORT,
223 value: 0,
224 },
225 ];
226 let bytes = unsafe {
227 std::slice::from_raw_parts(events.as_ptr() as *const u8, std::mem::size_of_val(&events))
228 };
229 let written = unsafe {
230 libc::write(
231 self.fd.as_raw_fd(),
232 bytes.as_ptr() as *const libc::c_void,
233 bytes.len(),
234 )
235 };
236 if written < 0 {
237 return Err(format!("uinput write: {}", std::io::Error::last_os_error()));
238 }
239 Ok(())
240 }
241}
242
243impl Drop for Device {
244 fn drop(&mut self) {
245 unsafe { libc::ioctl(self.fd.as_raw_fd(), UI_DEV_DESTROY as _) };
246 }
247}
248
249pub struct Pair {
251 pub keyboard: Device,
252 pub pointer: Device,
253}
254
255impl Pair {
256 pub fn open() -> Option<Self> {
259 if !available() {
260 return None;
261 }
262 match (Device::keyboard(), Device::pointer()) {
263 (Ok(keyboard), Ok(pointer)) => Some(Self { keyboard, pointer }),
264 (keyboard, pointer) => {
265 for err in [keyboard.err(), pointer.err()].into_iter().flatten() {
266 eprintln!("[HostCapture] uinput unavailable: {err}");
267 }
268 None
269 }
270 }
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
288 #[ignore]
289 fn emitted_events_reach_the_device_byte_for_byte() {
290 let path = std::env::var("UINPUT_SHIM_STREAM").expect("UINPUT_SHIM_STREAM");
291 let keyboard = Device::keyboard().expect("the emulator serves /dev/uinput");
292 keyboard.emit(EV_KEY, 30, 1).unwrap();
293 keyboard.emit(EV_KEY, 30, 0).unwrap();
294 drop(keyboard);
295 let raw = std::fs::read(&path).expect("event stream");
296 let size = std::mem::size_of::<InputEvent>();
297 let mut events: Vec<(u16, u16, i32)> = Vec::new();
298 let mut at = 0;
299 while at + size <= raw.len() {
300 let c = &raw[at..at + size];
301 events.push((
302 u16::from_ne_bytes([c[16], c[17]]),
303 u16::from_ne_bytes([c[18], c[19]]),
304 i32::from_ne_bytes([c[20], c[21], c[22], c[23]]),
305 ));
306 at += size;
307 }
308 assert_eq!(
309 events,
310 vec![
311 (EV_KEY, 30, 1),
312 (EV_SYN, SYN_REPORT, 0),
313 (EV_KEY, 30, 0),
314 (EV_SYN, SYN_REPORT, 0),
315 ],
316 "stream was {events:?}"
317 );
318 }
319
320 #[test]
321 fn ioctls_and_structs_match_the_kernel_abi() {
322 assert_eq!(UI_DEV_CREATE, 0x5501);
323 assert_eq!(UI_DEV_DESTROY, 0x5502);
324 assert_eq!(UI_SET_EVBIT, 0x4004_5564);
325 assert_eq!(UI_SET_KEYBIT, 0x4004_5565);
326 assert_eq!(UI_SET_RELBIT, 0x4004_5566);
327 assert_eq!(UI_SET_ABSBIT, 0x4004_5567);
328 assert_eq!(std::mem::size_of::<UinputSetup>(), 92);
329 assert_eq!(std::mem::size_of::<UinputAbsSetup>(), 28);
330 assert_eq!(std::mem::size_of::<InputEvent>(), 24);
331 assert_eq!(std::mem::offset_of!(UinputAbsSetup, absinfo), 4);
332 assert_eq!(std::mem::offset_of!(UinputSetup, name), 8);
333 assert_eq!(std::mem::offset_of!(UinputSetup, ff_effects_max), 88);
334 assert_eq!(UI_DEV_SETUP, 0x405c_5503);
335 assert_eq!(UI_ABS_SETUP, 0x401c_5504);
336 }
337}