1use crossbeam_channel::Sender;
2use serde::{Deserialize, Serialize};
3
4pub mod platform;
5
6#[cfg(target_os = "linux")]
7pub use platform::linux::LinuxMonitor as Monitor;
8#[cfg(target_os = "macos")]
9pub use platform::macos::MacMonitor as Monitor;
10#[cfg(target_os = "windows")]
11pub use platform::windows::WindowsMonitor as Monitor;
12
13pub type RoleId = String;
15
16#[derive(Debug, Clone)]
18pub struct RawDeviceInfo {
19 pub vid: u16,
20 pub pid: u16,
21 pub serial: Option<String>,
22 pub port_path: String, pub system_path: String, pub system_path_alt: Option<String>, }
26
27#[derive(Debug, Clone)]
29pub struct ResolvedDevice {
30 pub role: RoleId,
31 pub device: RawDeviceInfo,
32 pub match_method: MatchMethod,
33}
34
35#[derive(Debug, Clone, Copy)]
36pub enum MatchMethod {
37 SerialExact,
38 TopologyFallback,
39 PortPath,
40 VidPidOnly,
41}
42
43#[derive(Debug, Clone)]
45pub enum DeviceEvent {
46 Attached(RawDeviceInfo),
48 Detached(String),
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct DeviceRule {
55 pub role: RoleId,
56 pub vid: u16,
57 pub pid: u16,
58 pub serial: Option<String>, pub port_path: Option<String>, }
61
62impl DeviceRule {
63 pub fn matches(&self, device: &RawDeviceInfo) -> Option<MatchMethod> {
65 if self.vid != device.vid || self.pid != device.pid {
67 return None;
68 }
69
70 if let (Some(rule_sn), Some(dev_sn)) = (&self.serial, &device.serial)
72 && rule_sn == dev_sn
73 {
74 return Some(MatchMethod::SerialExact);
75 }
76
77 if let Some(rule_path) = &self.port_path
79 && rule_path == &device.port_path
80 {
81 return Some(MatchMethod::PortPath);
82 }
83
84 if self.serial.is_none() && self.port_path.is_none() {
86 return Some(MatchMethod::VidPidOnly);
87 }
88
89 if let Some(cfg_path) = &self.port_path
92 && cfg_path == &device.port_path
93 {
94 return Some(MatchMethod::TopologyFallback);
95 }
96
97 None
98 }
99}
100
101pub trait DeviceMonitor {
103 fn start(&self, tx: Sender<DeviceEvent>) -> anyhow::Result<()>;
105
106 fn scan_now(&self) -> anyhow::Result<Vec<RawDeviceInfo>>;
108}
109
110pub fn get_monitor() -> Box<dyn DeviceMonitor> {
112 #[cfg(target_os = "linux")]
113 return Box::new(platform::linux::LinuxMonitor::new());
114
115 #[cfg(target_os = "windows")]
116 return Box::new(platform::windows::WindowsMonitor::new());
117
118 #[cfg(target_os = "macos")]
119 return Box::new(platform::macos::MacMonitor::new());
120
121 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
122 panic!("Unsupported OS");
123}