use std::collections::VecDeque;
use crcbl_core::input::{ButtonState, DeviceId, PointerButton};
use super::ffi::value;
pub const KEYBOARD_DEVICE: DeviceId = DeviceId(1);
pub const POINTER_DEVICE: DeviceId = DeviceId(2);
pub const TOUCH_DEVICE: DeviceId = DeviceId(3);
const FIRST_REAL_DEVICE: u32 = TOUCH_DEVICE.0 + 1;
pub const MATCH_WINDOW_MS: u32 = 500;
const MAX_PENDING: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Edge {
Key { scancode: u32, pressed: bool },
Button {
button: PointerButton,
pressed: bool,
},
Wheel { horizontal: bool },
}
#[derive(Clone, Copy, Debug)]
struct Pending {
edge: Edge,
device: DeviceId,
millis: u32,
}
const fn close(a: u32, b: u32) -> bool {
a.wrapping_sub(b) <= MATCH_WINDOW_MS || b.wrapping_sub(a) <= MATCH_WINDOW_MS
}
#[must_use]
pub const fn raw_scancode(make_code: u16, flags: u16) -> Option<u32> {
if flags & value::RI_KEY_E1 != 0 {
return None;
}
let prefix = if flags & value::RI_KEY_E0 != 0 {
0xE000
} else {
0
};
Some(prefix | make_code as u32)
}
fn mouse_edges(flags: u16) -> impl Iterator<Item = Edge> {
const BUTTONS: [(u16, PointerButton); 5] = [
(value::RI_MOUSE_LEFT_BUTTON_DOWN, PointerButton::Left),
(value::RI_MOUSE_RIGHT_BUTTON_DOWN, PointerButton::Right),
(value::RI_MOUSE_MIDDLE_BUTTON_DOWN, PointerButton::Middle),
(value::RI_MOUSE_BUTTON_4_DOWN, PointerButton::Back),
(value::RI_MOUSE_BUTTON_5_DOWN, PointerButton::Forward),
];
let buttons = BUTTONS.into_iter().flat_map(move |(down, button)| {
[(down, true), (down << 1, false)]
.into_iter()
.filter(move |&(bit, _)| flags & bit != 0)
.map(move |(_, pressed)| Edge::Button { button, pressed })
});
let wheels = [
(value::RI_MOUSE_WHEEL, false),
(value::RI_MOUSE_H_WHEEL, true),
]
.into_iter()
.filter(move |&(bit, _)| flags & bit != 0)
.map(|(_, horizontal)| Edge::Wheel { horizontal });
buttons.chain(wheels)
}
#[derive(Debug)]
pub struct Attribution {
pending: VecDeque<Pending>,
pointer: DeviceId,
}
impl Default for Attribution {
fn default() -> Self {
Self {
pending: VecDeque::new(),
pointer: POINTER_DEVICE,
}
}
}
impl Attribution {
pub fn raw_key(&mut self, scancode: u32, pressed: bool, device: Option<DeviceId>, millis: u32) {
let device = device.unwrap_or(KEYBOARD_DEVICE);
self.push(Edge::Key { scancode, pressed }, device, millis);
}
pub fn raw_mouse(&mut self, button_flags: u16, device: Option<DeviceId>, millis: u32) {
let device = device.unwrap_or(POINTER_DEVICE);
self.pointer = device;
for edge in mouse_edges(button_flags) {
self.push(edge, device, millis);
}
}
pub fn key(&mut self, scancode: u32, state: ButtonState, millis: u32) -> DeviceId {
let pressed = state.is_pressed();
self.take(Edge::Key { scancode, pressed }, millis)
.unwrap_or(KEYBOARD_DEVICE)
}
pub fn button(&mut self, button: PointerButton, state: ButtonState, millis: u32) -> DeviceId {
let pressed = state.is_pressed();
self.take(Edge::Button { button, pressed }, millis)
.unwrap_or(self.pointer)
}
pub fn wheel(&mut self, horizontal: bool, millis: u32) -> DeviceId {
self.take(Edge::Wheel { horizontal }, millis)
.unwrap_or(self.pointer)
}
#[must_use]
pub const fn pointer(&self) -> DeviceId {
self.pointer
}
fn push(&mut self, edge: Edge, device: DeviceId, millis: u32) {
self.retire(millis);
if self.pending.len() == MAX_PENDING {
self.pending.pop_front();
}
self.pending.push_back(Pending {
edge,
device,
millis,
});
}
fn take(&mut self, edge: Edge, millis: u32) -> Option<DeviceId> {
self.retire(millis);
let index = self
.pending
.iter()
.position(|pending| pending.edge == edge && close(pending.millis, millis))?;
self.pending.remove(index).map(|pending| pending.device)
}
fn retire(&mut self, now: u32) {
while self
.pending
.front()
.is_some_and(|oldest| !close(oldest.millis, now))
{
self.pending.pop_front();
}
}
}
#[derive(Debug)]
pub struct DeviceTable {
handles: Vec<(isize, DeviceId)>,
names: Vec<(String, DeviceId)>,
next: u32,
}
impl Default for DeviceTable {
fn default() -> Self {
Self {
handles: Vec::new(),
names: Vec::new(),
next: FIRST_REAL_DEVICE,
}
}
}
impl DeviceTable {
pub fn resolve(
&mut self,
handle: isize,
name: impl FnOnce() -> Option<String>,
) -> Option<DeviceId> {
if handle == 0 {
return None;
}
if let Some(&(_, id)) = self.handles.iter().find(|&&(known, _)| known == handle) {
return Some(id);
}
let named = name();
let known = named
.as_ref()
.and_then(|name| self.names.iter().find(|(known, _)| known == name))
.map(|&(_, id)| id);
let id = known.unwrap_or_else(|| {
let id = DeviceId(self.next);
self.next += 1;
if let Some(name) = named {
self.names.push((name, id));
}
id
});
self.handles.push((handle, id));
Some(id)
}
pub fn remove(&mut self, handle: isize) {
self.handles.retain(|&(known, _)| known != handle);
}
}
#[cfg(test)]
mod tests {
use super::*;
const KEYBOARD_A: DeviceId = DeviceId(10);
const KEYBOARD_B: DeviceId = DeviceId(11);
const MOUSE_A: DeviceId = DeviceId(12);
#[test]
fn a_message_takes_the_device_of_the_report_that_produced_it() {
let mut attribution = Attribution::default();
attribution.raw_key(0x1E, true, Some(KEYBOARD_A), 1_000);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_000),
KEYBOARD_A
);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_001),
KEYBOARD_DEVICE
);
}
#[test]
fn two_keyboards_typing_in_one_pump_keep_their_own_keys() {
let mut attribution = Attribution::default();
attribution.raw_key(0x1E, true, Some(KEYBOARD_A), 1_000);
attribution.raw_key(0x1F, true, Some(KEYBOARD_B), 1_001);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_000),
KEYBOARD_A
);
assert_eq!(
attribution.key(0x1F, ButtonState::Pressed, 1_001),
KEYBOARD_B
);
}
#[test]
fn a_report_whose_message_never_came_is_passed_over_by_content() {
let mut attribution = Attribution::default();
attribution.raw_key(0x0F, true, Some(KEYBOARD_A), 1_000);
attribution.raw_key(0x1E, true, Some(KEYBOARD_B), 1_010);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_010),
KEYBOARD_B
);
attribution.raw_mouse(value::RI_MOUSE_LEFT_BUTTON_DOWN, Some(MOUSE_A), 1_020);
assert_eq!(
attribution.key(0x0F, ButtonState::Pressed, 1_020),
KEYBOARD_A
);
assert_eq!(
attribution.button(PointerButton::Left, ButtonState::Pressed, 1_020),
MOUSE_A
);
}
#[test]
fn the_same_key_on_two_keyboards_is_claimed_in_order() {
let mut attribution = Attribution::default();
attribution.raw_key(0x1E, true, Some(KEYBOARD_A), 1_000);
attribution.raw_key(0x1E, true, Some(KEYBOARD_B), 1_002);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_000),
KEYBOARD_A
);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_002),
KEYBOARD_B
);
}
#[test]
fn a_press_does_not_claim_a_release_and_the_prefix_is_part_of_the_code() {
let mut attribution = Attribution::default();
attribution.raw_key(0xE048, false, Some(KEYBOARD_A), 1_000);
assert_eq!(
attribution.key(0x48, ButtonState::Released, 1_000),
KEYBOARD_DEVICE
);
assert_eq!(
attribution.key(0xE048, ButtonState::Pressed, 1_000),
KEYBOARD_DEVICE
);
assert_eq!(
attribution.key(0xE048, ButtonState::Released, 1_000),
KEYBOARD_A
);
}
#[test]
fn a_report_nothing_claimed_ages_out_instead_of_being_given_to_a_later_key() {
let mut attribution = Attribution::default();
attribution.raw_key(0x0F, true, Some(KEYBOARD_A), 1_000);
let later = 1_000 + MATCH_WINDOW_MS + 1;
assert_eq!(
attribution.key(0x0F, ButtonState::Pressed, later),
KEYBOARD_DEVICE
);
assert!(attribution.pending.is_empty(), "{:?}", attribution.pending);
}
#[test]
fn matching_survives_the_tick_count_wrapping() {
let mut attribution = Attribution::default();
attribution.raw_key(0x1E, true, Some(KEYBOARD_A), u32::MAX - 2);
assert_eq!(attribution.key(0x1E, ButtonState::Pressed, 3), KEYBOARD_A);
}
#[test]
fn injected_input_carries_the_per_kind_device() {
let mut attribution = Attribution::default();
attribution.raw_key(0x1E, true, None, 1_000);
assert_eq!(
attribution.key(0x1E, ButtonState::Pressed, 1_000),
KEYBOARD_DEVICE
);
attribution.raw_mouse(value::RI_MOUSE_LEFT_BUTTON_DOWN, None, 1_000);
assert_eq!(
attribution.button(PointerButton::Left, ButtonState::Pressed, 1_000),
POINTER_DEVICE
);
}
#[test]
fn a_mouse_report_names_the_pointer_and_every_edge_it_carries() {
let mut attribution = Attribution::default();
let flags = value::RI_MOUSE_LEFT_BUTTON_DOWN
| (value::RI_MOUSE_RIGHT_BUTTON_DOWN << 1)
| value::RI_MOUSE_BUTTON_5_DOWN
| value::RI_MOUSE_WHEEL;
attribution.raw_mouse(flags, Some(MOUSE_A), 2_000);
assert_eq!(attribution.pointer(), MOUSE_A);
assert_eq!(
attribution.button(PointerButton::Left, ButtonState::Pressed, 2_000),
MOUSE_A
);
assert_eq!(
attribution.button(PointerButton::Right, ButtonState::Released, 2_000),
MOUSE_A
);
assert_eq!(
attribution.button(PointerButton::Forward, ButtonState::Pressed, 2_000),
MOUSE_A
);
assert_eq!(attribution.wheel(false, 2_000), MOUSE_A);
assert!(attribution.pending.is_empty(), "{:?}", attribution.pending);
assert_eq!(
attribution.button(PointerButton::Middle, ButtonState::Pressed, 2_001),
MOUSE_A
);
}
#[test]
fn the_raw_scancode_folds_the_prefix_as_the_message_does() {
assert_eq!(raw_scancode(0x1E, 0), Some(0x1E));
assert_eq!(raw_scancode(0x48, value::RI_KEY_E0), Some(0xE048));
assert_eq!(
raw_scancode(0x1D, value::RI_KEY_E0 | value::RI_KEY_BREAK),
Some(0xE01D)
);
assert_eq!(raw_scancode(0x1D, value::RI_KEY_E1), None);
}
#[test]
fn a_device_keeps_its_id_across_a_replug_and_ids_are_never_reissued() {
let mut table = DeviceTable::default();
let path = || Some("\\\\?\\HID#VID_3151".to_owned());
assert_eq!(table.resolve(0, path), None, "injected input has no device");
let first = table.resolve(0x100, path).expect("a real handle");
assert!(first.0 >= FIRST_REAL_DEVICE, "{first:?}");
assert_eq!(table.resolve(0x100, || panic!("cached")), Some(first));
let other = table
.resolve(0x200, || Some("\\\\?\\HID#VID_3710".to_owned()))
.expect("a second device");
assert_ne!(other, first);
table.remove(0x100);
assert_eq!(table.resolve(0x300, path), Some(first));
let newcomer = table
.resolve(0x100, || Some("\\\\?\\HID#VID_9999".to_owned()))
.expect("a third device");
assert_ne!(newcomer, first);
assert_ne!(newcomer, other);
}
#[test]
fn a_device_with_no_readable_path_still_gets_an_id_of_its_own() {
let mut table = DeviceTable::default();
let a = table.resolve(0x100, || None).expect("an id");
let b = table.resolve(0x200, || None).expect("an id");
assert_ne!(a, b);
}
}