use std::path::Path;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::protocol::PROTO_VER;
use crate::transport::Transport;
use crate::types::{DeviceInfo, DeviceKind, PortInfo, Version};
use super::Device;
#[derive(Debug, Clone)]
pub struct BoxInfo {
pub port: PortInfo,
pub version: Version,
pub device: Option<DeviceInfo>,
}
impl BoxInfo {
pub fn id(&self) -> String {
self.version.mac_hex()
}
pub fn serial(&self) -> Option<&str> {
self.port.serial.as_deref()
}
pub fn name(&self) -> &str {
&self.version.name
}
}
fn probe(port: &PortInfo) -> Option<BoxInfo> {
let serial = crate::transport::serial::SerialTransport::open(Path::new(&port.path)).ok()?;
probe_transport(port, Arc::new(serial))
}
pub(crate) fn probe_transport(port: &PortInfo, transport: Arc<dyn Transport>) -> Option<BoxInfo> {
let device = Device::from_transport(transport);
let version = device.read_version().ok()?;
let info = if version.proto_ver == PROTO_VER {
Some(device.device_info().ok()?)
} else {
None
};
Some(BoxInfo {
port: port.clone(),
version,
device: info,
})
}
fn openable(b: &BoxInfo) -> Result<&BoxInfo> {
match b.device {
Some(_) => Ok(b),
None => Err(Error::BadProtoVer {
got: b.version.proto_ver,
}),
}
}
pub(crate) fn pick_by_id<'a>(boxes: &'a [BoxInfo], id: &str) -> Result<&'a BoxInfo> {
let want: String = id
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.flat_map(char::to_lowercase)
.collect();
let found = boxes
.iter()
.find(|b| {
b.id() == want
|| b.serial()
.is_some_and(|s| s.eq_ignore_ascii_case(id) || s.eq_ignore_ascii_case(&want))
})
.ok_or(Error::NotFound)?;
openable(found)
}
pub(crate) fn pick_where(boxes: &[BoxInfo], pred: impl Fn(&BoxInfo) -> bool) -> Result<&BoxInfo> {
let found = boxes
.iter()
.find(|b| b.device.is_some() && pred(b))
.or_else(|| boxes.iter().find(|b| pred(b)))
.ok_or(Error::NotFound)?;
openable(found)
}
pub(crate) fn may_clone(kind: DeviceKind) -> impl Fn(&BoxInfo) -> bool {
move |b| b.device.as_ref().is_none_or(|d| d.kind == kind)
}
impl Device {
pub fn list() -> Vec<BoxInfo> {
crate::transport::scan::find_medius()
.iter()
.filter_map(probe)
.collect()
}
pub fn open_by_id(id: &str) -> Result<Device> {
Device::open(&pick_by_id(&Device::list(), id)?.port.path)
}
pub fn find_mouse_box() -> Result<Device> {
Device::find_where(may_clone(DeviceKind::Mouse))
}
pub fn find_keyboard_box() -> Result<Device> {
Device::find_where(may_clone(DeviceKind::Keyboard))
}
pub fn find_where(pred: impl Fn(&BoxInfo) -> bool) -> Result<Device> {
Device::open(&pick_where(&Device::list(), pred)?.port.path)
}
}