use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::mpsc::{self, Receiver};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use super::frames::{self, MakcuFrames, frame};
use super::{MakcuBackend, Poller, attach};
use crate::backend::{
Backend, Command, Input, InputKind, InputKinds, LockSlot, Sign, Signal, Sink, Usage,
};
use crate::capability::{Action, Axis, Button, Execution, Family, Firmware, Snapshot, StableId};
use crate::click::{Click, ClickTiming, Holds};
use crate::device::{Device, Start};
use crate::error::Error;
use crate::event::{DropSource, Event};
use crate::key::Key;
use crate::transport::codec::{Codec, Incoming};
use crate::transport::fake::{FakeBox, FakeSystem};
use crate::transport::{Candidate, PortUsb, System};
const PORT: &str = "COM11";
const SERIAL: &str = "SERIAL0001";
struct State {
baud: u32,
text: Option<&'static [u8]>,
build: Option<u32>,
kinds: u8,
stream: u8,
key_stream: u8,
injected: u8,
refuse: HashSet<u8>,
silent_versions: usize,
silent_versions_left: usize,
silent_builds_left: usize,
build_reply: Option<Vec<u8>>,
refuse_once: HashSet<u8>,
apply_presses: bool,
keys: HashSet<u8>,
locks: u16,
masks: HashSet<u8>,
slow: HashMap<u8, Duration>,
}
impl State {
fn makcu(build: u32) -> Self {
Self {
baud: 4_000_000,
text: Some(b"km.MAKCU\r\n>>> "),
build: Some(build),
kinds: 0x03,
stream: 0,
key_stream: 0,
injected: 0,
refuse: HashSet::new(),
silent_versions: 0,
silent_versions_left: 0,
silent_builds_left: 0,
build_reply: None,
refuse_once: HashSet::new(),
apply_presses: true,
keys: HashSet::new(),
locks: 0,
masks: HashSet::new(),
slow: HashMap::new(),
}
}
}
type Shared = Arc<Mutex<State>>;
fn respond(shared: &Shared, baud: u32, bytes: &[u8]) -> Vec<Vec<u8>> {
let mut state = shared.lock().unwrap();
if baud != state.baud {
return Vec::new();
}
if bytes == frames::VERSION_QUERY {
if state.silent_versions_left > 0 {
state.silent_versions_left -= 1;
return Vec::new();
}
return state
.text
.map(|text| vec![text.to_vec()])
.unwrap_or_default();
}
let Some((command, payload)) = frames::parts(bytes) else {
return Vec::new();
};
if let Some(&delay) = state.slow.get(&command) {
drop(state);
thread::sleep(delay);
state = shared.lock().unwrap();
}
if state.refuse.contains(&command) || state.refuse_once.remove(&command) {
return vec![frame(command, &[0xFF])];
}
match (command, payload) {
(frames::FIRMWARE_VERSION, []) if state.silent_builds_left > 0 => {
state.silent_builds_left -= 1;
Vec::new()
}
(frames::FIRMWARE_VERSION, []) if state.build_reply.is_some() => {
vec![frame(command, state.build_reply.as_deref().unwrap())]
}
(frames::FIRMWARE_VERSION, []) => state
.build
.map(|build| vec![frame(command, &build.to_le_bytes())])
.unwrap_or_default(),
(frames::DEVICE, []) => vec![frame(command, &[state.kinds])],
(frames::INPUT_STREAM, [1, on]) => {
state.stream = *on;
Vec::new()
}
(frames::INPUT_STREAM, [1]) => vec![frame(command, &[state.stream])],
(frames::INPUT_STREAM, [2, on]) => {
state.key_stream = *on;
Vec::new()
}
(frames::INPUT_STREAM, [2]) => vec![frame(command, &[state.key_stream])],
(frames::INJECT_SNAPSHOT, []) => {
let mut snapshot = vec![0u8; 40];
snapshot[0] = state.injected;
snapshot[1..3].copy_from_slice(&state.locks.to_le_bytes());
for &key in &state.keys {
if (0xE0..=0xE7).contains(&key) {
snapshot[3] |= 1 << (key - 0xE0);
} else {
snapshot[4 + usize::from(key / 8)] |= 1 << (key % 8);
}
}
vec![frame(command, &snapshot)]
}
(frames::CLICK, [button, ..]) => {
state.injected |= 1 << (button - 1);
Vec::new()
}
(frames::LOCK, [target, on]) => {
if *on == 1 {
state.locks |= 1 << target;
} else {
state.locks &= !(1 << target);
}
Vec::new()
}
(frames::KEY_MASK, [key, on]) => {
if *on == 1 {
state.masks.insert(*key);
} else {
state.masks.remove(key);
}
Vec::new()
}
(frames::KEY_DOWN, [key]) => {
state.keys.insert(*key);
Vec::new()
}
(frames::KEY_UP, [key]) => {
state.keys.remove(key);
Vec::new()
}
(0x11..=0x15, [1]) if !state.apply_presses => Vec::new(),
(0x11..=0x15, [on]) => {
let bit = 1 << (command - 0x11);
if *on == 1 {
state.injected |= bit;
} else {
state.injected &= !bit;
}
Vec::new()
}
_ => Vec::new(),
}
}
fn makcu_usb() -> Option<PortUsb> {
Some(PortUsb {
vid: 0x1A86,
pid: 0x55D3,
serial: Some(SERIAL.into()),
})
}
struct Rig {
system: FakeSystem,
fake: Arc<FakeBox>,
state: Shared,
}
fn rig(state: State) -> Rig {
rig_with(state, makcu_usb())
}
fn rig_with(state: State, usb: Option<PortUsb>) -> Rig {
let state = Arc::new(Mutex::new(state));
let fake = FakeBox::by_baud({
let state = state.clone();
move |baud, bytes| respond(&state, baud, bytes)
});
let system = FakeSystem::default();
system.plug(PORT, usb, fake.clone());
Rig {
system,
fake,
state,
}
}
impl Rig {
fn candidate(&self) -> Candidate {
self.system
.ports()
.into_iter()
.find(|candidate| candidate.path == Path::new(PORT))
.unwrap_or(Candidate {
path: PORT.into(),
usb: None,
})
}
fn attach(&self) -> crate::error::Result<(MakcuBackend, Snapshot, Poller, Receiver<Signal>)> {
let (signals, incoming) = mpsc::channel();
let (backend, snapshot, poller) = attach(
Arc::new(self.system.clone()),
&self.candidate(),
Sink(signals),
)?;
Ok((backend, snapshot, poller, incoming))
}
fn device(&self) -> (Device, Poller) {
let (signals, incoming) = mpsc::channel();
let (backend, snapshot, poller) = attach(
Arc::new(self.system.clone()),
&self.candidate(),
Sink(signals.clone()),
)
.unwrap();
let device = Device::start(Start {
backend: Box::new(backend),
snapshot,
signals,
incoming,
click_timing: ClickTiming::Makcu,
holds: Holds::from_clock(),
port: PORT.into(),
})
.unwrap();
(device, poller)
}
fn written(&self) -> Vec<Vec<u8>> {
self.fake.written()
}
fn clear(&self) {
self.fake.clear_written();
}
fn set(&self, change: impl FnOnce(&mut State)) {
change(&mut self.state.lock().unwrap());
}
}
fn until(mut done: impl FnMut() -> bool) {
let deadline = Instant::now() + Duration::from_secs(5);
while !done() {
assert!(Instant::now() < deadline, "gave up waiting");
thread::sleep(Duration::from_millis(5));
}
}
fn link_events(signals: &Receiver<Signal>) -> Vec<&'static str> {
signals
.try_iter()
.filter_map(|signal| match signal {
Signal::SessionReset => Some("session reset"),
Signal::Disconnected => Some("disconnected"),
Signal::Reconnected(_) => Some("reconnected"),
_ => None,
})
.collect()
}
fn split_all(codec: &mut MakcuFrames, bytes: &[u8]) -> Vec<Vec<u8>> {
let mut buffer = bytes.to_vec();
let mut out = Vec::new();
while let Some(frame) = codec.split(&mut buffer) {
out.push(frame);
}
out
}
#[test]
fn a_firmware_reply_carries_the_build() {
let reply = [0xDE, 0xAD, 0x04, 0x00, 0x04, 0xFE, 0x0F, 0x00, 0x00];
assert_eq!(
frame(frames::FIRMWARE_VERSION, &[]),
[0xDE, 0xAD, 0, 0, 0x04]
);
assert_eq!(
split_all(&mut MakcuFrames::binary(Default::default()), &reply),
vec![reply.to_vec()]
);
let (_, payload) = frames::parts(&reply).unwrap();
assert_eq!(u32::from_le_bytes(payload.try_into().unwrap()), 4094);
}
#[test]
fn a_mouse_input_change_is_a_button_event_and_other_kinds_are_noise() {
let mut codec = MakcuFrames::binary(Default::default());
let right = vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x01, 0x01];
assert!(matches!(
codec.classify(&right),
Incoming::Input {
input: Input::Button {
button: Button::Right,
pressed: true
},
box_time: None
}
));
let extra = vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 31, 0x00];
assert!(matches!(
codec.classify(&extra),
Incoming::Input {
input: Input::Button {
button: Button::Extra(31),
pressed: false
},
..
}
));
for noise in [
vec![0xDE, 0xAD, 0x04, 0x00, 0x53, 0x03, 0x0A, 0xFF, 0x03],
vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x02, 0x00, 0x01],
vec![0xDE, 0xAD, 0x04, 0x00, 0x53, 0x01, 0x20, 0x01, 0x00],
vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 32, 0x01],
] {
assert!(
matches!(codec.classify(&noise), Incoming::Noise),
"{noise:02X?}"
);
}
}
#[test]
fn the_overflow_frame_is_a_lost_stream() {
let overflow = vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0xFF, 0xFF];
assert!(matches!(
MakcuFrames::binary(Default::default()).classify(&overflow),
Incoming::StreamLost(crate::backend::InputKind::Buttons)
));
}
#[test]
fn a_refusal_names_what_was_refused_and_a_zero_reply_is_just_a_reply() {
let last = Arc::new(frames::LastSent::default());
last.set(0, 1);
let mut codec = MakcuFrames::binary(last);
let cases = [
(0x18, Action::Move),
(0x67, Action::MoveNow),
(0x19, Action::Wheel),
(0x61, Action::Click),
(0x11, Action::Press),
(0x15, Action::Release),
(0x52, Action::ButtonEvents),
];
for (command, expected) in cases {
match codec.classify(&frame(command, &[0xFF])) {
Incoming::Refused { key, action } => {
assert_eq!(key, u32::from(command));
assert_eq!(action, Some(expected));
}
other => panic!("{command:02X} gave {other:?}"),
}
}
assert!(matches!(
codec.classify(&frame(0x52, &[0x00])),
Incoming::Reply(0x52)
));
assert!(matches!(
codec.classify(&frame(frames::INJECT_SNAPSHOT, &[0xFF])),
Incoming::Refused { action: None, .. }
));
}
#[test]
fn text_ends_at_the_prompt_and_a_binary_frame_after_it_splits_on_its_own() {
let mut bytes = b"ERR\r\n>>> km.MAKCU\r\n>>> ".to_vec();
bytes.extend(frame(frames::DEVICE, &[0x03]));
let frames = split_all(&mut MakcuFrames::probe(), &bytes);
assert_eq!(
frames,
vec![
b"ERR\r\n>>> ".to_vec(),
b"km.MAKCU\r\n>>> ".to_vec(),
frame(frames::DEVICE, &[0x03])
]
);
assert!(!frames::says_makcu(&frames[0]));
assert!(frames::says_makcu(&frames[1]));
assert!(!frames::says_makcu(b"km.version(km.MAKCU)\r\n>>> "));
}
#[test]
fn a_frame_split_across_reads_waits_for_the_rest_and_an_empty_payload_is_whole() {
let mut codec = MakcuFrames::binary(Default::default());
let mut buffer = vec![0xDE, 0xAD, 0x04, 0x00, 0x04, 0xFE];
assert_eq!(codec.split(&mut buffer), None);
assert_eq!(buffer.len(), 6);
let mut buffer = vec![0xDE, 0xAD, 0x00];
assert_eq!(codec.split(&mut buffer), None);
let mut buffer = vec![0xDE, 0xAD, 0x00, 0x00, 0x52];
assert_eq!(
codec.split(&mut buffer),
Some(vec![0xDE, 0xAD, 0x00, 0x00, 0x52])
);
}
#[test]
fn junk_and_text_never_hold_up_the_events_behind_them() {
let event = vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x00, 0x01];
let mut bytes = b"\x00\xFFjunk km.MAKCU\r\n".to_vec();
bytes.extend(&event);
assert_eq!(
split_all(&mut MakcuFrames::binary(Default::default()), &bytes),
vec![event]
);
}
#[test]
fn a_corrupt_length_is_skipped_and_the_next_frame_still_parses() {
let device = frame(frames::DEVICE, &[0x03]);
let mut bytes = vec![0xDE, 0xAD, 0x41, 0x00, 0xDE];
bytes.extend(&device);
assert_eq!(
split_all(&mut MakcuFrames::binary(Default::default()), &bytes),
vec![device]
);
}
#[test]
fn a_v4_box_opens_with_its_identity() {
let rig = rig(State::makcu(4094));
let (_backend, snapshot, _poller, _signals) = rig.attach().unwrap();
assert_eq!(snapshot.info.family, Family::MakcuV4);
assert_eq!(snapshot.info.model.as_deref(), Some("MAKCU"));
assert_eq!(snapshot.info.firmware, Some(Firmware::Build(4094)));
assert_eq!(snapshot.info.id, Some(StableId::UsbSerial(SERIAL.into())));
assert_eq!(
snapshot.capabilities.buttons(),
[
Button::Left,
Button::Right,
Button::Middle,
Button::Side1,
Button::Side2
]
);
for action in [
Action::Move,
Action::MoveNow,
Action::Press,
Action::Click,
Action::ButtonEvents,
] {
assert_eq!(
snapshot.capabilities.get(action).execution,
Execution::Native,
"{action}"
);
}
for action in [Action::DiscardPendingMoves, Action::MotionEvents] {
assert!(
!snapshot.capabilities.get(action).is_supported(),
"{action}"
);
}
}
#[test]
fn the_probe_only_asks_and_switches_the_stream_off() {
let rig = rig(State::makcu(4094));
let _open = rig.attach().unwrap();
assert_eq!(
rig.written(),
vec![
frames::VERSION_QUERY.to_vec(),
frame(frames::FIRMWARE_VERSION, &[]),
frame(frames::DEVICE, &[]),
frame(frames::INPUT_STREAM, &[1, 0]),
frame(frames::INPUT_STREAM, &[2, 0]),
]
);
}
#[test]
fn a_box_switched_to_115200_is_found_there() {
let mut state = State::makcu(4094);
state.baud = 115_200;
let rig = rig(state);
let (_backend, snapshot, _poller, _signals) = rig.attach().unwrap();
assert_eq!(snapshot.info.firmware, Some(Firmware::Build(4094)));
assert_eq!(rig.system.opened_at(), vec![4_000_000, 115_200]);
}
#[test]
fn nothing_answering_at_either_rate_is_not_found() {
let mut state = State::makcu(4094);
state.text = None;
let rig = rig(state);
assert!(matches!(rig.attach(), Err(Error::NotFound { .. })));
let asked = rig.written();
assert_eq!(asked.len(), 4, "two tries per rate");
assert!(asked.iter().all(|bytes| bytes == frames::VERSION_QUERY));
}
#[test]
fn one_silent_identity_query_is_tried_again_at_the_same_rate() {
let mut state = State::makcu(4094);
state.silent_versions = 1;
state.silent_versions_left = 1;
let rig = rig(state);
assert!(rig.attach().is_ok());
assert_eq!(rig.system.opened_at(), vec![4_000_000]);
}
#[test]
fn junk_left_by_an_earlier_probe_is_skipped() {
let mut state = State::makcu(4094);
state.text = Some(b"ERR\r\n>>> km.MAKCU\r\n>>> ");
assert!(rig(state).attach().is_ok());
}
#[test]
fn a_box_that_only_answers_km_text_is_told_to_update() {
let mut state = State::makcu(4094);
state.build = None;
match rig(state).attach() {
Err(Error::FirmwareTooOld {
found: Firmware::Text(text),
needed: Firmware::Build(4085),
..
}) => assert_eq!(text, "V3 or older"),
other => panic!("{:?}", other.err()),
}
}
#[test]
fn the_firmware_gate_sits_at_4085_and_below_5000() {
assert!(matches!(
rig(State::makcu(4084)).attach().err(),
Some(Error::FirmwareTooOld {
found: Firmware::Build(4084),
needed: Firmware::Build(4085),
..
})
));
let (_backend, snapshot, _poller, _signals) = rig(State::makcu(4085)).attach().unwrap();
assert!(!snapshot.capabilities.get(Action::MoveNow).is_supported());
let (_backend, snapshot, _poller, _signals) = rig(State::makcu(4086)).attach().unwrap();
assert!(snapshot.capabilities.get(Action::MoveNow).is_supported());
assert!(matches!(
rig(State::makcu(5000)).attach().err(),
Some(Error::LibraryTooOld {
found: Firmware::Build(5000),
..
})
));
}
#[test]
fn a_port_that_says_its_another_box_is_never_asked() {
let ysc = Some(PortUsb {
vid: 0x1A86,
pid: 0xFE0C,
serial: Some("TOWMCULEFT".into()),
});
let rig = rig_with(State::makcu(4094), ysc);
assert!(matches!(rig.attach(), Err(Error::NotFound { .. })));
assert!(rig.system.opened_at().is_empty());
}
#[test]
fn a_ysc_style_answer_is_not_a_makcu() {
let mut state = State::makcu(4094);
state.text = Some(b"km.version(km.MAKCU)\r\n>>> ");
let rig = rig(state);
assert!(matches!(rig.attach(), Err(Error::NotFound { .. })));
assert!(
!rig.written()
.contains(&frame(frames::FIRMWARE_VERSION, &[]))
);
}
#[test]
fn a_port_with_no_usb_id_is_still_probed() {
let rig = rig_with(State::makcu(4094), None);
let (_backend, snapshot, _poller, _signals) = rig.attach().unwrap();
assert_eq!(snapshot.info.id, None);
}
#[test]
fn without_a_mouse_routed_mouse_output_says_so() {
let mut state = State::makcu(4094);
state.kinds = 0x02;
let rig = rig(state);
let (device, _poller) = rig.device();
rig.clear();
match device.move_by(1, 0) {
Err(Error::Unsupported { reason, .. }) => assert!(reason.contains("mouse"), "{reason}"),
other => panic!("{other:?}"),
}
assert!(device.snapshot().capabilities.buttons().is_empty());
assert!(
!device
.snapshot()
.capabilities
.get(Action::ButtonEvents)
.is_supported()
);
assert!(rig.written().is_empty());
}
#[test]
fn each_command_is_its_own_frame() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.clear();
device.move_by(10, -20).unwrap();
device.move_now(5, -5).unwrap();
device.press(Button::Side1).unwrap();
device.release(Button::Side1).unwrap();
device.wheel(-1).unwrap();
assert_eq!(
rig.written(),
vec![
vec![0xDE, 0xAD, 0x04, 0x00, 0x18, 0x0A, 0x00, 0xEC, 0xFF],
vec![0xDE, 0xAD, 0x04, 0x00, 0x67, 0x05, 0x00, 0xFB, 0xFF],
vec![0xDE, 0xAD, 0x01, 0x00, 0x14, 0x01],
vec![0xDE, 0xAD, 0x01, 0x00, 0x14, 0x00],
vec![0xDE, 0xAD, 0x02, 0x00, 0x19, 0xFF, 0xFF],
]
);
}
#[test]
fn a_click_lets_the_box_time_it_or_sends_the_hold_in_whole_ms_rounded_up() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.clear();
device.click(Button::Left).unwrap();
device
.click_with(Button::Right, Click::hold(Duration::from_millis(120)))
.unwrap();
device
.click_with(Button::Side2, Click::hold(Duration::from_micros(400)))
.unwrap();
device
.click_with(Button::Middle, Click::hold(Duration::ZERO))
.unwrap();
assert_eq!(
rig.written(),
vec![
vec![0xDE, 0xAD, 0x04, 0x00, 0x61, 0x01, 0x01, 0x00, 0x00],
vec![0xDE, 0xAD, 0x04, 0x00, 0x61, 0x02, 0x01, 0x78, 0x00],
vec![0xDE, 0xAD, 0x04, 0x00, 0x61, 0x05, 0x01, 0x01, 0x00],
vec![0xDE, 0xAD, 0x04, 0x00, 0x61, 0x03, 0x01, 0x01, 0x00],
]
);
}
#[test]
fn discard_is_unsupported_and_sends_nothing() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.clear();
assert!(matches!(
device.discard_pending_moves(),
Err(Error::Unsupported { .. })
));
assert!(rig.written().is_empty());
}
#[test]
fn release_all_releases_each_button() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
rig.clear();
backend
.release_all(&[
crate::backend::Usage::Button(Button::Left),
crate::backend::Usage::Button(Button::Side2),
])
.unwrap();
assert_eq!(rig.written(), vec![frame(0x11, &[0]), frame(0x15, &[0])]);
}
#[test]
fn the_backend_refuses_what_the_box_can_no_longer_do_but_always_releases() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
*backend.shared.snapshot.write().unwrap() = super::snapshot(4094, 0x02, None);
rig.clear();
assert!(matches!(
backend.send(Command::Move(1, 1)),
Err(Error::Unsupported { .. })
));
backend.send(Command::Release(Button::Left)).unwrap();
assert_eq!(rig.written(), vec![frame(0x11, &[0])]);
}
#[test]
fn streams_go_on_with_the_first_receiver_confirmed_and_off_with_the_last() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.clear();
let events = device.events();
assert_eq!(
rig.written(),
vec![
frame(0x52, &[1, 1]),
frame(0x52, &[1]),
frame(0x52, &[2, 1]),
frame(0x52, &[2])
]
);
rig.clear();
drop(events);
assert_eq!(
rig.written(),
vec![
frame(0x52, &[1, 0]),
frame(0x52, &[1]),
frame(0x52, &[2, 0]),
frame(0x52, &[2])
]
);
}
#[test]
fn a_stream_the_box_refuses_is_rejected() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse.insert(0x52);
});
assert!(matches!(
backend
.set_streams(InputKinds {
buttons: true,
..InputKinds::default()
})
.map_err(|failure| failure.error),
Err(Error::Rejected { .. })
));
until(|| {
signals
.try_iter()
.any(|signal| matches!(signal, Signal::Refused(Action::ButtonEvents)))
});
}
#[test]
fn a_refusal_a_get_was_waiting_for_is_its_answer_and_not_an_event() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse.insert(frames::INJECT_SNAPSHOT);
});
poller.tick();
assert!(
signals
.try_iter()
.all(|signal| !matches!(signal, Signal::Refused(_)))
);
}
#[test]
fn a_physical_click_comes_out_as_press_and_release() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
rig.fake
.send(&[0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x02, 0x01]);
rig.fake
.send(&[0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x02, 0x00]);
let first = events.recv_timeout(Duration::from_secs(2)).unwrap();
let second = events.recv_timeout(Duration::from_secs(2)).unwrap();
assert!(
matches!(first, Event::Button { button: Button::Middle, pressed: true, at } if at.box_time.is_none())
);
assert!(matches!(
second,
Event::Button {
button: Button::Middle,
pressed: false,
..
}
));
}
#[test]
fn an_overflow_reports_one_drop_and_turns_the_stream_back_on() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
rig.clear();
rig.fake
.send(&[0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0xFF, 0xFF]);
assert_eq!(
events.recv_timeout(Duration::from_secs(2)).unwrap(),
Event::Dropped {
count: 1,
source: DropSource::Device
}
);
until(|| rig.written().contains(&frame(0x52, &[1, 1])));
}
#[test]
fn a_refused_move_reaches_the_app_as_an_event() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
rig.set(|state| {
state.refuse.insert(0x18);
});
device.move_by(1, 0).unwrap();
let event = (0..3)
.filter_map(|_| events.recv_timeout(Duration::from_secs(2)).ok())
.find(|event| matches!(event, Event::Rejected { .. }));
assert_eq!(
event,
Some(Event::Rejected {
action: Action::Move,
count: 1
})
);
}
#[test]
fn a_box_that_lost_a_held_button_for_two_ticks_is_a_session_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
backend.send(Command::Press(Button::Left)).unwrap();
poller.tick();
assert!(link_events(&signals).is_empty());
rig.set(|state| state.injected = 0);
poller.tick();
assert!(
link_events(&signals).is_empty(),
"one tick could be a press still queued"
);
poller.tick();
assert_eq!(link_events(&signals), vec!["session reset"]);
}
#[test]
fn a_matching_tick_in_between_sets_the_count_back() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
backend.send(Command::Press(Button::Left)).unwrap();
poller.tick();
rig.set(|state| state.injected = 0);
poller.tick();
rig.set(|state| state.injected = 1);
poller.tick();
rig.set(|state| state.injected = 0);
poller.tick();
assert!(link_events(&signals).is_empty());
}
#[test]
fn a_wanted_stream_that_reads_off_for_two_ticks_is_a_session_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
backend
.set_streams(InputKinds {
buttons: true,
motion: false,
..InputKinds::default()
})
.unwrap();
rig.set(|state| state.stream = 0);
poller.tick();
poller.tick();
assert_eq!(link_events(&signals), vec!["session reset"]);
}
#[test]
fn nothing_held_and_no_stream_is_never_a_reset() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
for _ in 0..3 {
poller.tick();
}
assert!(link_events(&signals).is_empty());
}
#[test]
fn a_mouse_plugged_in_later_shows_up_on_the_fourth_tick() {
let mut state = State::makcu(4094);
state.kinds = 0x02;
let rig = rig(state);
let (_backend, snapshot, mut poller, signals) = rig.attach().unwrap();
assert!(snapshot.capabilities.buttons().is_empty());
rig.set(|state| state.kinds = 0x03);
for _ in 0..3 {
poller.tick();
}
assert!(link_events(&signals).is_empty());
poller.tick();
let fresh = signals.try_iter().find_map(|signal| match signal {
Signal::Reconnected(snapshot) => Some(snapshot),
_ => None,
});
assert_eq!(
fresh.map(|snapshot| snapshot.capabilities.buttons().len()),
Some(5)
);
}
#[test]
fn a_box_switched_to_the_other_rate_comes_back_there() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, _poller, signals) = rig.attach().unwrap();
rig.set(|state| state.baud = 115_200);
rig.fake.vanish();
let unplug = rig.system.clone();
unplug.unplug(PORT);
let fake = FakeBox::by_baud({
let state = rig.state.clone();
move |baud, bytes| respond(&state, baud, bytes)
});
rig.system.plug(PORT, makcu_usb(), fake);
until(|| {
let seen: Vec<_> = link_events(&signals);
seen.contains(&"reconnected")
});
assert!(rig.system.opened_at().ends_with(&[115_200]));
}
#[test]
fn another_box_on_the_same_port_is_not_taken_back() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, _poller, signals) = rig.attach().unwrap();
rig.system.unplug(PORT);
let other = Some(PortUsb {
vid: 0x1A86,
pid: 0x55D3,
serial: Some("5C00000000".into()),
});
let fake = FakeBox::by_baud({
let state = rig.state.clone();
move |baud, bytes| respond(&state, baud, bytes)
});
rig.system.plug(PORT, other, fake);
thread::sleep(Duration::from_millis(400));
assert_eq!(link_events(&signals), vec!["disconnected"]);
}
#[test]
fn shutdown_switches_the_stream_off() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
backend
.set_streams(InputKinds {
buttons: true,
motion: false,
..InputKinds::default()
})
.unwrap();
rig.clear();
backend.shutdown(Duration::from_millis(500));
assert!(rig.written().contains(&frame(0x52, &[1, 0])));
}
#[test]
fn a_press_the_box_refused_never_makes_a_reset_that_lets_go_of_a_real_hold() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
backend.send(Command::Press(Button::Right)).unwrap();
poller.tick();
rig.set(|state| {
state.refuse.insert(0x11);
});
backend.send(Command::Press(Button::Left)).unwrap();
for _ in 0..4 {
poller.tick();
}
assert!(link_events(&signals).is_empty());
assert_eq!(rig.state.lock().unwrap().injected, 0b10);
}
#[test]
fn a_press_still_queued_in_the_box_never_counts_as_lost() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
rig.set(|state| state.apply_presses = false);
backend.send(Command::Press(Button::Left)).unwrap();
for _ in 0..4 {
poller.tick();
}
assert!(link_events(&signals).is_empty());
}
#[test]
fn a_release_on_its_way_is_not_a_lost_hold() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
backend.send(Command::Press(Button::Left)).unwrap();
poller.tick();
backend.send(Command::Release(Button::Left)).unwrap();
rig.set(|state| state.injected = 0);
poller.tick();
poller.tick();
assert!(link_events(&signals).is_empty());
}
#[test]
fn a_stream_the_box_refused_once_is_asked_again_and_never_makes_a_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse_once.insert(0x52);
});
let on = InputKinds {
buttons: true,
motion: false,
..InputKinds::default()
};
assert!(backend.set_streams(on).is_err());
poller.tick();
poller.tick();
poller.tick();
assert_eq!(rig.state.lock().unwrap().stream, 1);
assert!(link_events(&signals).is_empty());
rig.set(|state| state.stream = 0);
rig.set(|state| {
state.refuse_once.clear();
});
poller.tick();
poller.tick();
assert_eq!(link_events(&signals), vec!["session reset"]);
}
#[test]
fn after_a_mouse_appears_moves_work() {
let mut state = State::makcu(4094);
state.kinds = 0x02;
let rig = rig(state);
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
assert!(backend.send(Command::Move(1, 0)).is_err());
rig.set(|state| state.kinds = 0x03);
for _ in 0..4 {
poller.tick();
}
backend.send(Command::Move(1, 0)).unwrap();
}
#[test]
fn a_frame_split_right_after_its_first_byte_still_arrives() {
let mut codec = MakcuFrames::binary(Default::default());
let device = frame(frames::DEVICE, &[0x03]);
let mut buffer = vec![0x00, device[0]];
assert_eq!(codec.split(&mut buffer), None);
buffer.extend(&device[1..]);
assert_eq!(codec.split(&mut buffer), Some(device));
}
#[test]
fn while_probing_text_ends_where_a_binary_frame_starts() {
let device = frame(frames::DEVICE, &[0x03]);
let mut bytes = b"km.MAK".to_vec();
bytes.extend(&device);
let frames = split_all(&mut MakcuFrames::probe(), &bytes);
assert_eq!(frames, vec![b"km.MAK".to_vec(), device]);
}
#[test]
fn a_button_state_past_one_is_noise() {
let odd = vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x00, 0x02];
assert!(matches!(
MakcuFrames::binary(Default::default()).classify(&odd),
Incoming::Noise
));
}
#[test]
fn a_busy_port_says_busy() {
let rig = rig(State::makcu(4094));
rig.system.refuse(Some(if cfg!(windows) { 5 } else { 16 }));
assert!(matches!(rig.attach(), Err(Error::PortBusy { .. })));
}
#[test]
fn one_silent_firmware_query_is_asked_again() {
let mut state = State::makcu(4094);
state.silent_builds_left = 1;
assert!(rig(state).attach().is_ok());
}
#[test]
fn a_malformed_firmware_reply_is_too_old_never_not_found() {
let mut state = State::makcu(4094);
state.build_reply = Some(vec![0xFE, 0x0F]);
assert!(matches!(
rig(state).attach().err(),
Some(Error::FirmwareTooOld { .. })
));
}
#[test]
fn a_refused_re_enable_after_an_overflow_is_asked_again_not_a_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
let on = InputKinds {
buttons: true,
motion: false,
..InputKinds::default()
};
backend.set_streams(on).unwrap();
poller.tick();
rig.set(|state| {
state.stream = 0;
state.refuse_once.insert(0x52);
});
assert!(backend.restart_stream(InputKind::Buttons).is_err());
poller.tick();
poller.tick();
assert!(link_events(&signals).is_empty());
assert_eq!(rig.state.lock().unwrap().stream, 1);
}
#[test]
fn a_stream_nobody_asked_for_is_never_switched_on() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
rig.clear();
for _ in 0..4 {
poller.tick();
}
assert!(!rig.written().contains(&frame(0x52, &[1, 1])));
}
#[test]
fn a_late_refusal_of_the_backends_own_query_is_no_event() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
rig.fake.send(&frame(frames::INJECT_SNAPSHOT, &[0xFF]));
rig.fake
.send(&[0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x00, 0x01]);
assert!(matches!(
events.recv_timeout(Duration::from_secs(2)),
Ok(Event::Button { .. })
));
assert!(events.try_recv().is_err());
}
#[test]
fn three_notches_go_out_as_one_wheel_command() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.clear();
device.wheel(3).unwrap();
assert_eq!(rig.written(), vec![frame(frames::WHEEL, &[0x03, 0x00])]);
}
#[test]
fn a_release_the_box_refused_goes_again_and_a_refused_release_says_release() {
let rig = rig(State::makcu(4094));
let (device, mut poller) = rig.device();
let events = device.events();
device.press(Button::Left).unwrap();
rig.set(|state| {
state.refuse_once.insert(0x11);
});
device.release(Button::Left).unwrap();
let event = (0..3)
.filter_map(|_| events.recv_timeout(Duration::from_secs(2)).ok())
.find(|event| matches!(event, Event::Rejected { .. }));
assert_eq!(
event,
Some(Event::Rejected {
action: Action::Release,
count: 1
})
);
assert_eq!(rig.state.lock().unwrap().injected, 1);
poller.tick();
assert_eq!(rig.state.lock().unwrap().injected, 0);
}
#[test]
fn a_held_press_is_never_released_by_the_poller() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend.send(Command::Press(Button::Left)).unwrap();
poller.tick();
poller.tick();
assert_eq!(rig.state.lock().unwrap().injected, 1);
}
#[test]
fn a_refused_press_through_the_backend_says_press() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse.insert(0x12);
});
backend.send(Command::Press(Button::Right)).unwrap();
until(|| {
signals
.try_iter()
.any(|signal| matches!(signal, Signal::Refused(Action::Press)))
});
}
#[test]
fn nothing_held_means_the_poller_writes_no_releases() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
rig.clear();
poller.tick();
poller.tick();
assert!(
rig.written()
.iter()
.all(|bytes| !matches!(frames::parts(bytes), Some((0x11..=0x15, _))))
);
}
#[test]
fn a_running_box_click_is_never_cut_short_by_the_poller() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend
.send(Command::Click {
button: Button::Left,
hold: Some(Duration::from_secs(5)),
})
.unwrap();
rig.clear();
poller.tick();
poller.tick();
assert!(!rig.written().contains(&frame(0x11, &[0])));
}
mod hardware {
use std::time::{Duration, Instant};
use crate::capability::{Action, Button, Family, Firmware, StableId};
use crate::click::Click;
use crate::connect::Connect;
use crate::error::Error;
use crate::event::{Event, RecvTimeoutError};
fn port() -> String {
std::env::var("ANYBOX_PORT").expect("set ANYBOX_PORT to the port your box is on")
}
fn open() -> crate::device::Device {
Connect::port(port())
.family(Family::MakcuV4)
.open()
.unwrap_or_else(|error| panic!("{error}"))
}
#[cfg(windows)]
#[allow(unsafe_code)]
mod beeps {
#[link(name = "kernel32")]
unsafe extern "system" {
fn Beep(frequency: u32, millis: u32) -> i32;
}
pub(super) fn beep(high: bool) {
unsafe {
Beep(if high { 1500 } else { 500 }, 400);
}
}
pub(super) fn done() {
beep(false);
std::thread::sleep(std::time::Duration::from_millis(150));
beep(false);
}
}
#[cfg(windows)]
#[test]
#[ignore]
fn received_time_next_to_a_qpc_read_right_after() {
use crate::event::EventKinds;
use crate::transport::os::Read;
let device = open();
let events = device.events_of(EventKinds::BUTTONS);
beeps::beep(true);
let deadline = Instant::now() + Duration::from_secs(30);
let mut gaps = Vec::new();
let mut last_qpc = 0i64;
while gaps.len() < 20 {
let Some(left) = deadline.checked_duration_since(Instant::now()) else {
break;
};
match events.recv_timeout(left) {
Ok(Event::Button { at, .. }) => {
let after = Read::now();
let qpc = at.received_qpc.expect("a QPC on Windows");
assert!(qpc >= last_qpc, "received went backwards");
last_qpc = qpc;
let ticks = after.qpc.unwrap() - qpc;
let elapsed = after.at.duration_since(at.received);
assert!(ticks >= 0, "the QPC read after came before received");
gaps.push((ticks, elapsed));
}
Ok(_) => {}
Err(RecvTimeoutError::Timeout) => break,
Err(error) => panic!("{error:?}"),
}
}
beeps::done();
for (ticks, elapsed) in &gaps {
println!("qpc ticks after received {ticks:>7}, instant {elapsed:?}");
}
assert!(gaps.len() >= 5, "only {} clicks came in", gaps.len());
assert!(
gaps.iter()
.all(|(_, elapsed)| *elapsed < Duration::from_millis(50)),
"an event took more than 50 ms from the read to the app"
);
}
#[test]
#[ignore]
fn a_real_box_opens_with_its_identity() {
let device = open();
let snapshot = device.snapshot();
println!("{:?}", snapshot.info);
println!("buttons {:?}", snapshot.capabilities.buttons());
assert_eq!(snapshot.info.family, Family::MakcuV4);
assert!(matches!(snapshot.info.firmware, Some(Firmware::Build(_))));
assert!(matches!(snapshot.info.id, Some(StableId::UsbSerial(_))));
}
#[test]
#[ignore]
fn a_second_open_is_busy_and_reopens_right_after_a_drop_work() {
let first = open();
let second = Connect::port(port()).family(Family::MakcuV4).open();
assert!(
matches!(second, Err(Error::PortBusy { .. })),
"{:?}",
second.err()
);
drop(first);
for round in 0..10 {
let device = open();
device
.move_by(0, 0)
.unwrap_or_else(|error| panic!("round {round}: {error}"));
}
}
#[test]
#[ignore]
fn the_snapshot_shows_held_keys_where_the_docs_say() {
use super::super::frames::{self, frame};
use crate::transport::System;
let port = crate::transport::os::OsSystem
.open(std::path::Path::new(&port()), 4_000_000)
.unwrap();
port.write_all(&frame(frames::KEY_DOWN, &[0x68])).unwrap();
port.write_all(&frame(frames::KEY_DOWN, &[0xE4])).unwrap();
std::thread::sleep(Duration::from_millis(50));
port.write_all(&frame(frames::INJECT_SNAPSHOT, &[]))
.unwrap();
std::thread::sleep(Duration::from_millis(100));
let mut chunk = [0u8; 256];
let got = port.read(&mut chunk).unwrap_or(0);
port.write_all(&frame(frames::KEY_UP, &[0x68])).unwrap();
port.write_all(&frame(frames::KEY_UP, &[0xE4])).unwrap();
println!("{:02X?}", &chunk[..got]);
let reply = &chunk[..got];
let at = reply
.windows(5)
.position(|window| window == [0xDE, 0xAD, 40, 0, frames::INJECT_SNAPSHOT])
.expect("the box didn't send back a 40 byte snapshot");
let payload = &reply[at + 5..at + 45];
assert_eq!(frames::injected_keys(payload), vec![0xE4, 0x68]);
}
#[test]
#[ignore]
fn a_key_pressed_right_after_a_box_tap() {
use super::super::frames::{self, frame};
use crate::transport::System;
let port = crate::transport::os::OsSystem
.open(std::path::Path::new(&port()), 4_000_000)
.unwrap();
let read = |what: &str| {
std::thread::sleep(Duration::from_millis(30));
let mut chunk = [0u8; 512];
let got = port.read(&mut chunk).unwrap_or(0);
let reply = &chunk[..got];
let held = reply
.windows(5)
.position(|window| window == [0xDE, 0xAD, 40, 0, frames::INJECT_SNAPSHOT])
.map(|at| frames::injected_keys(&reply[at + 5..at + 45]));
println!("{what}: held {held:02X?}, all bytes {reply:02X?}");
};
let snapshot = || {
port.write_all(&frame(frames::INJECT_SNAPSHOT, &[]))
.unwrap();
};
for gap in [0u64, 10, 50, 100, 300] {
port.write_all(&frame(frames::KEY_PRESS, &[0x69])).unwrap();
std::thread::sleep(Duration::from_millis(gap));
port.write_all(&frame(frames::KEY_DOWN, &[0x68])).unwrap();
std::thread::sleep(Duration::from_millis(50));
snapshot();
read(&format!("tap then down after {gap} ms"));
port.write_all(&frame(frames::KEY_UP, &[0x68])).unwrap();
std::thread::sleep(Duration::from_millis(100));
let mut drain = [0u8; 512];
let _ = port.read(&mut drain);
}
port.write_all(&frame(frames::KEY_DOWN, &[0x68])).unwrap();
std::thread::sleep(Duration::from_millis(50));
snapshot();
read("down with no tap before");
port.write_all(&frame(frames::KEY_UP, &[0x68])).unwrap();
}
#[test]
#[ignore]
fn how_the_x_lock_targets_relate() {
use super::super::frames::{self, frame};
use crate::transport::System;
let port = crate::transport::os::OsSystem
.open(std::path::Path::new(&port()), 4_000_000)
.unwrap();
let bits = |what: &str| {
std::thread::sleep(Duration::from_millis(50));
let mut drain = [0u8; 512];
let _ = port.read(&mut drain);
port.write_all(&frame(frames::INJECT_SNAPSHOT, &[]))
.unwrap();
std::thread::sleep(Duration::from_millis(50));
let mut chunk = [0u8; 512];
let got = port.read(&mut chunk).unwrap_or(0);
let reply = &chunk[..got];
let locks = reply
.windows(5)
.position(|window| window == [0xDE, 0xAD, 40, 0, frames::INJECT_SNAPSHOT])
.map(|at| frames::locked_targets(&reply[at + 5..at + 45]));
println!("{what}: {:?}", locks.map(|bits| format!("{bits:014b}")));
};
let set = |target: u8, on: u8| {
port.write_all(&frame(frames::LOCK, &[target, on])).unwrap();
};
bits("nothing");
set(5, 1);
bits("lock 5");
set(5, 0);
bits("unlock 5");
set(6, 1);
set(7, 1);
bits("lock 6 and 7");
set(6, 0);
set(7, 0);
set(5, 1);
set(6, 0);
bits("lock 5 then unlock 6");
for target in 5..8 {
set(target, 0);
}
bits("all off");
}
#[test]
#[ignore]
fn refused_and_unknown_text_commands_reply() {
use crate::transport::System;
let port = crate::transport::os::OsSystem
.open(std::path::Path::new(&port()), 4_000_000)
.unwrap();
for command in [
&b"km.left(9)\r\n"[..],
b"km.nothing()\r\n",
b"km.version()\r\n",
] {
port.write_all(command).unwrap();
std::thread::sleep(Duration::from_millis(300));
let mut chunk = [0u8; 256];
let got = port.read(&mut chunk).unwrap_or(0);
println!(
"{:?} -> {:02X?} {:?}",
String::from_utf8_lossy(command),
&chunk[..got],
String::from_utf8_lossy(&chunk[..got])
);
}
}
#[test]
#[ignore]
fn fifty_opens_each_answer_right_away() {
let mut slowest = Duration::ZERO;
for round in 0..50 {
let started = Instant::now();
let device = open();
slowest = slowest.max(started.elapsed());
drop(device);
assert!(started.elapsed() < Duration::from_secs(2), "round {round}");
}
println!("slowest open {slowest:?}");
}
#[test]
#[ignore]
fn connect_without_a_family_still_finds_it_after_the_medius_probe() {
let device = Connect::port(port())
.open()
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(device.snapshot().info.family, Family::MakcuV4);
}
#[test]
#[ignore]
fn the_cursor_moves_right_and_back() {
let device = open();
for _ in 0..20 {
device.move_by(2, 0).unwrap();
std::thread::sleep(Duration::from_millis(10));
}
for _ in 0..20 {
device.move_now(-2, 0).unwrap();
std::thread::sleep(Duration::from_millis(10));
}
}
#[test]
#[ignore]
fn a_box_right_click_reaches_the_pc() {
let device = open();
std::thread::sleep(Duration::from_secs(3));
device.click(Button::Right).unwrap();
}
#[test]
#[ignore]
fn three_notches_in_one_command_each_way() {
let device = open();
std::thread::sleep(Duration::from_secs(2));
device.wheel(-3).unwrap();
std::thread::sleep(Duration::from_secs(2));
device.wheel(3).unwrap();
std::thread::sleep(Duration::from_millis(500));
}
#[test]
#[ignore]
fn unplugging_usb1_shows_what_the_box_lost() {
let device = open();
let events = device.events();
device.press(Button::Middle).unwrap();
println!("middle held, unplug USB1 now, then plug it back in");
let started = Instant::now();
while started.elapsed() < Duration::from_secs(40) {
if let Ok(event) = events.recv_timeout(Duration::from_millis(200))
&& !matches!(event, Event::Button { .. })
{
println!(
"{event:?} after {:?}, held {:?}",
started.elapsed(),
device.held()
);
}
}
}
#[test]
#[ignore]
fn box_click_holds_for_a_capture() {
let device = open();
std::thread::sleep(Duration::from_secs(1));
for hold in [None, Some(120), Some(1), Some(5000)] {
let click =
hold.map_or_else(Click::default, |ms| Click::hold(Duration::from_millis(ms)));
println!("middle click, hold {hold:?} ms, at {:.6}", unix_now());
device.click_with(Button::Middle, click).unwrap();
std::thread::sleep(Duration::from_millis(5500));
}
}
#[test]
#[ignore]
fn a_held_button_is_let_go_when_the_device_is_dropped() {
let device = open();
std::thread::sleep(Duration::from_secs(2));
device.press(Button::Right).unwrap();
std::thread::sleep(Duration::from_secs(1));
drop(device);
}
#[test]
#[ignore]
fn all_five_physical_buttons_arrive_in_order() {
let device = open();
let events = device.events();
println!("press left, right, middle, back and forward now, one after another");
let mut pressed = Vec::new();
let until = Instant::now() + Duration::from_secs(30);
while pressed.len() < 5 && Instant::now() < until {
match events.recv_timeout(Duration::from_millis(200)) {
Ok(Event::Button {
button,
pressed: true,
..
}) => {
println!("{button}");
pressed.push(button);
}
Ok(_) | Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Closed) => break,
}
}
assert_eq!(
pressed,
[
Button::Left,
Button::Right,
Button::Middle,
Button::Side1,
Button::Side2
]
);
}
#[test]
#[ignore]
fn unplugging_the_mouse_shows_in_the_snapshot() {
let device = open();
let events = device.events();
println!("unplug the mouse from the box now, then plug it back in");
let started = Instant::now();
let mut seen = Vec::new();
while started.elapsed() < Duration::from_secs(40) {
if let Ok(event) = events.recv_timeout(Duration::from_millis(200))
&& !matches!(event, Event::Button { .. })
{
let routed = device
.snapshot()
.capabilities
.get(Action::Move)
.is_supported();
println!(
"{event:?} after {:?}, mouse routed {routed}",
started.elapsed()
);
seen.push(event);
}
}
assert!(!seen.is_empty(), "the box reported nothing");
}
#[test]
#[ignore]
fn unplugging_the_control_cable_disconnects_and_reconnects() {
let device = open();
let events = device.events();
device.press(Button::Middle).unwrap();
println!("middle held at {:.6}", unix_now());
println!("unplug the control cable now, then plug it back in");
let started = Instant::now();
let mut seen = Vec::new();
while !seen.contains(&"reconnected") && started.elapsed() < Duration::from_secs(45) {
match events.recv_timeout(Duration::from_millis(200)) {
Ok(Event::Disconnected) => {
println!(
"disconnected after {:?}, at {:.6}",
started.elapsed(),
unix_now()
);
seen.push("disconnected");
}
Ok(Event::Reconnected) => {
println!(
"reconnected after {:?}, at {:.6}",
started.elapsed(),
unix_now()
);
seen.push("reconnected");
}
_ => {}
}
}
assert_eq!(seen, vec!["disconnected", "reconnected"]);
device.move_by(1, 0).unwrap();
device.move_by(-1, 0).unwrap();
}
#[test]
#[ignore]
fn a_flood_of_moves_shows_what_the_box_refuses() {
let device = open();
let events = device.events();
let started = Instant::now();
let mut sent = 0u32;
while started.elapsed() < Duration::from_secs(1) {
device
.move_by(if sent.is_multiple_of(2) { 1 } else { -1 }, 0)
.unwrap();
sent += 1;
}
std::thread::sleep(Duration::from_millis(500));
let rejected: u64 = std::iter::from_fn(|| events.try_recv().ok())
.map(|event| match event {
Event::Rejected { count, .. } => count,
_ => 0,
})
.sum();
println!("sent {sent} moves, {rejected} rejected");
}
fn moves_at(
device: &crate::device::Device,
events: &crate::event::Events,
per_second: Option<u32>,
) -> (u32, u64) {
let started = Instant::now();
let mut sent = 0u32;
while started.elapsed() < Duration::from_secs(1) {
if let Some(rate) = per_second {
let due = started + Duration::from_secs_f64(f64::from(sent) / f64::from(rate));
while Instant::now() < due {}
}
device
.move_by(
if sent.is_multiple_of(2) || std::env::var("ONE_WAY").is_ok() {
1
} else {
-1
},
0,
)
.unwrap();
sent += 1;
}
std::thread::sleep(Duration::from_secs(1));
let rejected: u64 = std::iter::from_fn(|| events.try_recv().ok())
.map(|event| match event {
Event::Rejected { count, .. } => count,
_ => 0,
})
.sum();
(sent, rejected)
}
#[test]
#[ignore]
fn how_many_moves_a_second_the_box_takes() {
let device = open();
let events = device.events();
for rate in [None, Some(1000), Some(2000)] {
println!("phase {rate:?} starts at {:.6}", unix_now());
let (sent, rejected) = moves_at(&device, &events, rate);
let accepted = u64::from(sent) - rejected;
println!("{rate:?} per second: sent {sent}, rejected {rejected}, accepted {accepted}");
std::thread::sleep(Duration::from_millis(500));
}
}
fn unix_now() -> f64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64()
}
}
#[test]
fn keys_go_out_as_their_own_opcodes() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
rig.clear();
backend.send(Command::KeyPress(Key::A)).unwrap();
backend.send(Command::KeyRelease(Key::A)).unwrap();
backend
.send(Command::KeyTap {
key: Key::B,
hold: None,
})
.unwrap();
backend
.send(Command::KeyTap {
key: Key::B,
hold: Some(Duration::from_millis(300)),
})
.unwrap();
assert_eq!(
rig.written(),
vec![
frame(0x20, &[0x04]),
frame(0x21, &[0x04]),
frame(0x23, &[0x05]),
frame(0x23, &[0x05, 0x2C, 0x01, 0x00, 0x00]),
]
);
}
#[test]
fn keys_follow_the_keyboard_bit_and_media_keys_are_never_there() {
for (kinds, keys) in [(0x03, true), (0x01, false)] {
let rig = rig(State {
kinds,
..State::makcu(4094)
});
let (_backend, snapshot, _poller, _signals) = rig.attach().unwrap();
let capabilities = &snapshot.capabilities;
for action in [Action::KeyPress, Action::KeyRelease, Action::KeyTap] {
assert_eq!(capabilities.get(action).is_supported(), keys, "{action}");
}
if !keys {
match &capabilities.get(Action::KeyPress).execution {
Execution::Unsupported { reason } => {
assert!(reason.contains("no keyboard interface"), "{reason}");
}
other => panic!("{other:?}"),
}
}
for action in [Action::MediaPress, Action::MediaRelease, Action::MediaTap] {
assert!(!capabilities.get(action).is_supported(), "{action}");
}
}
}
#[test]
fn a_keyboard_routed_or_pulled_after_connect_shows_up_on_the_fourth_tick() {
let rig = rig(State::makcu(4094));
let (_backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
rig.set(|state| state.kinds = 0x01);
for _ in 0..4 {
poller.tick();
}
let signals: Vec<Signal> = signals.try_iter().collect();
assert!(
!signals
.iter()
.any(|signal| matches!(signal, Signal::Changed(_)))
);
let snapshots: Vec<Snapshot> = signals
.into_iter()
.filter_map(|signal| match signal {
Signal::Reconnected(snapshot) => Some(*snapshot),
_ => None,
})
.collect();
assert_eq!(snapshots.len(), 1);
assert!(
!snapshots[0]
.capabilities
.get(Action::KeyPress)
.is_supported()
);
assert!(snapshots[0].capabilities.get(Action::Move).is_supported());
}
#[test]
fn each_refused_key_opcode_says_which_key_action() {
let mut codec = MakcuFrames::binary(Arc::new(frames::LastSent::default()));
for (command, expected) in [
(0x20, Action::KeyPress),
(0x21, Action::KeyRelease),
(0x23, Action::KeyTap),
] {
match codec.classify(&frame(command, &[0xFF])) {
Incoming::Refused { action, .. } => assert_eq!(action, Some(expected)),
other => panic!("{command:02X} gave {other:?}"),
}
}
}
#[test]
fn a_key_release_the_box_lost_goes_again_and_a_held_key_is_left_alone() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend.send(Command::KeyPress(Key::B)).unwrap();
backend.send(Command::KeyPress(Key::LEFT_SHIFT)).unwrap();
rig.set(|state| {
state.keys.insert(0x04);
state.keys.insert(0xE4);
});
rig.clear();
poller.tick();
let written = rig.written();
assert!(written.contains(&frame(0x21, &[0x04])), "{written:02X?}");
assert!(written.contains(&frame(0x21, &[0xE4])), "{written:02X?}");
assert!(!written.contains(&frame(0x21, &[0x05])));
assert!(!written.contains(&frame(0x21, &[0xE1])));
assert_eq!(rig.state.lock().unwrap().keys, HashSet::from([0x05, 0xE1]));
}
#[test]
fn a_held_key_never_makes_a_session_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
backend.send(Command::KeyPress(Key::A)).unwrap();
rig.set(|state| state.keys.clear());
for _ in 0..3 {
poller.tick();
}
assert!(link_events(&signals).is_empty());
}
#[test]
fn release_all_lets_go_of_each_key_and_never_sends_key_init() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
rig.clear();
backend
.release_all(&[Usage::Key(Key::A), Usage::Button(Button::Left)])
.unwrap();
let written = rig.written();
assert!(written.contains(&frame(0x21, &[0x04])));
assert!(written.contains(&frame(0x11, &[0x00])));
assert!(
written
.iter()
.all(|bytes| frames::parts(bytes).map(|(c, _)| c) != Some(0x22))
);
}
#[test]
fn a_box_timed_tap_is_capped_like_a_click() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
assert!(matches!(
device.tap_key_with(Key::A, Click::hold(Duration::from_millis(5001))),
Err(Error::InvalidArgument { .. })
));
rig.clear();
device
.tap_key_with(Key::A, Click::hold(Duration::from_millis(5000)))
.unwrap();
assert_eq!(
rig.written(),
vec![frame(0x23, &[0x04, 0x88, 0x13, 0x00, 0x00])]
);
}
#[test]
fn injected_keys_reads_modifiers_and_the_bitmap() {
let mut snapshot = vec![0u8; 40];
snapshot[3] = 0b1000_0001;
snapshot[4] = 0b0001_0000;
snapshot[35] = 0b0000_0001;
assert_eq!(
frames::injected_keys(&snapshot),
vec![0xE0, 0xE7, 0x04, 0xF8]
);
assert!(frames::injected_keys(&[1]).is_empty());
}
#[test]
fn a_key_release_the_box_refused_goes_again() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend.send(Command::KeyPress(Key::A)).unwrap();
rig.set(|state| {
state.refuse_once.insert(frames::KEY_UP);
});
backend.send(Command::KeyRelease(Key::A)).unwrap();
until(|| rig.state.lock().unwrap().refuse_once.is_empty());
assert!(rig.state.lock().unwrap().keys.contains(&0x04));
poller.tick();
assert!(!rig.state.lock().unwrap().keys.contains(&0x04));
}
#[test]
fn a_running_box_tap_is_never_cut_short_by_the_poller() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend
.send(Command::KeyTap {
key: Key::A,
hold: Some(Duration::from_millis(300)),
})
.unwrap();
rig.set(|state| {
state.keys.insert(0x04);
});
rig.clear();
poller.tick();
assert!(!rig.written().contains(&frame(frames::KEY_UP, &[0x04])));
}
fn key_frame(usage: u8, state: u8) -> Vec<u8> {
vec![0xDE, 0xAD, 0x03, 0x00, 0x53, 0x02, usage, state]
}
#[test]
fn physical_keys_arrive_as_key_events_and_odd_ones_are_noise() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
for frame in [
key_frame(0xE1, 1),
key_frame(0x00, 1),
key_frame(0x03, 1),
key_frame(0x04, 2),
key_frame(0x04, 1),
key_frame(0x04, 0),
] {
rig.fake.send(&frame);
}
let mut seen = Vec::new();
while seen.len() < 3 {
if let Ok(Event::Key { key, pressed, .. }) = events.recv_timeout(Duration::from_secs(2)) {
seen.push((key, pressed));
}
}
assert_eq!(
seen,
[(Key::LEFT_SHIFT, true), (Key::A, true), (Key::A, false)]
);
assert!(
events
.recv_timeout(Duration::from_millis(100))
.is_err_and(|error| error == crate::event::RecvTimeoutError::Timeout)
);
}
#[test]
fn a_keyboard_overflow_restarts_only_the_keyboard_stream() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
rig.clear();
rig.fake
.send(&[0xDE, 0xAD, 0x03, 0x00, 0x53, 0x02, 0xFF, 0xFF]);
assert_eq!(
events.recv_timeout(Duration::from_secs(2)).unwrap(),
Event::Dropped {
count: 1,
source: DropSource::Device
}
);
until(|| rig.written().contains(&frame(0x52, &[2, 1])));
assert!(!rig.written().contains(&frame(0x52, &[1, 1])));
}
#[test]
fn a_keyboard_stream_never_confirmed_is_asked_again_and_never_a_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse_once.insert(0x52);
});
let keys = InputKinds {
keys: true,
..InputKinds::default()
};
assert!(backend.set_streams(keys).is_err());
rig.set(|state| state.key_stream = 0);
for _ in 0..3 {
poller.tick();
}
assert!(link_events(&signals).is_empty());
assert_eq!(rig.state.lock().unwrap().key_stream, 1);
}
#[test]
fn a_keyboard_stream_that_reads_off_is_one_drop_and_switched_on_again_never_a_reset() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, signals) = rig.attach().unwrap();
let both = InputKinds {
buttons: true,
keys: true,
..InputKinds::default()
};
backend.set_streams(both).unwrap();
rig.set(|state| state.key_stream = 0);
for _ in 0..3 {
poller.tick();
}
let signals: Vec<Signal> = signals.try_iter().collect();
assert!(
!signals
.iter()
.any(|signal| matches!(signal, Signal::SessionReset)),
"{signals:?}"
);
assert_eq!(
signals
.iter()
.filter(|signal| matches!(signal, Signal::Dropped(1, DropSource::Device, _)))
.count(),
1
);
assert_eq!(rig.state.lock().unwrap().key_stream, 1);
}
#[test]
fn a_keyboard_stream_query_the_box_refuses_never_stops_the_tick() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend
.set_streams(InputKinds {
keys: true,
..InputKinds::default()
})
.unwrap();
rig.set(|state| {
state.injected = 1;
state.refuse.insert(0x52);
});
poller.tick();
assert_eq!(rig.state.lock().unwrap().injected, 0);
}
#[test]
fn a_refused_keyboard_stream_switch_names_key_events() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse.insert(0x52);
});
assert!(matches!(
backend
.set_streams(InputKinds {
keys: true,
..InputKinds::default()
})
.map_err(|failure| failure.error),
Err(Error::Rejected {
action: Action::KeyEvents,
..
})
));
let mut codec = MakcuFrames::binary(backend.shared.last.clone());
assert!(matches!(
codec.classify(&frame(0x52, &[0xFF])),
Incoming::Refused {
action: Some(Action::KeyEvents),
..
}
));
}
#[test]
fn the_keyboard_going_away_turns_its_stream_off_with_no_reset() {
let rig = rig(State::makcu(4094));
let (device, mut poller) = rig.device();
let _events = device.events();
assert_eq!(rig.state.lock().unwrap().key_stream, 1);
rig.set(|state| state.kinds = 0x01);
let reset = device.events();
for _ in 0..4 {
poller.tick();
}
device.settle();
until(|| rig.state.lock().unwrap().key_stream == 0);
assert!(
!std::iter::from_fn(|| reset.try_recv().ok()).any(|event| event == Event::SessionReset)
);
}
#[test]
fn shutdown_switches_both_streams_off() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
assert_eq!(rig.state.lock().unwrap().key_stream, 1);
drop(events);
let _events = device.events();
drop(device);
let state = rig.state.lock().unwrap();
assert_eq!((state.stream, state.key_stream), (0, 0));
}
#[test]
fn a_box_without_the_keyboard_bit_never_gets_its_stream_switched_on() {
let rig = rig(State {
kinds: 0x01,
..State::makcu(4094)
});
let (device, _poller) = rig.device();
rig.clear();
let _events = device.events();
assert!(!rig.written().contains(&frame(0x52, &[2, 1])));
}
#[test]
fn a_refused_keyboard_stream_retry_names_key_events() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse_once.insert(0x52);
});
let _ = backend.set_streams(InputKinds {
keys: true,
..InputKinds::default()
});
backend.shared.last.set_stream(frames::MOUSE);
rig.set(|state| state.key_stream = 0);
poller.tick();
let mut codec = MakcuFrames::binary(backend.shared.last.clone());
assert!(matches!(
codec.classify(&frame(0x52, &[0xFF])),
Incoming::Refused {
action: Some(Action::KeyEvents),
..
}
));
}
#[test]
fn restarting_a_stream_nobody_wants_sends_nothing() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
rig.clear();
backend.restart_stream(InputKind::Keys).unwrap();
assert!(rig.written().is_empty());
}
#[test]
fn a_keyboard_stream_lost_with_its_bit_gone_isnt_asked_again() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
backend
.set_streams(InputKinds {
keys: true,
..InputKinds::default()
})
.unwrap();
rig.set(|state| {
state.key_stream = 0;
state.kinds = 0x01;
});
for _ in 0..4 {
poller.tick();
}
rig.clear();
rig.set(|state| state.key_stream = 0);
for _ in 0..3 {
poller.tick();
}
assert!(!rig.written().contains(&frame(0x52, &[2, 1])));
}
fn lock(backend: &MakcuBackend, slot: LockSlot, keep: i16) -> crate::error::Result<()> {
backend.send(Command::Lock { slot, keep })
}
#[test]
fn locks_go_out_as_lock_and_key_mask_with_the_signed_axis_targets() {
let rig = rig(State::makcu(4094));
let (backend, snapshot, _poller, _signals) = rig.attach().unwrap();
assert_eq!(
snapshot.capabilities.axes(),
[Axis::X, Axis::Y, Axis::Wheel]
);
assert!(!snapshot.capabilities.get(Action::ScaleAxis).is_supported());
rig.clear();
lock(&backend, LockSlot::Button(Button::Side2), 0).unwrap();
for (axis, positive, negative) in [(Axis::X, 6, 7), (Axis::Y, 9, 10), (Axis::Wheel, 12, 13)] {
lock(&backend, LockSlot::Axis(axis, Sign::Positive), 0).unwrap();
lock(&backend, LockSlot::Axis(axis, Sign::Negative), 100).unwrap();
assert!(rig.written().contains(&frame(0x60, &[positive, 1])));
assert!(rig.written().contains(&frame(0x60, &[negative, 0])));
}
lock(&backend, LockSlot::Key(Key::A), 0).unwrap();
assert!(rig.written().contains(&frame(0x60, &[4, 1])));
assert!(rig.written().contains(&frame(0x29, &[0x04, 1])));
assert!(matches!(
lock(&backend, LockSlot::Axis(Axis::X, Sign::Positive), 50),
Err(Error::Unsupported { .. })
));
}
#[test]
fn a_lock_the_box_lost_goes_again_and_someone_elses_is_left_alone() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
rig.set(|state| state.locks = 1 << 1);
poller.tick();
assert_eq!(rig.state.lock().unwrap().locks, 0b11);
}
#[test]
fn an_unlock_the_box_didnt_take_goes_again() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
rig.set(|state| {
state.refuse_once.insert(0x60);
});
lock(&backend, LockSlot::Button(Button::Left), 100).unwrap();
until(|| rig.state.lock().unwrap().refuse_once.is_empty());
assert_eq!(rig.state.lock().unwrap().locks, 1);
poller.tick();
assert_eq!(rig.state.lock().unwrap().locks, 0);
}
#[test]
fn a_lock_the_box_keeps_refusing_stops_after_three_tries() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse.insert(0x60);
});
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
rig.clear();
for _ in 0..6 {
poller.tick();
}
let tries = rig
.written()
.iter()
.filter(|bytes| **bytes == frame(0x60, &[0, 1]))
.count();
assert_eq!(tries, 3);
}
#[test]
fn key_masks_go_again_every_fourth_tick() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
lock(&backend, LockSlot::Key(Key::A), 0).unwrap();
rig.set(|state| state.masks.clear());
for _ in 0..4 {
poller.tick();
}
assert!(rig.state.lock().unwrap().masks.contains(&0x04));
lock(&backend, LockSlot::Key(Key::A), 100).unwrap();
rig.clear();
for _ in 0..4 {
poller.tick();
}
assert!(!rig.written().contains(&frame(0x29, &[0x04, 1])));
}
#[test]
fn shutdown_looks_once_more_for_an_unlock_the_box_didnt_take() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
device.lock_button(Button::Left).unwrap();
rig.set(|state| {
state.refuse_once.insert(0x60);
});
drop(device);
assert_eq!(rig.state.lock().unwrap().locks, 0);
}
#[test]
fn a_refused_lock_names_a_button_or_an_axis_and_a_refused_mask_a_key() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
let mut codec = MakcuFrames::binary(backend.shared.last.clone());
let refused =
|codec: &mut MakcuFrames, command: u8| match codec.classify(&frame(command, &[0xFF])) {
Incoming::Refused { action, .. } => action,
other => panic!("{other:?}"),
};
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
assert_eq!(refused(&mut codec, 0x60), Some(Action::LockButton));
lock(&backend, LockSlot::Axis(Axis::Y, Sign::Positive), 0).unwrap();
assert_eq!(refused(&mut codec, 0x60), Some(Action::LockAxis));
assert_eq!(refused(&mut codec, 0x29), Some(Action::LockKey));
}
#[test]
fn every_loss_of_an_accepted_lock_gets_its_own_tries() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
for round in 0..5 {
rig.set(|state| state.locks = 0);
poller.tick();
assert_eq!(rig.state.lock().unwrap().locks, 1, "round {round}");
poller.tick();
}
}
#[test]
fn a_new_write_after_the_tries_ran_out_starts_the_count_again() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, mut poller, _signals) = rig.attach().unwrap();
rig.set(|state| {
state.refuse.insert(0x60);
});
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
for _ in 0..4 {
poller.tick();
}
rig.set(|state| {
state.refuse.clear();
});
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
rig.set(|state| state.locks = 0);
poller.tick();
assert_eq!(rig.state.lock().unwrap().locks, 1);
}
#[test]
fn shutdown_leaves_a_lock_anybox_never_wrote_alone() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.set(|state| state.locks = 1 << 1);
drop(device);
assert_eq!(rig.state.lock().unwrap().locks, 1 << 1);
}
#[test]
fn pan_is_unsupported_and_says_why() {
let rig = rig(State::makcu(4094));
let (_backend, snapshot, _poller, _signals) = rig.attach().unwrap();
match &snapshot.capabilities.get(Action::Pan).execution {
Execution::Unsupported { reason } => {
assert!(reason.contains("only exists on V3"), "{reason}")
}
other => panic!("{other:?}"),
}
}
#[test]
fn an_off_the_deadline_skipped_still_goes_out_at_shutdown() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
assert_eq!(rig.state.lock().unwrap().key_stream, 1);
rig.set(|state| {
state
.slow
.insert(frames::INPUT_STREAM, Duration::from_millis(300));
});
drop(events);
assert_eq!(rig.state.lock().unwrap().key_stream, 1);
rig.set(|state| state.slow.clear());
drop(device);
assert_eq!(rig.state.lock().unwrap().key_stream, 0);
}
#[test]
fn shutdown_stays_within_its_deadline_on_a_slow_box() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
backend
.set_streams(InputKinds {
buttons: true,
keys: true,
..InputKinds::default()
})
.unwrap();
rig.set(|state| {
state
.slow
.insert(frames::INPUT_STREAM, Duration::from_millis(400));
state
.slow
.insert(frames::INJECT_SNAPSHOT, Duration::from_millis(400));
});
let started = Instant::now();
backend.shutdown(Duration::from_secs(1));
let took = started.elapsed();
assert!(took < Duration::from_secs(2), "{took:?}");
}
#[test]
fn shutdown_unlocks_a_lock_whose_unlock_was_never_sent() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
assert_eq!(rig.state.lock().unwrap().locks, 1);
backend.shutdown(Duration::from_secs(1));
assert_eq!(rig.state.lock().unwrap().locks, 0);
}
#[test]
fn a_slow_stream_off_never_costs_the_last_unlock() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
backend
.set_streams(InputKinds {
buttons: true,
keys: true,
..InputKinds::default()
})
.unwrap();
lock(&backend, LockSlot::Button(Button::Left), 0).unwrap();
rig.set(|state| {
state
.slow
.insert(frames::INPUT_STREAM, Duration::from_millis(450));
});
backend.shutdown(Duration::from_millis(800));
assert_eq!(rig.state.lock().unwrap().locks, 0);
}
#[test]
fn shutdown_unmasks_a_key_whose_unlock_was_never_sent() {
let rig = rig(State::makcu(4094));
let (backend, _snapshot, _poller, _signals) = rig.attach().unwrap();
lock(&backend, LockSlot::Key(Key::A), 0).unwrap();
assert!(rig.state.lock().unwrap().masks.contains(&0x04));
backend.shutdown(Duration::from_secs(1));
assert!(rig.state.lock().unwrap().masks.is_empty());
}
#[test]
fn an_event_says_when_its_bytes_were_read_and_the_qpc_on_windows() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
let events = device.events();
rig.fake
.send(&[0xDE, 0xAD, 0x03, 0x00, 0x53, 0x01, 0x02, 0x01]);
let Ok(Event::Button { at, .. }) = events.recv_timeout(Duration::from_secs(2)) else {
panic!("no button")
};
assert_eq!(at.received, at.arrived);
assert_eq!(at.received_since_open, at.since_open);
assert_eq!(at.received_qpc.is_some(), cfg!(windows));
}
#[test]
fn a_refused_stream_switch_is_one_rejected() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.set(|state| {
state.refuse.insert(0x52);
});
let events = device.events_of(crate::event::EventKinds::BUTTONS);
let mut count = 0;
while let Ok(event) = events.recv_timeout(Duration::from_millis(700)) {
if let Event::Rejected {
action: Action::ButtonEvents,
count: n,
} = event
{
count += n;
}
}
assert_eq!(count, 1);
}
#[test]
fn a_receiver_made_right_after_a_refused_one_gets_its_own_single_rejected() {
let rig = rig(State::makcu(4094));
let (device, _poller) = rig.device();
rig.set(|state| {
state.refuse.insert(0x52);
});
let rejected = |events: &crate::event::Events| {
let mut count = 0;
while let Ok(event) = events.recv_timeout(Duration::from_millis(300)) {
if let Event::Rejected {
action: Action::ButtonEvents,
count: n,
} = event
{
count += n;
}
}
count
};
let first = device.events_of(crate::event::EventKinds::BUTTONS);
assert_eq!(rejected(&first), 1);
drop(first);
let second = device.events_of(crate::event::EventKinds::BUTTONS);
assert_eq!(rejected(&second), 1);
}