use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use crate::backend::Input;
use crate::capability::Button;
use crate::key::Key;
use crate::transport::codec::{self, Codec, Frame, Incoming};
pub(crate) const PROMPT: &[u8] = b">>> ";
pub(crate) const END: &[u8] = b"\r\n";
const MAX_LINE: usize = 1024;
const BUTTONS_LEN: usize = 6;
pub(crate) const SOFTWARE_VERSION: &[u8] = b"kmbox: Ferrum";
pub(crate) const LEGACY_VERSION: &[u8] = b"kmbox: 2.0.0 Aug 31 2020 21:49:51";
pub(crate) const VERSION_QUERY: &[u8] = b"\r\nkm.version()\r\n";
const CALLBACKS: [&[u8]; 4] = [
b"Axes(",
b"Keys(",
b"ControllerAxes(",
b"ControllerButtons(",
];
fn echoed(line: &[u8]) -> bool {
[&b"km."[..], b"device.", b"mouse_proxy."]
.iter()
.any(|prefix| line.starts_with(prefix))
}
pub(crate) fn key(command: &[u8]) -> codec::ReplyKey {
command.iter().fold(0x811c_9dc5, |hash: u32, &byte| {
(hash ^ u32::from(byte)).wrapping_mul(0x0100_0193)
})
}
#[derive(Default)]
pub(crate) struct Seen {
buttons: u8,
keys: Vec<u8>,
}
impl Seen {
pub(crate) fn forget_buttons(&mut self) {
self.buttons = 0;
}
pub(crate) fn forget_keys(&mut self) {
self.keys.clear();
}
}
pub(crate) fn lock(seen: &Mutex<Seen>) -> MutexGuard<'_, Seen> {
seen.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) struct FerrumFrames {
echo: bool,
reply: Vec<u8>,
seen: Arc<Mutex<Seen>>,
}
impl FerrumFrames {
pub(crate) fn new(echo: bool, seen: Arc<Mutex<Seen>>) -> Self {
Self {
echo,
reply: Vec::new(),
seen,
}
}
pub(crate) fn probe(echo: bool) -> Self {
Self::new(echo, Arc::default())
}
fn callback(&self, buffer: &mut Vec<u8>, len: usize) -> Option<Frame> {
let rest = &buffer[len..];
if rest.starts_with(PROMPT) {
return Some(buffer.drain(..len + PROMPT.len()).collect());
}
if self.echo && PROMPT.starts_with(rest) {
return None;
}
Some(buffer.drain(..len).collect())
}
fn buttons(&self, state: u8) -> Incoming {
let state = state & 0x1F;
let mut seen = lock(&self.seen);
let changed = state ^ seen.buttons;
seen.buttons = state;
let edges = [false, true].into_iter().flat_map(|pressed| {
(0..5u8)
.filter(move |bit| {
changed & (1 << bit) != 0 && (state & (1 << bit) != 0) == pressed
})
.map(move |bit| Input::Button {
button: BUTTONS[usize::from(bit)],
pressed,
})
});
Incoming::Inputs(edges.collect())
}
fn keys(&self, held: Vec<u8>) -> Incoming {
let held: Vec<u8> = held
.into_iter()
.filter(|&key| Key(key).is_valid())
.collect();
let mut seen = lock(&self.seen);
let released = seen.keys.iter().filter(|key| !held.contains(key));
let pressed = held.iter().filter(|key| !seen.keys.contains(key));
let edges = released
.map(|&key| Input::Key {
key: Key(key),
pressed: false,
})
.chain(pressed.map(|&key| Input::Key {
key: Key(key),
pressed: true,
}))
.collect();
seen.keys = held;
Incoming::Inputs(edges)
}
}
const BUTTONS: [Button; 5] = [
Button::Left,
Button::Right,
Button::Middle,
Button::Side1,
Button::Side2,
];
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
fn arguments<T: std::str::FromStr>(frame: &[u8], name: &[u8]) -> Option<Vec<T>> {
let inside = frame.strip_prefix(name)?;
let close = inside.iter().position(|&byte| byte == b')')?;
let inside = std::str::from_utf8(&inside[..close]).ok()?;
if inside.trim().is_empty() {
return Some(Vec::new());
}
inside
.split(',')
.map(|number| number.trim().parse().ok())
.collect()
}
pub(crate) fn result(frame: &[u8], echo: bool) -> Option<&[u8]> {
let start = if echo {
find(frame, END)? + END.len()
} else {
0
};
let rest = &frame[start..];
find(rest, END).map(|end| &rest[..end])
}
pub(crate) fn version(frame: &[u8]) -> Option<super::FerrumApi> {
match frame.strip_suffix(END).unwrap_or(frame) {
SOFTWARE_VERSION => Some(super::FerrumApi::Software),
LEGACY_VERSION => Some(super::FerrumApi::Legacy),
_ => None,
}
}
impl Codec for FerrumFrames {
fn split(&mut self, buffer: &mut Vec<u8>) -> Option<Frame> {
loop {
if buffer.starts_with(b"km.") {
if buffer.len() < BUTTONS_LEN {
return None;
}
if &buffer[4..BUTTONS_LEN] == END {
return self.callback(buffer, BUTTONS_LEN);
}
}
if buffer.starts_with(PROMPT) {
buffer.drain(..PROMPT.len());
let mut frame = std::mem::take(&mut self.reply);
frame.extend_from_slice(PROMPT);
return Some(frame);
}
if PROMPT.starts_with(buffer) {
return None;
}
let Some(end) = find(buffer, END) else {
if buffer.len() > MAX_LINE {
buffer.clear();
self.reply.clear();
}
return None;
};
let len = end + END.len();
if CALLBACKS.iter().any(|name| buffer.starts_with(name)) {
return self.callback(buffer, len);
}
let line: Vec<u8> = buffer.drain(..len).collect();
if end == 0 {
continue;
}
if !self.echo {
if echoed(&line) {
continue;
}
return Some(line);
}
if echoed(&line) {
self.reply.clear();
} else if self.reply.is_empty() {
continue;
}
self.reply.extend_from_slice(&line);
}
}
fn classify(&mut self, frame: &Frame) -> Incoming {
if frame.starts_with(b"km.") && frame.get(4..BUTTONS_LEN) == Some(END) {
return self.buttons(frame[3]);
}
if frame.starts_with(b"Axes(") {
return match arguments::<i32>(frame, b"Axes(").as_deref() {
Some(&[dx, dy, wheel]) => Incoming::Input {
input: Input::Motion {
dx,
dy,
wheel,
pan: 0,
},
box_time: None,
},
_ => Incoming::Noise,
};
}
if frame.starts_with(b"Keys(") {
return arguments::<u8>(frame, b"Keys(")
.map_or(Incoming::Noise, |held| self.keys(held));
}
match find(frame, END) {
Some(end) if self.echo && echoed(frame) && frame.ends_with(PROMPT) => {
Incoming::Reply(key(&frame[..end]))
}
_ => Incoming::Noise,
}
}
}