use std::collections::HashMap;
use tokio::sync::mpsc;
use tracing::{info, debug, warn, error};
#[derive(Debug, Clone)]
pub enum DeviceEvent {
Add {
devnode: String,
device_id: String,
},
Remove {
devnode: String,
device_id: String,
},
}
pub struct DeviceMonitor {
receiver: mpsc::Receiver<DeviceEvent>,
}
impl DeviceMonitor {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let (tx, receiver) = mpsc::channel(32);
tokio::task::spawn_blocking(move || {
if let Err(e) = run_udev_monitor(tx) {
error!("Udev monitor task exited with error: {}", e);
}
});
Ok(Self { receiver })
}
pub async fn recv(&mut self) -> Option<DeviceEvent> {
self.receiver.recv().await
}
}
pub fn format_device_id(vendor_id: u16, product_id: u16) -> String {
format!("{:04x}:{:04x}", vendor_id, product_id)
}
fn run_udev_monitor(tx: mpsc::Sender<DeviceEvent>) -> Result<(), Box<dyn std::error::Error>> {
use udev::{MonitorBuilder, EventType};
info!("Starting udev monitor for input subsystem");
let socket = MonitorBuilder::new()?
.match_subsystem("input")?
.listen()?;
debug!("Udev monitor listening on netlink socket");
let mut device_map: HashMap<String, String> = HashMap::new();
for event in socket.iter() {
let devnode = match event.devnode() {
Some(path) => path.to_string_lossy().to_string(),
None => continue,
};
match event.event_type() {
EventType::Add => {
debug!("Device add event: {}", devnode);
if let Ok(device_id) = extract_device_id(&devnode) {
debug!("Device ID for {}: {}", devnode, device_id);
device_map.insert(devnode.clone(), device_id.clone());
let event = DeviceEvent::Add {
devnode,
device_id,
};
if let Err(e) = tx.blocking_send(event) {
warn!("Failed to send device add event: {}", e);
break;
}
} else {
debug!("Skipping device without VID/PID: {}", devnode);
}
}
EventType::Remove => {
debug!("Device remove event: {}", devnode);
if let Some(device_id) = device_map.remove(&devnode) {
let event = DeviceEvent::Remove {
devnode,
device_id,
};
if let Err(e) = tx.blocking_send(event) {
warn!("Failed to send device remove event: {}", e);
break;
}
} else {
debug!("Remove event for untracked device: {}", devnode);
}
}
_ => {
debug!("Ignoring udev event: {:?}", event.event_type());
}
}
}
info!("Udev monitor task exited");
Ok(())
}
fn extract_device_id(devnode: &str) -> Result<String, Box<dyn std::error::Error>> {
use evdev::Device;
let device = Device::open(devnode)?;
let input_id = device.input_id();
let vendor_id = input_id.vendor();
let product_id = input_id.product();
if vendor_id == 0 && product_id == 0 {
return Err("Device has no vendor/product ID".into());
}
Ok(format_device_id(vendor_id, product_id))
}
pub async fn handle_device_remove(
device_id: &str,
_state: &tokio::sync::RwLock<crate::DaemonState>,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Device {} removed - LED state preserved in memory", device_id);
Ok(())
}
pub async fn handle_device_add(
device_id: &str,
state: &tokio::sync::RwLock<crate::DaemonState>,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Device {} added - checking LED state restoration", device_id);
let has_led_state = {
let state_guard = state.read().await;
let led_state = state_guard.led_state.read().await;
led_state.contains_key(device_id)
};
if has_led_state {
info!("Restoring LED state for device {}", device_id);
match crate::led_controller::LedController::find_led_interface() {
Ok(led_controller) => {
let saved_state = {
let state_guard = state.read().await;
let led_state = state_guard.led_state.read().await;
led_state.get(device_id).cloned()
};
if let Some(saved) = saved_state {
if let Err(e) = led_controller.import_state(saved).await {
warn!("Failed to restore LED state for {}: {}", device_id, e);
} else {
info!("LED state restored for device {}", device_id);
}
}
let mut state_guard = state.write().await;
state_guard.set_led_controller(Some(std::sync::Arc::new(led_controller))).await;
}
Err(e) => {
warn!("Failed to open LED controller for {}: {}", device_id, e);
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_device_id() {
assert_eq!(format_device_id(0x1532, 0x0220), "1532:0220");
assert_eq!(format_device_id(0xABCD, 0x1234), "abcd:1234");
assert_eq!(format_device_id(0x0001, 0x0002), "0001:0002");
}
#[test]
fn test_device_event_add() {
let event = DeviceEvent::Add {
devnode: "/dev/input/event0".to_string(),
device_id: "1532:0220".to_string(),
};
match event {
DeviceEvent::Add { devnode, device_id } => {
assert_eq!(devnode, "/dev/input/event0");
assert_eq!(device_id, "1532:0220");
}
_ => panic!("Expected Add event"),
}
}
#[test]
fn test_device_event_remove() {
let event = DeviceEvent::Remove {
devnode: "/dev/input/event1".to_string(),
device_id: "046d:c52b".to_string(),
};
match event {
DeviceEvent::Remove { devnode, device_id } => {
assert_eq!(devnode, "/dev/input/event1");
assert_eq!(device_id, "046d:c52b");
}
_ => panic!("Expected Remove event"),
}
}
}