From 31f1f4f9e09a853b2039e943722699bb622e4f0a Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Mon, 27 Apr 2020 11:20:12 +0200 Subject: [PATCH] backend.udev: rework as an event source --- anvil/src/udev.rs | 58 +++++++------- src/backend/udev.rs | 179 +++++++++++++++++++++++++++----------------- 2 files changed, 143 insertions(+), 94 deletions(-) diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 393cae2..206c909 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -32,7 +32,7 @@ use smithay::{ auto::{auto_session_bind, AutoSession}, notify_multiplexer, AsSessionObserver, Session, SessionNotifier, }, - udev::{primary_gpu, udev_backend_bind, UdevBackend, UdevHandler}, + udev::{primary_gpu, UdevBackend, UdevEvent}, }, reexports::{ calloop::{generic::Generic, EventLoop, LoopHandle, Source}, @@ -124,28 +124,25 @@ pub fn run_udev( let primary_gpu = primary_gpu(&seat).unwrap_or_default(); let bytes = include_bytes!("../resources/cursor2.rgba"); - let udev_backend = UdevBackend::new( - UdevHandlerImpl { - compositor_token: state.ctoken, - #[cfg(feature = "egl")] - egl_buffer_reader, - session: session.clone(), - backends: HashMap::new(), - display: display.clone(), - primary_gpu, - window_map: state.window_map.clone(), - pointer_location: pointer_location.clone(), - pointer_image: ImageBuffer::from_raw(64, 64, bytes.to_vec()).unwrap(), - cursor_status: cursor_status.clone(), - dnd_icon: state.dnd_icon.clone(), - loop_handle: event_loop.handle(), - notifier: udev_notifier, - logger: log.clone(), - }, - seat.clone(), - log.clone(), - ) - .map_err(|_| ())?; + let udev_backend = UdevBackend::new(seat.clone(), log.clone()).map_err(|_| ())?; + + let mut udev_handler = UdevHandlerImpl { + compositor_token: state.ctoken, + #[cfg(feature = "egl")] + egl_buffer_reader, + session: session.clone(), + backends: HashMap::new(), + display: display.clone(), + primary_gpu, + window_map: state.window_map.clone(), + pointer_location: pointer_location.clone(), + pointer_image: ImageBuffer::from_raw(64, 64, bytes.to_vec()).unwrap(), + cursor_status: cursor_status.clone(), + dnd_icon: state.dnd_icon.clone(), + loop_handle: event_loop.handle(), + notifier: udev_notifier, + logger: log.clone(), + }; /* * Initialize wayland input object @@ -226,7 +223,18 @@ pub fn run_udev( let session_event_source = auto_session_bind(notifier, event_loop.handle()) .map_err(|(e, _)| e) .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() }) .unwrap(); @@ -375,7 +383,7 @@ impl UdevHandlerImpl { } } -impl UdevHandler for UdevHandlerImpl { +impl UdevHandlerImpl { fn device_added(&mut self, _device: dev_t, path: PathBuf) { // Try to open the device if let Some(mut device) = self diff --git a/src/backend/udev.rs b/src/backend/udev.rs index d0baeaa..75d4b53 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -1,8 +1,38 @@ //! //! Provides `udev` related functionality for automated device scanning. //! -//! This module mainly provides the [`UdevBackend`](::backend::udev::UdevBackend), which constantly monitors available DRM devices -//! and notifies a user supplied [`UdevHandler`](::backend::udev::UdevHandler) of any changes. +//! This module mainly provides the [`UdevBackend`](::backend::udev::UdevBackend), which +//! 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. //! @@ -11,7 +41,7 @@ use nix::sys::stat::{dev_t, stat}; use std::{ - collections::HashSet, + collections::HashMap, ffi::OsString, io::Result as IoResult, os::unix::io::{AsRawFd, RawFd}, @@ -19,34 +49,32 @@ use std::{ }; 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. /// /// 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 /// attached monitors. -pub struct UdevBackend { - devices: HashSet, +pub struct UdevBackend { + devices: HashMap, monitor: MonitorSocket, - handler: T, logger: ::slog::Logger, } -impl AsRawFd for UdevBackend { +impl AsRawFd for UdevBackend { fn as_raw_fd(&self) -> RawFd { self.monitor.as_raw_fd() } } -impl UdevBackend { +impl UdevBackend { /// Creates a new [`UdevBackend`] /// /// ## Arguments - /// `handler` - User-provided handler to respond to any detected changes - /// `seat` - + /// `seat` - system seat which should be bound /// `logger` - slog Logger to be used by the backend and its `DrmDevices`. - pub fn new>(mut handler: T, seat: S, logger: L) -> IoResult> + pub fn new>(seat: S, logger: L) -> IoResult where L: Into>, { @@ -56,10 +84,7 @@ impl UdevBackend { .into_iter() // Create devices .flat_map(|path| match stat(&path) { - Ok(stat) => { - handler.device_added(stat.st_rdev, path); - Some(stat.st_rdev) - } + Ok(stat) => Some((stat.st_rdev, path)), Err(err) => { warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err); None @@ -72,93 +97,109 @@ impl UdevBackend { Ok(UdevBackend { devices, monitor, - handler, logger: log, }) } -} -impl Drop for UdevBackend { - fn drop(&mut self) { - for device in &self.devices { - self.handler.device_removed(*device); - } + /// Get a list of DRM devices currently known to the backend + /// + /// You should call this once before inserting the event source into your + /// event loop, to get an initial snapshot of the device state. + pub fn device_list(&self) -> impl Iterator { + self.devices.iter().map(|(&id, path)| (id, path.as_ref())) } } -/// calloop event source associated with the Udev backend -pub type UdevSource = Generic>; +impl EventSource for UdevBackend { + type Event = UdevEvent; + type Metadata = (); + type Ret = (); -/// Binds a [`UdevBackend`] to a given [`EventLoop`](calloop::EventLoop). -/// -/// Allows the backend to receive kernel events and thus to drive the [`UdevHandler`]. -/// No runtime functionality can be provided without using this function. -pub fn udev_backend_bind( - udev: UdevBackend, - handle: &LoopHandle, -) -> Result>, InsertError>> { - let source = Generic::new(udev, calloop::Interest::Readable, calloop::Mode::Level); - - handle.insert_source(source, |_, backend, _| { - backend.process_events(); - Ok(()) - }) -} - -impl UdevBackend { - fn process_events(&mut self) { + fn process_events(&mut self, _: Readiness, _: Token, mut callback: F) -> std::io::Result<()> + where + F: FnMut(UdevEvent, &mut ()), + { let monitor = self.monitor.clone(); for event in monitor { + debug!( + self.logger, + "Udev event: type={}, devnum={:?} devnode={:?}", + event.event_type(), + event.devnum(), + event.devnode() + ); match event.event_type() { // New device EventType::Add => { - info!(self.logger, "Device Added"); if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) { - if self.devices.insert(devnum) { - self.handler.device_added(devnum, path.to_path_buf()); + info!(self.logger, "New device: #{} at {}", devnum, path.display()); + 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 EventType::Remove => { - info!(self.logger, "Device Remove"); if let Some(devnum) = event.devnum() { - if self.devices.remove(&devnum) { - self.handler.device_removed(devnum); + info!(self.logger, "Device removed: #{}", devnum); + if self.devices.remove(&devnum).is_some() { + callback(UdevEvent::Removed { device_id: devnum }, &mut ()); } } } // New connector EventType::Change => { - info!(self.logger, "Device Changed"); if let Some(devnum) = event.devnum() { - info!(self.logger, "Devnum: {:b}", devnum); - if self.devices.contains(&devnum) { - self.handler.device_changed(devnum); - } else { - info!(self.logger, "changed, but device not tracked by backend"); - }; - } else { - info!(self.logger, "changed, but no devnum"); + info!(self.logger, "Device changed: #{}", devnum); + if self.devices.contains_key(&devnum) { + callback(UdevEvent::Changed { device_id: devnum }, &mut ()); + } } } _ => {} } } + 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. -pub trait UdevHandler { - /// Called when a new device is detected. - fn device_added(&mut self, device: dev_t, path: PathBuf); - /// Called when an open device is changed. - /// - /// This usually indicates that some connectors did become available or were unplugged. The handler - /// should scan again for connected monitors and mode switch accordingly. - fn device_changed(&mut self, device: dev_t); - /// Called when a device was removed. - fn device_removed(&mut self, device: dev_t); +/// Events generated by the [`UdevBackend`], notifying you of changes in system devices +pub enum UdevEvent { + /// A new device has been detected + Added { + /// ID of the new device + device_id: dev_t, + /// Path of the new device + path: PathBuf, + }, + /// A device has changed + 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