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}