evdevil/
hotplug.rs

1//! Support for hotplug events.
2//!
3//! This module is currently Linux-specific. It uses the udev netlink socket to listen for events
4//! from a udev implementation.
5//!
6//! The recommended way to support device hotplug is to use the [`hotplug::enumerate`] function,
7//! which returns an iterator over all devices that are or will be plugged into the system.
8//!
9//! [`hotplug::enumerate`]: crate::hotplug::enumerate
10
11#[cfg(target_os = "linux")]
12mod linux;
13#[cfg(target_os = "linux")]
14use linux::Impl;
15
16#[cfg(target_os = "freebsd")]
17mod freebsd;
18#[cfg(target_os = "freebsd")]
19use freebsd::Impl;
20
21mod fallback;
22#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
23use fallback::Impl;
24
25use std::{
26    fmt, io,
27    os::fd::{AsRawFd, RawFd},
28};
29
30use crate::{Evdev, util::set_nonblocking};
31
32trait HotplugImpl: Sized + AsRawFd {
33    fn open() -> io::Result<Self>;
34    fn read(&self) -> io::Result<Evdev>;
35}
36
37/// Monitors the system for newly plugged in input devices.
38///
39/// This type implements [`Iterator`], which will block until the next event is received.
40pub struct HotplugMonitor {
41    imp: Impl,
42}
43
44impl fmt::Debug for HotplugMonitor {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.debug_struct("HotplugMonitor")
47            .field("fd", &self.as_raw_fd())
48            .finish()
49    }
50}
51
52impl AsRawFd for HotplugMonitor {
53    #[inline]
54    fn as_raw_fd(&self) -> RawFd {
55        self.imp.as_raw_fd()
56    }
57}
58
59impl HotplugMonitor {
60    /// Creates a new [`HotplugMonitor`] and starts listening for hotplug events.
61    ///
62    /// # Errors
63    ///
64    /// This will fail with [`io::ErrorKind::Unsupported`] on unsupported platforms.
65    /// Callers should degrade gracefully, by using only the currently plugged-in devices and not
66    /// supporting hotplug functionality.
67    ///
68    /// It may fail with other types of errors if connecting to the system's hotplug mechanism
69    /// fails.
70    pub fn new() -> io::Result<Self> {
71        Ok(Self { imp: Impl::open()? })
72    }
73
74    /// Moves the socket into or out of non-blocking mode.
75    ///
76    /// [`HotplugMonitor::next`] will return [`None`] when the socket is in non-blocking mode and
77    /// there are no incoming hotplug events.
78    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<bool> {
79        set_nonblocking(self.as_raw_fd(), nonblocking)
80    }
81}
82
83impl Iterator for HotplugMonitor {
84    type Item = io::Result<Evdev>;
85
86    fn next(&mut self) -> Option<Self::Item> {
87        match self.imp.read() {
88            Ok(dev) => Some(Ok(dev)),
89            Err(e) if e.kind() == io::ErrorKind::WouldBlock => None,
90            Err(e) => Some(Err(e)),
91        }
92    }
93}
94
95/// Enumerates all `evdev` devices, including hotplugged ones.
96///
97/// This will first yield all devices currently plugged in, and then starts yielding hotplug events
98/// similar to [`HotplugMonitor`].
99///
100/// This allows an application to process a single stream of [`Evdev`]s to both open an already
101/// plugged-in device on startup, but also to react to hot-plugged devices automatically, which is
102/// typically the desired UX of applications.
103///
104/// Like [`crate::enumerate`], this function returns a *blocking* iterator that might take a
105/// significant amount of time to open each device.
106/// This iterator will also keep blocking as it waits for hotplug events, but might terminate if
107/// hotplug events are unavailable.
108///
109/// If hotplug support is unimplemented on the current platform, this will degrade gracefully and
110/// only yield the currently plugged-in devices.
111pub fn enumerate() -> io::Result<impl Iterator<Item = io::Result<Evdev>>> {
112    let monitor = match HotplugMonitor::new() {
113        Ok(m) => Some(m),
114        Err(e) if e.kind() == io::ErrorKind::Unsupported => {
115            log::warn!("hotplug is not supported on this platform; hotplugged devices won't work");
116            None
117        }
118        Err(e) => return Err(e),
119    };
120    Ok(crate::enumerate()?.chain(monitor.into_iter().flatten()))
121}