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}