use mio;
use std::ffi::OsString;
use std::io;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use udev;
struct Notify {
var: Condvar,
m: Mutex<()>,
}
impl Notify {
fn new() -> Arc<Self> {
Arc::new(Self {
var: Condvar::new(),
m: Mutex::new(()),
})
}
fn notify(n: Arc<Self>) {
let Notify { var, .. } = &*n;
var.notify_all()
}
fn wait(n: Arc<Self>) {
let Notify { var, m } = &*n;
let m = m.lock().unwrap();
let _ignore_poisoned = var.wait(m);
}
}
#[derive(Debug)]
pub enum EventKind {
Added,
Removed,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Device {
pub system_path: OsString,
pub device_path: OsString,
pub device_node: Option<OsString>,
}
impl Device {
pub fn stable_id(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.device_path.hash(&mut hasher);
hasher.finish()
}
}
impl From<udev::Device> for Device {
fn from(d: udev::Device) -> Self {
Self {
system_path: d.syspath().into(),
device_path: d.devpath().into(),
device_node: d.devnode().map(OsString::from),
}
}
}
#[derive(Debug)]
pub struct Event {
pub device: Device,
pub kind: EventKind,
}
const WAKE_TOKEN: mio::Token = mio::Token(10);
pub struct Watcher {
waker: Arc<mio::Waker>,
thread: Option<thread::JoinHandle<Result<(), io::Error>>>,
}
impl Watcher {
pub fn spawn(f: (impl FnMut(Event) + Send + 'static)) -> Result<Self, io::Error> {
let poll = mio::Poll::new()?;
let initialized = Notify::new();
let waker = Arc::new(mio::Waker::new(poll.registry(), WAKE_TOKEN)?);
let initialized_inner = initialized.clone();
let thread = Some(thread::spawn(move ||
watch(poll, initialized_inner, f)
));
Notify::wait(initialized);
Ok(Self {
waker,
thread,
})
}
pub fn stop(mut self) -> Result<Result<(), io::Error>, (Self, io::Error)> {
match self.waker.wake() {
Ok(()) => Ok({
if let Some(thread) = self.thread.take() {
thread.join().unwrap()
} else {
Ok(())
}
}),
Err(e) => Err((self, e)),
}
}
}
impl Drop for Watcher {
fn drop(&mut self) {
let _ = self.waker.wake();
}
}
fn watch(
mut poll: mio::Poll,
initialized: Arc<Notify>,
mut f: impl FnMut(Event),
) -> io::Result<()> {
let mut socket = udev::MonitorBuilder::new()?
.match_subsystem("media")?
.match_subsystem("video4linux")?
.listen()?;
let mut events = mio::Events::with_capacity(16);
poll.registry().register(
&mut socket,
mio::Token(0),
mio::Interest::READABLE | mio::Interest::WRITABLE,
)?;
let mut e = udev::Enumerator::new()?;
e.match_subsystem("media")?;
e.match_subsystem("video4linux")?;
e.match_is_initialized()?;
for device in e.scan_devices()? {
f(Event { device: device.into(), kind: EventKind::Added });
}
Notify::notify(initialized);
loop {
poll.poll(&mut events, None)?;
let stop_message = events.iter().find(
|ev| ev.is_readable() && ev.token() == WAKE_TOKEN
);
if stop_message.is_some() {
break;
}
for ev in socket.iter() {
let kind = match ev.event_type() {
udev::EventType::Add => Some(EventKind::Added),
udev::EventType::Remove => Some(EventKind::Removed),
_ => None,
};
if let Some(kind) = kind {
f(Event { device: ev.device().into(), kind })
}
}
}
Ok(())
}