backend.udev: rework as an event source

This commit is contained in:
Victor Berger 2020-04-27 11:20:12 +02:00 committed by Victor Berger
parent 104774eeb0
commit 31f1f4f9e0
2 changed files with 143 additions and 94 deletions

View File

@ -32,7 +32,7 @@ use smithay::{
auto::{auto_session_bind, AutoSession}, auto::{auto_session_bind, AutoSession},
notify_multiplexer, AsSessionObserver, Session, SessionNotifier, notify_multiplexer, AsSessionObserver, Session, SessionNotifier,
}, },
udev::{primary_gpu, udev_backend_bind, UdevBackend, UdevHandler}, udev::{primary_gpu, UdevBackend, UdevEvent},
}, },
reexports::{ reexports::{
calloop::{generic::Generic, EventLoop, LoopHandle, Source}, calloop::{generic::Generic, EventLoop, LoopHandle, Source},
@ -124,8 +124,9 @@ pub fn run_udev(
let primary_gpu = primary_gpu(&seat).unwrap_or_default(); let primary_gpu = primary_gpu(&seat).unwrap_or_default();
let bytes = include_bytes!("../resources/cursor2.rgba"); let bytes = include_bytes!("../resources/cursor2.rgba");
let udev_backend = UdevBackend::new( let udev_backend = UdevBackend::new(seat.clone(), log.clone()).map_err(|_| ())?;
UdevHandlerImpl {
let mut udev_handler = UdevHandlerImpl {
compositor_token: state.ctoken, compositor_token: state.ctoken,
#[cfg(feature = "egl")] #[cfg(feature = "egl")]
egl_buffer_reader, egl_buffer_reader,
@ -141,11 +142,7 @@ pub fn run_udev(
loop_handle: event_loop.handle(), loop_handle: event_loop.handle(),
notifier: udev_notifier, notifier: udev_notifier,
logger: log.clone(), logger: log.clone(),
}, };
seat.clone(),
log.clone(),
)
.map_err(|_| ())?;
/* /*
* Initialize wayland input object * Initialize wayland input object
@ -226,7 +223,18 @@ pub fn run_udev(
let session_event_source = auto_session_bind(notifier, event_loop.handle()) let session_event_source = auto_session_bind(notifier, event_loop.handle())
.map_err(|(e, _)| e) .map_err(|(e, _)| e)
.unwrap(); .unwrap();
let udev_event_source = udev_backend_bind(udev_backend, &event_loop.handle())
for (dev, path) in udev_backend.device_list() {
udev_handler.device_added(dev, path.into())
}
let udev_event_source = event_loop
.handle()
.insert_source(udev_backend, move |event, _, _state| match event {
UdevEvent::Added { device_id, path } => udev_handler.device_added(device_id, path),
UdevEvent::Changed { device_id } => udev_handler.device_changed(device_id),
UdevEvent::Removed { device_id } => udev_handler.device_removed(device_id),
})
.map_err(|e| -> IoError { e.into() }) .map_err(|e| -> IoError { e.into() })
.unwrap(); .unwrap();
@ -375,7 +383,7 @@ impl<S: SessionNotifier, Data: 'static> UdevHandlerImpl<S, Data> {
} }
} }
impl<S: SessionNotifier, Data: 'static> UdevHandler for UdevHandlerImpl<S, Data> { impl<S: SessionNotifier, Data: 'static> UdevHandlerImpl<S, Data> {
fn device_added(&mut self, _device: dev_t, path: PathBuf) { fn device_added(&mut self, _device: dev_t, path: PathBuf) {
// Try to open the device // Try to open the device
if let Some(mut device) = self if let Some(mut device) = self

View File

@ -1,8 +1,38 @@
//! //!
//! Provides `udev` related functionality for automated device scanning. //! Provides `udev` related functionality for automated device scanning.
//! //!
//! This module mainly provides the [`UdevBackend`](::backend::udev::UdevBackend), which constantly monitors available DRM devices //! This module mainly provides the [`UdevBackend`](::backend::udev::UdevBackend), which
//! and notifies a user supplied [`UdevHandler`](::backend::udev::UdevHandler) of any changes. //! monitors available DRM devices and acts as an event source, generating events whenever these
//! devices change.
//!
//! *Note:* Once inserted into the event loop, the [`UdevBackend`](::backend::udev::UdevBackend) will
//! only notify you about *changes* in the device list. To get an initial snapshot of the state during
//! your initialization, you need to call its `device_list` method.
//!
//! ```no_run
//! use smithay::backend::udev::{UdevBackend, UdevEvent};
//!
//! let udev = UdevBackend::new("seat0", None).expect("Failed to monitor udev.");
//!
//! for (dev_id, node_path) in udev.device_list() {
//! // process the initial list of devices
//! }
//!
//! # let event_loop = smithay::reexports::calloop::EventLoop::<()>::new().unwrap();
//! # let loop_handle = event_loop.handle();
//! // setup the event source for long-term monitoring
//! loop_handle.insert_source(udev, |event, _, _dispatch_data| match event {
//! UdevEvent::Added { device_id, path } => {
//! // a new device has been added
//! },
//! UdevEvent::Changed { device_id } => {
//! // a device has been changed
//! },
//! UdevEvent::Removed { device_id } => {
//! // a device has been removed
//! }
//! }).expect("Failed to insert the udev source into the event loop");
//! ```
//! //!
//! Additionally this contains some utility functions related to scanning. //! Additionally this contains some utility functions related to scanning.
//! //!
@ -11,7 +41,7 @@
use nix::sys::stat::{dev_t, stat}; use nix::sys::stat::{dev_t, stat};
use std::{ use std::{
collections::HashSet, collections::HashMap,
ffi::OsString, ffi::OsString,
io::Result as IoResult, io::Result as IoResult,
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
@ -19,34 +49,32 @@ use std::{
}; };
use udev::{Enumerator, EventType, MonitorBuilder, MonitorSocket}; use udev::{Enumerator, EventType, MonitorBuilder, MonitorSocket};
use calloop::{generic::Generic, InsertError, LoopHandle, Source}; use calloop::{EventSource, Interest, Mode, Poll, Readiness, Token};
/// Backend to monitor available drm devices. /// Backend to monitor available drm devices.
/// ///
/// Provides a way to automatically scan for available gpus and notifies the /// Provides a way to automatically scan for available gpus and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for gpus and /// given handler of any changes. Can be used to provide hot-plug functionality for gpus and
/// attached monitors. /// attached monitors.
pub struct UdevBackend<T: UdevHandler + 'static> { pub struct UdevBackend {
devices: HashSet<dev_t>, devices: HashMap<dev_t, PathBuf>,
monitor: MonitorSocket, monitor: MonitorSocket,
handler: T,
logger: ::slog::Logger, logger: ::slog::Logger,
} }
impl<T: UdevHandler + 'static> AsRawFd for UdevBackend<T> { impl AsRawFd for UdevBackend {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
self.monitor.as_raw_fd() self.monitor.as_raw_fd()
} }
} }
impl<T: UdevHandler + 'static> UdevBackend<T> { impl UdevBackend {
/// Creates a new [`UdevBackend`] /// Creates a new [`UdevBackend`]
/// ///
/// ## Arguments /// ## Arguments
/// `handler` - User-provided handler to respond to any detected changes /// `seat` - system seat which should be bound
/// `seat` -
/// `logger` - slog Logger to be used by the backend and its `DrmDevices`. /// `logger` - slog Logger to be used by the backend and its `DrmDevices`.
pub fn new<L, S: AsRef<str>>(mut handler: T, seat: S, logger: L) -> IoResult<UdevBackend<T>> pub fn new<L, S: AsRef<str>>(seat: S, logger: L) -> IoResult<UdevBackend>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
@ -56,10 +84,7 @@ impl<T: UdevHandler + 'static> UdevBackend<T> {
.into_iter() .into_iter()
// Create devices // Create devices
.flat_map(|path| match stat(&path) { .flat_map(|path| match stat(&path) {
Ok(stat) => { Ok(stat) => Some((stat.st_rdev, path)),
handler.device_added(stat.st_rdev, path);
Some(stat.st_rdev)
}
Err(err) => { Err(err) => {
warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err); warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err);
None None
@ -72,93 +97,109 @@ impl<T: UdevHandler + 'static> UdevBackend<T> {
Ok(UdevBackend { Ok(UdevBackend {
devices, devices,
monitor, monitor,
handler,
logger: log, logger: log,
}) })
} }
}
impl<T: UdevHandler + 'static> Drop for UdevBackend<T> { /// Get a list of DRM devices currently known to the backend
fn drop(&mut self) {
for device in &self.devices {
self.handler.device_removed(*device);
}
}
}
/// calloop event source associated with the Udev backend
pub type UdevSource<T> = Generic<UdevBackend<T>>;
/// Binds a [`UdevBackend`] to a given [`EventLoop`](calloop::EventLoop).
/// ///
/// Allows the backend to receive kernel events and thus to drive the [`UdevHandler`]. /// You should call this once before inserting the event source into your
/// No runtime functionality can be provided without using this function. /// event loop, to get an initial snapshot of the device state.
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>( pub fn device_list(&self) -> impl Iterator<Item = (dev_t, &Path)> {
udev: UdevBackend<T>, self.devices.iter().map(|(&id, path)| (id, path.as_ref()))
handle: &LoopHandle<Data>, }
) -> Result<Source<UdevSource<T>>, InsertError<UdevSource<T>>> {
let source = Generic::new(udev, calloop::Interest::Readable, calloop::Mode::Level);
handle.insert_source(source, |_, backend, _| {
backend.process_events();
Ok(())
})
} }
impl<T: UdevHandler + 'static> UdevBackend<T> { impl EventSource for UdevBackend {
fn process_events(&mut self) { type Event = UdevEvent;
type Metadata = ();
type Ret = ();
fn process_events<F>(&mut self, _: Readiness, _: Token, mut callback: F) -> std::io::Result<()>
where
F: FnMut(UdevEvent, &mut ()),
{
let monitor = self.monitor.clone(); let monitor = self.monitor.clone();
for event in monitor { for event in monitor {
debug!(
self.logger,
"Udev event: type={}, devnum={:?} devnode={:?}",
event.event_type(),
event.devnum(),
event.devnode()
);
match event.event_type() { match event.event_type() {
// New device // New device
EventType::Add => { EventType::Add => {
info!(self.logger, "Device Added");
if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) { if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) {
if self.devices.insert(devnum) { info!(self.logger, "New device: #{} at {}", devnum, path.display());
self.handler.device_added(devnum, path.to_path_buf()); if self.devices.insert(devnum, path.to_path_buf()).is_none() {
callback(
UdevEvent::Added {
device_id: devnum,
path: path.to_path_buf(),
},
&mut (),
);
} }
} }
} }
// Device removed // Device removed
EventType::Remove => { EventType::Remove => {
info!(self.logger, "Device Remove");
if let Some(devnum) = event.devnum() { if let Some(devnum) = event.devnum() {
if self.devices.remove(&devnum) { info!(self.logger, "Device removed: #{}", devnum);
self.handler.device_removed(devnum); if self.devices.remove(&devnum).is_some() {
callback(UdevEvent::Removed { device_id: devnum }, &mut ());
} }
} }
} }
// New connector // New connector
EventType::Change => { EventType::Change => {
info!(self.logger, "Device Changed");
if let Some(devnum) = event.devnum() { if let Some(devnum) = event.devnum() {
info!(self.logger, "Devnum: {:b}", devnum); info!(self.logger, "Device changed: #{}", devnum);
if self.devices.contains(&devnum) { if self.devices.contains_key(&devnum) {
self.handler.device_changed(devnum); callback(UdevEvent::Changed { device_id: devnum }, &mut ());
} else { }
info!(self.logger, "changed, but device not tracked by backend");
};
} else {
info!(self.logger, "changed, but no devnum");
} }
} }
_ => {} _ => {}
} }
} }
Ok(())
}
fn register(&mut self, poll: &mut Poll, token: Token) -> std::io::Result<()> {
poll.register(self.as_raw_fd(), Interest::Readable, Mode::Level, token)
}
fn reregister(&mut self, poll: &mut Poll, token: Token) -> std::io::Result<()> {
poll.reregister(self.as_raw_fd(), Interest::Readable, Mode::Level, token)
}
fn unregister(&mut self, poll: &mut Poll) -> std::io::Result<()> {
poll.unregister(self.as_raw_fd())
} }
} }
/// Handler for the [`UdevBackend`], allows to open, close and update drm devices as they change during runtime. /// Events generated by the [`UdevBackend`], notifying you of changes in system devices
pub trait UdevHandler { pub enum UdevEvent {
/// Called when a new device is detected. /// A new device has been detected
fn device_added(&mut self, device: dev_t, path: PathBuf); Added {
/// Called when an open device is changed. /// ID of the new device
/// device_id: dev_t,
/// This usually indicates that some connectors did become available or were unplugged. The handler /// Path of the new device
/// should scan again for connected monitors and mode switch accordingly. path: PathBuf,
fn device_changed(&mut self, device: dev_t); },
/// Called when a device was removed. /// A device has changed
fn device_removed(&mut self, device: dev_t); Changed {
/// ID of the changed device
device_id: dev_t,
},
/// A device has been removed
Removed {
/// ID of the removed device
device_id: dev_t,
},
} }
/// Returns the path of the primary GPU device if any /// Returns the path of the primary GPU device if any