use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, Ordering};
use crate::backend::{Input, InputKind, LockSlot, Sign};
use crate::capability::Axis;
use crate::capability::{Action, Button};
use crate::key::Key;
use crate::transport::codec::{Codec, Frame, Incoming};
const START: [u8; 2] = [0xDE, 0xAD];
const HEADER: usize = 5;
const MAX_LEN: usize = 64;
const PROMPT: &[u8] = b">>> ";
const REFUSED: u8 = 0xFF;
pub(crate) const DEVICE: u8 = 0x02;
pub(crate) const FIRMWARE_VERSION: u8 = 0x04;
pub(crate) const KEY_DOWN: u8 = 0x20;
pub(crate) const KEY_UP: u8 = 0x21;
pub(crate) const KEY_PRESS: u8 = 0x23;
pub(crate) const KEY_MASK: u8 = 0x29;
pub(crate) const LEFT: u8 = 0x11;
pub(crate) const MOVE: u8 = 0x18;
pub(crate) const WHEEL: u8 = 0x19;
pub(crate) const INPUT_STREAM: u8 = 0x52;
const INPUT_CHANGE: u8 = 0x53;
pub(crate) const INJECT_SNAPSHOT: u8 = 0x56;
pub(crate) const LOCK: u8 = 0x60;
pub(crate) const CLICK: u8 = 0x61;
pub(crate) const MOVE_NOW: u8 = 0x67;
pub(crate) const MOUSE: u8 = 1;
pub(crate) const KEYBOARD: u8 = 2;
pub(crate) const MOUSE_ROUTED: u8 = 0x01;
pub(crate) const KEYBOARD_ROUTED: u8 = 0x02;
pub(crate) const VERSION_QUERY: &[u8] = b"\r\nkm.version()\r\n";
pub(crate) fn frame(command: u8, payload: &[u8]) -> Vec<u8> {
let len = u16::try_from(payload.len()).expect("MAK_API payloads are short");
let mut frame = START.to_vec();
frame.extend_from_slice(&len.to_le_bytes());
frame.push(command);
frame.extend_from_slice(payload);
frame
}
pub(crate) fn parts(frame: &[u8]) -> Option<(u8, &[u8])> {
if frame.starts_with(&START) && frame.len() >= HEADER {
Some((frame[4], &frame[HEADER..]))
} else {
None
}
}
pub(crate) fn refused(payload: &[u8]) -> bool {
payload == [REFUSED]
}
pub(crate) fn says_makcu(frame: &[u8]) -> bool {
parts(frame).is_none()
&& frame
.split(|&byte| byte == b'\r' || byte == b'\n')
.any(|line| line == b"km.MAKCU")
}
pub(crate) fn button_id(button: Button) -> Option<u8> {
match button {
Button::Left => Some(0),
Button::Right => Some(1),
Button::Middle => Some(2),
Button::Side1 => Some(3),
Button::Side2 => Some(4),
_ => None,
}
}
pub(crate) struct LastSent {
buttons: [AtomicU8; 5],
keys: [AtomicBool; 256],
stream: AtomicU8,
locks: [AtomicU8; 14],
lock_writes: [AtomicU32; 14],
lock_target: AtomicU8,
}
impl Default for LastSent {
fn default() -> Self {
Self {
buttons: Default::default(),
keys: std::array::from_fn(|_| AtomicBool::new(false)),
stream: AtomicU8::new(MOUSE),
locks: Default::default(),
lock_writes: Default::default(),
lock_target: AtomicU8::new(0),
}
}
}
impl LastSent {
pub(crate) fn set(&self, id: u8, state: u8) {
self.buttons[usize::from(id)].store(state, Ordering::Release);
}
pub(crate) fn get(&self, id: u8) -> u8 {
self.buttons[usize::from(id)].load(Ordering::Acquire)
}
pub(crate) fn set_key(&self, key: u8, pressed: bool) {
self.keys[usize::from(key)].store(pressed, Ordering::Release);
}
pub(crate) fn key(&self, key: u8) -> bool {
self.keys[usize::from(key)].load(Ordering::Acquire)
}
pub(crate) fn set_stream(&self, kind: u8) {
self.stream.store(kind, Ordering::Release);
}
pub(crate) const NEVER: u8 = 0;
pub(crate) const LOCKED: u8 = 1;
pub(crate) const UNLOCKED: u8 = 2;
pub(crate) fn set_lock(&self, target: u8, state: u8) {
self.locks[usize::from(target)].store(state, Ordering::Release);
self.lock_writes[usize::from(target)].fetch_add(1, Ordering::AcqRel);
self.name_lock(target);
}
pub(crate) fn name_lock(&self, target: u8) {
self.lock_target.store(target, Ordering::Release);
}
pub(crate) fn lock_writes(&self, target: u8) -> u32 {
self.lock_writes[usize::from(target)].load(Ordering::Acquire)
}
pub(crate) fn lock(&self, target: u8) -> u8 {
self.locks[usize::from(target)].load(Ordering::Acquire)
}
pub(crate) fn forget_locks(&self) {
for lock in &self.locks {
lock.store(Self::NEVER, Ordering::Release);
}
}
}
pub(crate) struct MakcuFrames {
text: bool,
last: Arc<LastSent>,
}
impl MakcuFrames {
pub(crate) fn probe() -> Self {
Self {
text: true,
last: Arc::default(),
}
}
pub(crate) fn binary(last: Arc<LastSent>) -> Self {
Self { text: false, last }
}
fn refused_action(&self, command: u8) -> Option<Action> {
Some(match command {
MOVE => Action::Move,
MOVE_NOW => Action::MoveNow,
WHEEL => Action::Wheel,
CLICK => Action::Click,
INPUT_STREAM if self.last.stream.load(Ordering::Acquire) == KEYBOARD => {
Action::KeyEvents
}
INPUT_STREAM => Action::ButtonEvents,
LOCK if self.last.lock_target.load(Ordering::Acquire) < 5 => Action::LockButton,
LOCK => Action::LockAxis,
KEY_MASK => Action::LockKey,
KEY_DOWN => Action::KeyPress,
KEY_UP => Action::KeyRelease,
KEY_PRESS => Action::KeyTap,
LEFT..=0x15 if self.last.get(command - LEFT) == 1 => Action::Press,
LEFT..=0x15 => Action::Release,
_ => return None,
})
}
fn binary_frame(buffer: &mut Vec<u8>) -> Option<Frame> {
loop {
if buffer.len() < HEADER {
return None;
}
let len = usize::from(u16::from_le_bytes([buffer[2], buffer[3]]));
if len > MAX_LEN {
buffer.drain(..1);
drop_to_start(buffer);
continue;
}
if buffer.len() < HEADER + len {
return None;
}
return Some(buffer.drain(..HEADER + len).collect());
}
}
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
fn drop_to_start(buffer: &mut Vec<u8>) {
match find(buffer, &START) {
Some(start) => {
buffer.drain(..start);
}
None => {
let keep = usize::from(buffer.last() == Some(&START[0]));
buffer.drain(..buffer.len() - keep);
}
}
}
impl Codec for MakcuFrames {
fn split(&mut self, buffer: &mut Vec<u8>) -> Option<Frame> {
if !self.text {
drop_to_start(buffer);
return Self::binary_frame(buffer);
}
let start = find(buffer, &START);
if start == Some(0) {
return Self::binary_frame(buffer);
}
let prompt = find(buffer, PROMPT).map(|at| at + PROMPT.len());
let end = match (prompt, start) {
(Some(prompt), Some(start)) => prompt.min(start),
(Some(end), None) | (None, Some(end)) => end,
(None, None) => return None,
};
Some(buffer.drain(..end).collect())
}
fn classify(&mut self, frame: &Frame) -> Incoming {
let Some((command, payload)) = parts(frame) else {
return Incoming::Noise;
};
if command == INPUT_CHANGE {
return match *payload {
[MOUSE, 0xFF, 0xFF] => Incoming::StreamLost(InputKind::Buttons),
[KEYBOARD, 0xFF, 0xFF] => Incoming::StreamLost(InputKind::Keys),
[KEYBOARD, usage, state @ 0..=1] if Key(usage).is_valid() => Incoming::Input {
input: Input::Key {
key: Key(usage),
pressed: state == 1,
},
box_time: None,
},
[MOUSE, id @ 0..=31, state @ 0..=1] => Incoming::Input {
input: Input::Button {
button: match id {
0 => Button::Left,
1 => Button::Right,
2 => Button::Middle,
3 => Button::Side1,
4 => Button::Side2,
n => Button::Extra(n),
},
pressed: state == 1,
},
box_time: None,
},
_ => Incoming::Noise,
};
}
if refused(payload) {
return Incoming::Refused {
key: u32::from(command),
action: self.refused_action(command),
};
}
Incoming::Reply(u32::from(command))
}
}
pub(crate) fn injected_keys(snapshot: &[u8]) -> Vec<u8> {
let mut keys = Vec::new();
if let Some(&modifiers) = snapshot.get(3) {
keys.extend(
(0..8u8)
.filter(|bit| modifiers & (1 << bit) != 0)
.map(|bit| 0xE0 + bit),
);
}
if let Some(bitmap) = snapshot.get(4..36) {
for (byte, &bits) in bitmap.iter().enumerate() {
for bit in 0..8u8 {
let key = u8::try_from(byte * 8).unwrap_or(u8::MAX) + bit;
if bits & (1 << bit) != 0 && !keys.contains(&key) {
keys.push(key);
}
}
}
}
keys
}
pub(crate) fn lock_target(slot: LockSlot) -> Option<u8> {
Some(match slot {
LockSlot::Button(button) => button_id(button)?,
LockSlot::Axis(Axis::X, Sign::Positive) => 6,
LockSlot::Axis(Axis::X, Sign::Negative) => 7,
LockSlot::Axis(Axis::Y, Sign::Positive) => 9,
LockSlot::Axis(Axis::Y, Sign::Negative) => 10,
LockSlot::Axis(Axis::Wheel, Sign::Positive) => 12,
LockSlot::Axis(Axis::Wheel, Sign::Negative) => 13,
LockSlot::Axis(..) | LockSlot::Key(_) => return None,
})
}
pub(crate) fn locked_targets(snapshot: &[u8]) -> u16 {
match snapshot.get(1..3) {
Some(&[low, high]) => u16::from_le_bytes([low, high]),
_ => 0,
}
}