evdevil/hotplug.rs
1//! Support for hotplug events.
2//!
3//! The recommended way to support device hotplug in applications is to use the
4//! [`enumerate_hotplug`] function, which returns an iterator over all devices that are or will be
5//! plugged into the system.
6//!
7//! # Platform Support
8//!
9//! Hotplug functionality is supported on Linux and FreeBSD, as follows:
10//!
11//! | OS | Details |
12//! |---------|---------|
13//! | Linux | Uses the `NETLINK_KOBJECT_UEVENT` socket. Requires `udev`. |
14//! | FreeBSD | Uses `devd`'s seqpacket socket at `/var/run/devd.seqpacket.pipe`. |
15//!
16//! [`enumerate_hotplug`]: crate::enumerate_hotplug
17
18#[cfg(target_os = "linux")]
19mod linux;
20#[cfg(target_os = "linux")]
21use linux::Impl;
22
23#[cfg(target_os = "freebsd")]
24mod freebsd;
25#[cfg(target_os = "freebsd")]
26use freebsd::Impl;
27
28mod fallback;
29#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
30use fallback::Impl;
31
32use std::{
33 fmt, io,
34 os::{
35 fd::{AsFd, AsRawFd, IntoRawFd, RawFd},
36 unix::prelude::BorrowedFd,
37 },
38 path::{Path, PathBuf},
39};
40
41use crate::{Evdev, util::set_nonblocking};
42
43trait HotplugImpl: Sized + AsRawFd + IntoRawFd {
44 fn open() -> io::Result<Self>;
45 fn read(&self) -> io::Result<HotplugEvent>;
46}
47
48/// Monitors the system for newly plugged in input devices.
49///
50/// Iterating over the hotplug events will yield [`io::Result`]s that may be arbitrary
51/// [`io::Error`]s that occurred while attempting to open a device.
52/// These error may happen at any point, since devices may be removed anytime (resulting in a
53/// [`NotFound`][io::ErrorKind::NotFound] error or some other error).
54/// Applications should handle these errors non-fatally.
55pub struct HotplugMonitor {
56 imp: Impl,
57}
58
59impl fmt::Debug for HotplugMonitor {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.debug_struct("HotplugMonitor")
62 .field("fd", &self.as_raw_fd())
63 .finish()
64 }
65}
66
67impl AsRawFd for HotplugMonitor {
68 #[inline]
69 fn as_raw_fd(&self) -> RawFd {
70 self.imp.as_raw_fd()
71 }
72}
73
74impl IntoRawFd for HotplugMonitor {
75 #[inline]
76 fn into_raw_fd(self) -> RawFd {
77 self.imp.into_raw_fd()
78 }
79}
80
81impl AsFd for HotplugMonitor {
82 #[inline]
83 fn as_fd(&self) -> BorrowedFd<'_> {
84 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
85 }
86}
87
88impl HotplugMonitor {
89 /// Creates a new [`HotplugMonitor`] and starts listening for hotplug events.
90 ///
91 /// This operation is always blocking.
92 ///
93 /// # Errors
94 ///
95 /// This will fail with [`io::ErrorKind::Unsupported`] on unsupported platforms.
96 /// It may also fail with other types of errors if connecting to the system's hotplug mechanism
97 /// fails.
98 ///
99 /// Callers should degrade gracefully, by using only the currently plugged-in devices and not
100 /// supporting hotplug functionality.
101 pub fn new() -> io::Result<Self> {
102 Ok(Self { imp: Impl::open()? })
103 }
104
105 /// Moves the socket into or out of non-blocking mode.
106 ///
107 /// [`Iter::next`] and [`IntoIter::next`] will return [`None`] when the socket is in
108 /// non-blocking mode and there are no incoming hotplug events.
109 ///
110 /// Note that the act of opening a device is always blocking, and may block for a significant
111 /// amount of time, so "non-blocking" operation only covers generation of [`HotplugEvent`]s,
112 /// not opening the device the events refer to.
113 pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<bool> {
114 set_nonblocking(self.as_raw_fd(), nonblocking)
115 }
116
117 /// Returns an iterator that yields hotplug events.
118 pub fn iter(&self) -> Iter<'_> {
119 Iter(self)
120 }
121}
122
123impl IntoIterator for HotplugMonitor {
124 type Item = io::Result<HotplugEvent>;
125 type IntoIter = IntoIter;
126
127 #[inline]
128 fn into_iter(self) -> Self::IntoIter {
129 IntoIter(self)
130 }
131}
132
133impl<'a> IntoIterator for &'a HotplugMonitor {
134 type Item = io::Result<HotplugEvent>;
135 type IntoIter = Iter<'a>;
136
137 #[inline]
138 fn into_iter(self) -> Self::IntoIter {
139 Iter(self)
140 }
141}
142
143/// An owning [`Iterator`] over hotplug events.
144///
145/// Returned by [`HotplugMonitor::into_iter`].
146///
147/// If [`HotplugMonitor::set_nonblocking`] has been used to put the [`HotplugMonitor`] in
148/// non-blocking mode, this iterator will yield [`None`] when no events are pending.
149/// Otherwise, it will block until a hotplug event arrives.
150#[derive(Debug)]
151pub struct IntoIter(HotplugMonitor);
152
153impl Iterator for IntoIter {
154 type Item = io::Result<HotplugEvent>;
155
156 fn next(&mut self) -> Option<Self::Item> {
157 match self.0.imp.read() {
158 Ok(ev) => Some(Ok(ev)),
159 Err(e) if e.kind() == io::ErrorKind::WouldBlock => None,
160 Err(e) => Some(Err(e)),
161 }
162 }
163}
164
165/// An [`Iterator`] over hotplug events.
166///
167/// Returned by [`HotplugMonitor::iter`].
168///
169/// If [`HotplugMonitor::set_nonblocking`] has been used to put the [`HotplugMonitor`] in
170/// non-blocking mode, this iterator will yield [`None`] when no events are pending.
171/// Otherwise, it will block until a hotplug event arrives.
172#[derive(Debug)]
173pub struct Iter<'a>(&'a HotplugMonitor);
174
175impl<'a> Iterator for Iter<'a> {
176 type Item = io::Result<HotplugEvent>;
177
178 fn next(&mut self) -> Option<Self::Item> {
179 match self.0.imp.read() {
180 Ok(ev) => Some(Ok(ev)),
181 Err(e) if e.kind() == io::ErrorKind::WouldBlock => None,
182 Err(e) => Some(Err(e)),
183 }
184 }
185}
186
187/// An event emitted by the [`HotplugMonitor`].
188#[derive(Debug, Clone)]
189pub struct HotplugEvent {
190 path: PathBuf,
191}
192
193impl HotplugEvent {
194 /// Returns the device path indicated by this event.
195 pub fn path(&self) -> &Path {
196 &self.path
197 }
198
199 /// Opens the [`Evdev`] indicated by this event.
200 ///
201 /// This operation is always blocking, and may block for a significant amount of time.
202 pub fn open(&self) -> io::Result<Evdev> {
203 Evdev::open(&self.path)
204 }
205}