1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Multi-box discovery: enumerate every connected medius box, then open one by identity or by the
//! kind of device it clones.
use crate::error::{Error, Result};
use crate::types::{DeviceInfo, DeviceKind, PortInfo, Version};
use super::Device;
/// One discovered medius box: its serial port, firmware version, and the device it currently clones.
/// The stable identity is the device MAC ([`BoxInfo::id`]); the CH343 [`serial`](PortInfo::serial) is
/// a faster scan-time key when the adapter serves one.
#[derive(Debug, Clone)]
pub struct BoxInfo {
/// The control serial port (path + USB ids + serial).
pub port: PortInfo,
/// Firmware version and the box's base MAC.
pub version: Version,
/// The cloned device's identity, kind, and product.
pub device: DeviceInfo,
}
impl BoxInfo {
/// The canonical, stable box id: the device MAC as 12 lowercase hex digits.
pub fn id(&self) -> String {
self.version.mac_hex()
}
/// The CH343 control-adapter serial, if it serves one.
pub fn serial(&self) -> Option<&str> {
self.port.serial.as_deref()
}
}
fn probe(port: &PortInfo) -> Option<BoxInfo> {
let device = Device::open(&port.path).ok()?;
let version = device.query_version().ok()?;
let info = device.device_info().ok()?;
Some(BoxInfo {
port: port.clone(),
version,
device: info,
})
}
impl Device {
/// Enumerate every connected medius box: for each CH343 control port, open it, handshake, and
/// read its version + cloned-device info. Ports that don't answer a valid handshake are skipped.
/// Each box is opened and closed in turn, so this does not disturb a box another handle is driving
/// beyond the brief probe.
pub fn list() -> Vec<BoxInfo> {
crate::transport::scan::find_medius()
.iter()
.filter_map(probe)
.collect()
}
/// Open the box whose identity matches `id` — either its device MAC (hex, separators and case
/// ignored) or its CH343 serial. [`Error::NotFound`] if no connected box matches.
pub fn open_by_id(id: &str) -> Result<Device> {
let want: String = id
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.flat_map(char::to_lowercase)
.collect();
let info = Device::list()
.into_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)?;
Device::open(&info.port.path)
}
/// Open the first box whose clone is a mouse ([`DeviceKind::Mouse`]).
pub fn find_mouse_box() -> Result<Device> {
Device::find_where(|b| b.device.kind == DeviceKind::Mouse)
}
/// Open the first box whose clone is a keyboard ([`DeviceKind::Keyboard`]).
pub fn find_keyboard_box() -> Result<Device> {
Device::find_where(|b| b.device.kind == DeviceKind::Keyboard)
}
/// Open the first discovered box that satisfies `pred`. [`Error::NotFound`] if none match.
pub fn find_where(pred: impl Fn(&BoxInfo) -> bool) -> Result<Device> {
let info = Device::list()
.into_iter()
.find(|b| pred(b))
.ok_or(Error::NotFound)?;
Device::open(&info.port.path)
}
}