1use std::cmp::Ordering;
2use std::path::{Path, PathBuf};
3use std::sync::mpsc::{self, Receiver, Sender};
4use std::time::Duration;
5
6use crate::backend::{Backend, Signal, Sink};
7use crate::capability::{Action, Capabilities, Family, Info, Snapshot, StableId};
8use crate::click::{ClickTiming, Holds};
9use crate::device::{Device, Start};
10use crate::error::{Error, Result};
11use crate::family::{self, Open};
12use crate::transport::os::OsSystem;
13use crate::transport::{Candidate, System, is_ch343};
14
15const CLOSE_DEADLINE: Duration = Duration::from_secs(1);
17
18pub fn connect() -> Result<Device> {
20 Connect::auto().open()
21}
22
23pub fn find() -> Result<Vec<Found>> {
25 Connect::auto().find()
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct Found {
32 pub port: String,
33 pub info: Info,
34 pub capabilities: Capabilities,
36}
37
38#[derive(Clone, Debug, Default)]
39pub struct Connect {
40 port: Option<PathBuf>,
41 family: Option<Family>,
42 id: Option<StableId>,
43 click_timing: ClickTiming,
44 can: Vec<Action>,
45}
46
47impl Connect {
48 pub fn auto() -> Self {
50 Self::default()
51 }
52
53 pub fn port(path: impl Into<PathBuf>) -> Self {
54 Self {
55 port: Some(path.into()),
56 ..Self::default()
57 }
58 }
59
60 pub fn can(mut self, action: Action) -> Self {
64 self.can.push(action);
65 self
66 }
67
68 pub fn family(self, family: Family) -> Self {
70 Self {
71 family: Some(family),
72 ..self
73 }
74 }
75
76 pub fn id(self, id: StableId) -> Self {
79 Self {
80 id: Some(id),
81 ..self
82 }
83 }
84
85 pub fn click_timing(self, click_timing: ClickTiming) -> Self {
87 Self {
88 click_timing,
89 ..self
90 }
91 }
92
93 pub fn open(self) -> Result<Device> {
94 self.open_in(&|| OsSystem.ports(), family::PROBES)
95 }
96
97 pub(crate) fn open_in(
98 self,
99 ports: &dyn Fn() -> Vec<Candidate>,
100 probes: &[(Family, Open)],
101 ) -> Result<Device> {
102 let mut search = self.search(ports, probes, true);
103 match search.opened.take() {
104 Some(found) => Device::start(Start {
105 backend: found.backend,
106 snapshot: found.snapshot,
107 signals: found.signals,
108 incoming: found.incoming,
109 click_timing: self.click_timing,
110 holds: Holds::from_clock(),
111 port: found.port,
112 }),
113 None => Err(search.error()),
114 }
115 }
116
117 pub fn find(self) -> Result<Vec<Found>> {
119 self.find_in(&|| OsSystem.ports(), family::PROBES)
120 }
121
122 pub(crate) fn find_in(
123 &self,
124 ports: &dyn Fn() -> Vec<Candidate>,
125 probes: &[(Family, Open)],
126 ) -> Result<Vec<Found>> {
127 let search = self.search(ports, probes, false);
128 if search.seen.is_empty() && !search.skipped.is_empty() {
130 return Err(search.error());
131 }
132 Ok(search.seen)
133 }
134
135 pub(crate) fn search(
138 &self,
139 ports: &dyn Fn() -> Vec<Candidate>,
140 probes: &[(Family, Open)],
141 first: bool,
142 ) -> Search {
143 let mut search = Search::default();
144 let listed = ports();
145 let mut candidates: Vec<Candidate> = match &self.port {
146 Some(path) => match listed.into_iter().find(|candidate| &candidate.path == path) {
148 Some(candidate) => vec![candidate],
149 None => vec![Candidate {
150 path: path.clone(),
151 usb: None,
152 }],
153 },
154 None => listed
155 .into_iter()
156 .filter(|candidate| candidate.usb.as_ref().is_some_and(is_ch343))
157 .collect(),
158 };
159 candidates.sort_by(|a, b| port_order(&a.path, &b.path));
160 for candidate in &candidates {
161 if self.port.is_none() && !self.might_be(candidate) {
162 continue;
163 }
164 search.tried.push(candidate.path.display().to_string());
165 if let Some(opened) = self.probe(candidate, ports, probes, &mut search) {
166 if first {
167 search.opened = Some(opened);
168 break;
169 }
170 opened.backend.shutdown(CLOSE_DEADLINE);
173 let capabilities = self.capabilities(&opened.snapshot);
174 search.seen.push(Found {
175 port: opened.port,
176 info: opened.snapshot.info,
177 capabilities,
178 });
179 }
180 }
181 search
182 }
183
184 fn capabilities(&self, snapshot: &Snapshot) -> Capabilities {
187 snapshot
188 .capabilities
189 .clone()
190 .with_host_taps(self.click_timing)
191 }
192
193 fn might_be(&self, candidate: &Candidate) -> bool {
195 match &self.id {
196 Some(StableId::UsbSerial(serial)) => {
197 candidate.usb.as_ref().and_then(|usb| usb.serial.as_ref()) == Some(serial)
198 }
199 _ => true,
200 }
201 }
202
203 fn wants(&self, family: Family) -> bool {
204 let by_id = self.port.is_some()
206 || match &self.id {
207 Some(StableId::Mac(_)) => family == Family::Medius,
209 Some(StableId::UsbSerial(_)) => family == Family::MakcuV4,
210 None => true,
211 };
212 by_id && self.family.is_none_or(|wanted| wanted == family)
213 }
214
215 fn probe(
216 &self,
217 candidate: &Candidate,
218 ports: &dyn Fn() -> Vec<Candidate>,
219 probes: &[(Family, Open)],
220 search: &mut Search,
221 ) -> Option<Opened> {
222 let port = candidate.path.display().to_string();
223 for (family, open) in probes {
224 if !self.wants(*family) {
225 continue;
226 }
227 let (signals, incoming) = mpsc::channel();
228 match open(candidate, Sink(signals.clone())) {
229 Ok((backend, mut snapshot)) => {
230 snapshot.info.port.clone_from(&port);
231 if let Some(expected) = &self.id
232 && snapshot.info.id.as_ref() != Some(expected)
233 {
234 backend.shutdown(CLOSE_DEADLINE);
235 if self.port.is_some() {
236 search.skipped.push(Error::WrongBox {
237 port,
238 expected: expected.clone(),
239 found: snapshot.info.id,
240 });
241 }
242 return None;
243 }
244 let capabilities = self.capabilities(&snapshot);
245 let missing: Vec<Action> = self
246 .can
247 .iter()
248 .copied()
249 .filter(|action| !capabilities.get(*action).is_supported())
250 .collect();
251 if !missing.is_empty() {
252 backend.shutdown(CLOSE_DEADLINE);
253 search.incapable.push((
254 Found {
255 port,
256 info: snapshot.info,
257 capabilities,
258 },
259 missing,
260 ));
261 return None;
262 }
263 return Some(Opened {
264 port,
265 backend,
266 snapshot,
267 signals,
268 incoming,
269 });
270 }
271 Err(Error::NotFound { .. }) => {}
272 Err(error) => {
275 search.skipped.push(error);
276 return None;
277 }
278 }
279 }
280 if let (Some(expected), Some(_)) = (self.family, &self.port)
283 && ports().iter().any(|listed| listed.path == candidate.path)
284 {
285 search.skipped.push(Error::WrongFamily {
286 port,
287 expected,
288 found: None,
289 });
290 }
291 None
292 }
293}
294
295pub(crate) struct Opened {
296 port: String,
297 backend: Box<dyn Backend>,
298 snapshot: Snapshot,
299 signals: Sender<Signal>,
300 incoming: Receiver<Signal>,
301}
302
303#[derive(Default)]
304pub(crate) struct Search {
305 opened: Option<Opened>,
306 seen: Vec<Found>,
307 skipped: Vec<Error>,
308 incapable: Vec<(Found, Vec<Action>)>,
310 tried: Vec<String>,
311}
312
313impl Search {
314 fn error(self) -> Error {
317 let rank = |error: &Error| match error {
318 Error::FirmwareTooOld { .. } | Error::LibraryTooOld { .. } => 0,
319 Error::PortBusy { .. } => 1,
320 Error::PermissionDenied { .. } => 2,
321 _ => 3,
322 };
323 let best = self.skipped.into_iter().min_by_key(rank);
324 match best {
325 Some(error) if rank(&error) < 3 => error,
326 _ if !self.incapable.is_empty() => Error::NoBoxCan {
328 boxes: self.incapable,
329 },
330 Some(error) => error,
331 None => Error::NotFound { tried: self.tried },
332 }
333 }
334}
335
336fn port_order(a: &Path, b: &Path) -> Ordering {
338 fn split(path: &Path) -> (String, Option<u64>) {
339 let name = path.display().to_string();
340 let stem = name.trim_end_matches(|c: char| c.is_ascii_digit());
341 (stem.to_string(), name[stem.len()..].parse().ok())
342 }
343 split(a).cmp(&split(b)).then_with(|| a.cmp(b))
344}
345
346#[cfg(test)]
347mod tests;