usbwatch_rs/watcher/
linux.rs1#[cfg(target_os = "linux")]
2use crate::device_info::{DeviceEventType, DeviceHandle, UsbDeviceInfo};
3#[cfg(target_os = "linux")]
4use std::collections::HashMap;
5#[cfg(target_os = "linux")]
6use std::path::Path;
7#[cfg(target_os = "linux")]
8use tokio::fs;
9#[cfg(target_os = "linux")]
10use tokio::sync::mpsc;
11
12#[cfg(target_os = "linux")]
13pub struct LinuxUsbWatcher {
22 tx: mpsc::Sender<UsbDeviceInfo>,
23}
24
25#[cfg(target_os = "linux")]
26impl LinuxUsbWatcher {
27 pub fn new(tx: mpsc::Sender<UsbDeviceInfo>) -> Self {
37 Self { tx }
38 }
39
40 pub async fn start_monitoring(&self) -> Result<(), String> {
59 println!("Starting USB device monitoring on Linux...");
60
61 let mut known_devices: HashMap<String, UsbDeviceInfo> = HashMap::new();
63 let mut poll_interval = tokio::time::Duration::from_secs(1); loop {
66 let scan_start = std::time::Instant::now();
67
68 match self.scan_usb_devices().await {
69 Ok(current_devices) => {
70 let current_map: HashMap<String, UsbDeviceInfo> = current_devices
71 .into_iter()
72 .map(|d| {
73 let key = format!(
74 "{}:{}:{}",
75 d.vendor_id,
76 d.product_id,
77 d.serial_number.as_deref().unwrap_or("unknown")
78 );
79 (key, d)
80 })
81 .collect();
82
83 let mut events_sent = 0;
84
85 for (key, device) in ¤t_map {
87 if !known_devices.contains_key(key) {
88 let mut device_clone = device.clone();
89 device_clone.event_type = DeviceEventType::Connected;
90 if let Err(e) = self.tx.send(device_clone).await {
91 eprintln!("Failed to send device event: {e}");
92 } else {
93 events_sent += 1;
94 }
95 }
96 }
97
98 for (key, device) in &known_devices {
100 if !current_map.contains_key(key) {
101 let mut device_clone = device.clone();
102 device_clone.event_type = DeviceEventType::Disconnected;
103 if let Err(e) = self.tx.send(device_clone).await {
104 eprintln!("Failed to send device event: {e}");
105 } else {
106 events_sent += 1;
107 }
108 }
109 }
110
111 known_devices = current_map;
112
113 if events_sent > 0 {
115 poll_interval = tokio::time::Duration::from_millis(500);
116 } else {
118 poll_interval = std::cmp::min(
119 poll_interval + tokio::time::Duration::from_millis(100),
120 tokio::time::Duration::from_secs(5), );
122 }
123 }
124 Err(e) => {
125 eprintln!("Error scanning USB devices: {e}");
126 poll_interval =
128 std::cmp::min(poll_interval * 2, tokio::time::Duration::from_secs(10));
129 }
130 }
131
132 let scan_duration = scan_start.elapsed();
134 if scan_duration < poll_interval {
135 tokio::time::sleep(poll_interval - scan_duration).await;
136 }
137 }
138 }
139
140 async fn scan_usb_devices(&self) -> Result<Vec<UsbDeviceInfo>, String> {
141 let mut devices = Vec::new();
142 let usb_devices_path = Path::new("/sys/bus/usb/devices");
143
144 if !usb_devices_path.exists() {
145 return Err(
146 "USB devices path not found. Make sure you're running on Linux with USB support."
147 .to_string(),
148 );
149 }
150
151 let mut entries = fs::read_dir(usb_devices_path)
152 .await
153 .map_err(|e| e.to_string())?;
154
155 while let Some(entry) = entries.next_entry().await.map_err(|e| e.to_string())? {
156 let path = entry.path();
157
158 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
160 let is_device = (name.matches('-').count() == 1 && !name.contains(':'))
165 || name.starts_with("usb");
166 let is_interface = name.contains(':');
167
168 if is_device && !is_interface {
169 if let Ok(device_info) = self.parse_usb_device(&path).await {
170 if device_info.vendor_id != "0000" || device_info.product_id != "0000" {
172 devices.push(device_info);
173 }
174 } else {
175 println!("Failed to parse device: {name}");
176 }
177 }
178 }
179 }
180
181 Ok(devices)
182 }
183
184 async fn parse_usb_device(&self, device_path: &Path) -> Result<UsbDeviceInfo, String> {
185 let vendor_id = self
186 .read_sys_file(device_path, "idVendor")
187 .await
188 .unwrap_or_else(|| "0000".to_string());
189 let product_id = self
190 .read_sys_file(device_path, "idProduct")
191 .await
192 .unwrap_or_else(|| "0000".to_string());
193
194 let product_name = self
195 .read_sys_file(device_path, "product")
196 .await
197 .unwrap_or_else(|| "Unknown Device".to_string());
198 let manufacturer = self
199 .read_sys_file(device_path, "manufacturer")
200 .await
201 .unwrap_or_default();
202 let serial_number = self.read_sys_file(device_path, "serial").await;
203
204 let device_name = if !manufacturer.is_empty() && !product_name.is_empty() {
205 format!("{manufacturer} {product_name}")
206 } else if !product_name.is_empty() {
207 product_name
208 } else {
209 "Unknown Device".to_string()
210 };
211
212 let device_handle = DeviceHandle::Linux {
213 sysfs_path: device_path.to_string_lossy().to_string(),
214 device_node: None, };
216
217 Ok(UsbDeviceInfo::with_handle(
218 device_name,
219 vendor_id,
220 product_id,
221 serial_number,
222 DeviceEventType::Connected, device_handle,
224 ))
225 }
226 async fn read_sys_file(&self, device_path: &Path, filename: &str) -> Option<String> {
227 let file_path = device_path.join(filename);
228 fs::read_to_string(file_path)
229 .await
230 .ok()
231 .map(|s| s.trim().to_string())
232 .filter(|s| !s.is_empty())
233 }
234}
235
236#[cfg(not(target_os = "linux"))]
237pub struct LinuxUsbWatcher;
238
239#[cfg(not(target_os = "linux"))]
240impl LinuxUsbWatcher {
241 pub fn new(_tx: tokio::sync::mpsc::Sender<crate::device_info::UsbDeviceInfo>) -> Self {
242 Self
243 }
244
245 pub async fn start_monitoring(&self) -> Result<(), String> {
246 Err("Linux USB monitoring not available on this platform".to_string())
247 }
248}