use std::io;
#[cfg(target_pointer_width = "64")]
pub(crate) type KernelUlong = u64;
#[cfg(target_pointer_width = "32")]
pub(crate) type KernelUlong = u32;
#[cfg(all(
target_os = "linux",
not(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "arm",
target_arch = "aarch64",
target_arch = "riscv64",
target_arch = "loongarch64",
))
))]
compile_error!(
"the evdev backend's O_NONBLOCK and ioctl encoding are unverified for this architecture"
);
#[cfg(all(
target_os = "linux",
target_arch = "x86_64",
target_pointer_width = "32"
))]
compile_error!("the evdev backend does not support x32, whose __kernel_ulong_t is 64-bit");
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct InputEvent {
pub(crate) sec: KernelUlong,
pub(crate) usec: KernelUlong,
pub(crate) kind: u16,
pub(crate) code: u16,
pub(crate) value: i32,
}
pub(crate) const EVENT_SIZE: usize = size_of::<InputEvent>();
impl InputEvent {
#[cfg(test)]
pub(crate) const fn new(kind: u16, code: u16, value: i32) -> Self {
Self {
sec: 0,
usec: 0,
kind,
code,
value,
}
}
fn from_bytes(bytes: &[u8; EVENT_SIZE]) -> Self {
fn field<const N: usize>(bytes: &[u8; EVENT_SIZE], at: usize) -> [u8; N] {
*bytes[at..]
.first_chunk()
.expect("the layout test pins every field inside the structure")
}
Self {
sec: KernelUlong::from_ne_bytes(field(bytes, core::mem::offset_of!(Self, sec))),
usec: KernelUlong::from_ne_bytes(field(bytes, core::mem::offset_of!(Self, usec))),
kind: u16::from_ne_bytes(field(bytes, core::mem::offset_of!(Self, kind))),
code: u16::from_ne_bytes(field(bytes, core::mem::offset_of!(Self, code))),
value: i32::from_ne_bytes(field(bytes, core::mem::offset_of!(Self, value))),
}
}
}
pub(crate) fn decode_events(bytes: &[u8]) -> io::Result<impl Iterator<Item = InputEvent>> {
let (events, rest) = bytes.as_chunks::<EVENT_SIZE>();
if rest.is_empty() {
Ok(events.iter().map(InputEvent::from_bytes))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"an evdev read returned {} bytes, not a multiple of {EVENT_SIZE}",
bytes.len()
),
))
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct AbsInfo {
pub(crate) value: i32,
pub(crate) minimum: i32,
pub(crate) maximum: i32,
pub(crate) fuzz: i32,
pub(crate) flat: i32,
pub(crate) resolution: i32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct InputId {
pub(crate) bustype: u16,
pub(crate) vendor: u16,
pub(crate) product: u16,
pub(crate) version: u16,
}
pub(crate) const EV_SYN: u16 = 0x00;
pub(crate) const EV_KEY: u16 = 0x01;
pub(crate) const EV_ABS: u16 = 0x03;
pub(crate) const SYN_REPORT: u16 = 0;
pub(crate) const SYN_DROPPED: u16 = 3;
pub(crate) const BTN_GAMEPAD: u16 = 0x130;
pub(crate) const BTN_SOUTH: u16 = 0x130;
pub(crate) const BTN_EAST: u16 = 0x131;
pub(crate) const BTN_NORTH: u16 = 0x133;
pub(crate) const BTN_WEST: u16 = 0x134;
pub(crate) const BTN_TL: u16 = 0x136;
pub(crate) const BTN_TR: u16 = 0x137;
pub(crate) const BTN_TL2: u16 = 0x138;
pub(crate) const BTN_TR2: u16 = 0x139;
pub(crate) const BTN_SELECT: u16 = 0x13a;
pub(crate) const BTN_START: u16 = 0x13b;
pub(crate) const BTN_MODE: u16 = 0x13c;
pub(crate) const BTN_THUMBL: u16 = 0x13d;
pub(crate) const BTN_THUMBR: u16 = 0x13e;
pub(crate) const BTN_DPAD_UP: u16 = 0x220;
pub(crate) const BTN_DPAD_DOWN: u16 = 0x221;
pub(crate) const BTN_DPAD_LEFT: u16 = 0x222;
pub(crate) const BTN_DPAD_RIGHT: u16 = 0x223;
pub(crate) const KEY_CNT: usize = 0x300;
pub(crate) const ABS_X: u16 = 0x00;
pub(crate) const ABS_Y: u16 = 0x01;
pub(crate) const ABS_Z: u16 = 0x02;
pub(crate) const ABS_RX: u16 = 0x03;
pub(crate) const ABS_RY: u16 = 0x04;
pub(crate) const ABS_RZ: u16 = 0x05;
pub(crate) const ABS_GAS: u16 = 0x09;
pub(crate) const ABS_BRAKE: u16 = 0x0a;
pub(crate) const ABS_HAT0X: u16 = 0x10;
pub(crate) const ABS_HAT0Y: u16 = 0x11;
pub(crate) const ABS_HAT2X: u16 = 0x14;
pub(crate) const ABS_HAT2Y: u16 = 0x15;
pub(crate) const ABS_CNT: usize = 0x40;
const WORD_BITS: usize = KernelUlong::BITS as usize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Bits<const WORDS: usize>(pub(crate) [KernelUlong; WORDS]);
impl<const WORDS: usize> Bits<WORDS> {
pub(crate) const EMPTY: Self = Self([0; WORDS]);
pub(crate) fn has(&self, code: u16) -> bool {
let code = usize::from(code);
self.0
.get(code / WORD_BITS)
.is_some_and(|word| word >> (code % WORD_BITS) & 1 != 0)
}
pub(crate) fn set(&mut self, code: u16, on: bool) {
let code = usize::from(code);
if let Some(word) = self.0.get_mut(code / WORD_BITS) {
let bit = 1 << (code % WORD_BITS);
if on {
*word |= bit;
} else {
*word &= !bit;
}
}
}
}
pub(crate) type KeyBits = Bits<{ KEY_CNT.div_ceil(WORD_BITS) }>;
pub(crate) type AbsBits = Bits<{ ABS_CNT.div_ceil(WORD_BITS) }>;
const IOC_READ: u32 = 2;
const IOC_SIZE_BITS: u32 = 14;
pub(crate) const fn evioc_read(nr: u32, size: usize) -> u32 {
assert!(
size < 1 << IOC_SIZE_BITS,
"an ioctl's size field is 14 bits"
);
(IOC_READ << 30) | ((size as u32) << 16) | ((b'E' as u32) << 8) | nr
}
pub(crate) const EVIOCGID_NR: u32 = 0x02;
pub(crate) const EVIOCGKEY_NR: u32 = 0x18;
pub(crate) const fn eviocgbit_nr(ev: u16) -> u32 {
0x20 + ev as u32
}
pub(crate) const fn eviocgabs_nr(abs: u16) -> u32 {
0x40 + abs as u32
}
#[cfg(target_os = "linux")]
pub(crate) const O_NONBLOCK: i32 = 0o4000;
pub(crate) const ENODEV: i32 = 19;
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
pub(crate) type IoctlRequest = core::ffi::c_ulong;
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub(crate) type IoctlRequest = core::ffi::c_int;
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
pub(crate) fn ioctl_request(request: u32) -> IoctlRequest {
IoctlRequest::from(request)
}
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub(crate) fn ioctl_request(request: u32) -> IoctlRequest {
request.cast_signed()
}
#[cfg(target_os = "linux")]
unsafe extern "C" {
pub(crate) fn ioctl(fd: core::ffi::c_int, request: IoctlRequest, ...) -> core::ffi::c_int;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ffi_layout::assert_layout;
#[test]
fn the_structures_match_the_c_layout() {
let word = size_of::<usize>();
assert_layout!(InputEvent, 2 * word + 8, {
sec: 0, word;
usec: word, word;
kind: 2 * word, 2;
code: 2 * word + 2, 2;
value: 2 * word + 4, 4;
});
#[cfg(target_pointer_width = "64")]
assert_eq!(EVENT_SIZE, 24, "x86_64 and aarch64");
#[cfg(target_pointer_width = "32")]
assert_eq!(EVENT_SIZE, 16, "32-bit, with either time_t");
assert_layout!(AbsInfo, 24, {
value: 0, 4;
minimum: 4, 4;
maximum: 8, 4;
fuzz: 12, 4;
flat: 16, 4;
resolution: 20, 4;
});
assert_layout!(InputId, 8, {
bustype: 0, 2;
vendor: 2, 2;
product: 4, 2;
version: 6, 2;
});
assert_eq!(size_of::<KeyBits>(), KEY_CNT / 8, "96 bytes of key bits");
assert_eq!(size_of::<AbsBits>(), ABS_CNT / 8, "8 bytes of axis bits");
}
#[test]
fn requests_encode_like_the_kernel_macros() {
assert_eq!(
evioc_read(EVIOCGID_NR, size_of::<InputId>()),
0x8008_4502,
"EVIOCGID"
);
assert_eq!(
evioc_read(eviocgabs_nr(ABS_X), size_of::<AbsInfo>()),
0x8018_4540,
"EVIOCGABS(ABS_X)"
);
assert_eq!(
evioc_read(eviocgabs_nr(ABS_HAT0Y), size_of::<AbsInfo>()),
0x8018_4551,
"EVIOCGABS(ABS_HAT0Y)"
);
assert_eq!(
evioc_read(eviocgbit_nr(EV_KEY), size_of::<KeyBits>()),
0x8060_4521,
"EVIOCGBIT(EV_KEY, 96)"
);
assert_eq!(
evioc_read(eviocgbit_nr(EV_ABS), size_of::<AbsBits>()),
0x8008_4523,
"EVIOCGBIT(EV_ABS, 8)"
);
assert_eq!(
evioc_read(EVIOCGKEY_NR, size_of::<KeyBits>()),
0x8060_4518,
"EVIOCGKEY(96)"
);
}
#[test]
fn read_bytes_decode_into_events() {
let word = size_of::<usize>();
let mut bytes = vec![0; 2 * EVENT_SIZE];
for (index, (kind, code, value)) in [(EV_ABS, ABS_Y, -32768), (EV_SYN, SYN_REPORT, 0)]
.into_iter()
.enumerate()
{
let event = &mut bytes[index * EVENT_SIZE..][..EVENT_SIZE];
if index == 0 {
event[..word].fill(0xAB);
}
event[2 * word..][..2].copy_from_slice(&kind.to_ne_bytes());
event[2 * word + 2..][..2].copy_from_slice(&code.to_ne_bytes());
event[2 * word + 4..][..4].copy_from_slice(&i32::to_ne_bytes(value));
}
let events: Vec<_> = decode_events(&bytes).expect("whole events").collect();
assert_eq!(events.len(), 2);
assert_eq!(
(events[0].kind, events[0].code, events[0].value),
(EV_ABS, ABS_Y, -32768)
);
assert_eq!(
events[0].sec,
KernelUlong::from_ne_bytes([0xAB; size_of::<KernelUlong>()])
);
assert_eq!(events[1], InputEvent::new(EV_SYN, SYN_REPORT, 0));
let torn = decode_events(&bytes[..EVENT_SIZE + 1]).map(|_| ());
assert_eq!(
torn.map_err(|error| error.kind()),
Err(io::ErrorKind::InvalidData)
);
}
#[test]
fn bitmaps_read_by_word() {
let mut keys = KeyBits::EMPTY;
for code in [0, 63, 64, BTN_SOUTH, 0x2ff] {
keys.set(code, true);
}
for code in [0, 63, 64, BTN_SOUTH, 0x2ff] {
assert!(keys.has(code), "{code:#x}");
}
assert!(!keys.has(1) && !keys.has(BTN_EAST));
keys.set(BTN_SOUTH, false);
assert!(!keys.has(BTN_SOUTH));
keys.set(0x300, true);
assert!(!keys.has(0x300), "past KEY_MAX is not a key");
}
}