#![cfg_attr(not(test), allow(dead_code))]
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use super::frames::{LEGACY_VERSION, SOFTWARE_VERSION};
use crate::capability::FerrumApi;
use crate::transport::PortUsb;
pub(crate) struct State {
pub(crate) api: FerrumApi,
pub(crate) baud: u32,
pub(crate) mouse: bool,
pub(crate) keyboard: bool,
pub(crate) roles: bool,
pub(crate) directional: bool,
pub(crate) ids: Option<(&'static str, &'static str)>,
pub(crate) ids_silent: bool,
pub(crate) version_silent: bool,
pub(crate) silent: bool,
pub(crate) legacy_prompt: bool,
pub(crate) version: Option<&'static str>,
pub(crate) catch: &'static str,
pub(crate) streams: HashMap<String, bool>,
pub(crate) lines: Vec<String>,
}
impl State {
pub(crate) fn software() -> Self {
Self {
api: FerrumApi::Software,
baud: 0,
mouse: true,
keyboard: true,
roles: true,
directional: true,
ids: Some(("046d", "c08b")),
ids_silent: false,
version_silent: false,
silent: false,
legacy_prompt: false,
version: Some("v2.0.6"),
catch: "(-3, 10)",
streams: HashMap::new(),
lines: Vec::new(),
}
}
pub(crate) fn legacy(baud: u32) -> Self {
Self {
api: FerrumApi::Legacy,
baud,
keyboard: false,
roles: false,
directional: false,
..Self::software()
}
}
fn answer(&mut self, line: &str) -> Option<String> {
let (name, args) = line.split_once('(')?;
let args = args.strip_suffix(')')?;
let bit = |on: bool| if on { "1" } else { "0" }.to_string();
match (name, args) {
("km.version", "") => Some(
String::from_utf8_lossy(match self.api {
FerrumApi::Software => SOFTWARE_VERSION,
FerrumApi::Legacy => LEGACY_VERSION,
})
.into(),
),
("km.has_mouse", "") if self.roles => Some(bit(self.mouse)),
("km.has_keyboard", "") if self.roles => Some(bit(self.keyboard)),
("km.lock_mx+", "") if self.directional => Some("0".into()),
("ferrum.version", "") => self.version.map(String::from),
("km.catch_xy", _) => Some(self.catch.into()),
("device.VID", "") => self.ids.map(|(vid, _)| format!("VID={vid}")),
("device.PID", "") => self.ids.map(|(_, pid)| format!("PID={pid}")),
("km.buttons" | "km.axes" | "km.keys", "") => {
Some(bit(self.streams.get(name).copied().unwrap_or(false)))
}
("km.buttons" | "km.axes" | "km.keys", on) => {
self.streams.insert(name.into(), on == "1");
None
}
_ => None,
}
}
}
pub(crate) type Shared = Arc<Mutex<State>>;
pub(crate) fn respond(shared: &Shared, baud: u32, bytes: &[u8]) -> Vec<Vec<u8>> {
let mut state = shared.lock().unwrap();
if state.silent || (state.api == FerrumApi::Legacy && baud != state.baud) {
return Vec::new();
}
let text = String::from_utf8_lossy(bytes).to_string();
let mut out = Vec::new();
for line in text.split(['\r', '\n']).filter(|line| !line.is_empty()) {
state.lines.push(line.into());
if (state.ids_silent && line.starts_with("device."))
|| (state.version_silent && line.starts_with("ferrum."))
{
continue;
}
let result = state.answer(line);
match state.api {
FerrumApi::Software => {
out.extend_from_slice(line.as_bytes());
out.extend_from_slice(b"\r\n");
if let Some(result) = result {
out.extend_from_slice(result.as_bytes());
out.extend_from_slice(b"\r\n");
}
out.extend_from_slice(b">>> ");
}
FerrumApi::Legacy => {
if let Some(result) = result {
out.extend_from_slice(result.as_bytes());
out.extend_from_slice(b"\r\n");
if state.legacy_prompt {
out.extend_from_slice(b">>> ");
}
}
}
}
}
vec![out]
}
pub(crate) fn cp210x() -> Option<PortUsb> {
Some(PortUsb {
vid: 0x10C4,
pid: 0xEA60,
serial: None,
})
}
#[cfg(feature = "mock")]
impl crate::device::Device {
pub fn mock_ferrum(click_timing: crate::click::ClickTiming) -> crate::error::Result<Self> {
use std::sync::mpsc;
use crate::backend::Sink;
use crate::device::Start;
use crate::transport::Candidate;
use crate::transport::fake::{FakeBox, FakeSystem};
const PORT: &str = "mock Ferrum";
let state = Arc::new(Mutex::new(State::software()));
let fake = FakeBox::by_baud(move |baud, bytes| respond(&state, baud, bytes));
let system = FakeSystem::default();
system.plug(PORT, cp210x(), fake);
let candidate = Candidate {
path: PORT.into(),
usb: cp210x(),
};
let (signals, incoming) = mpsc::channel();
let (backend, snapshot, poller) =
super::attach(Arc::new(system), &candidate, Sink(signals.clone()))?;
if let Some(poller) = poller {
*super::lock(&backend.poller) = Some(poller.spawn()?);
}
Self::start(Start {
backend: Box::new(backend),
snapshot,
signals,
incoming,
click_timing,
holds: crate::click::Holds::from_clock(),
port: PORT.into(),
})
}
}