backend.udev: rework as an event source
This commit is contained in:
parent
104774eeb0
commit
31f1f4f9e0
|
@ -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
|
||||||
|
|
|
@ -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 {
|
/// You should call this once before inserting the event source into your
|
||||||
self.handler.device_removed(*device);
|
/// event loop, to get an initial snapshot of the device state.
|
||||||
}
|
pub fn device_list(&self) -> impl Iterator<Item = (dev_t, &Path)> {
|
||||||
|
self.devices.iter().map(|(&id, path)| (id, path.as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// calloop event source associated with the Udev backend
|
impl EventSource for UdevBackend {
|
||||||
pub type UdevSource<T> = Generic<UdevBackend<T>>;
|
type Event = UdevEvent;
|
||||||
|
type Metadata = ();
|
||||||
|
type Ret = ();
|
||||||
|
|
||||||
/// Binds a [`UdevBackend`] to a given [`EventLoop`](calloop::EventLoop).
|
fn process_events<F>(&mut self, _: Readiness, _: Token, mut callback: F) -> std::io::Result<()>
|
||||||
///
|
where
|
||||||
/// Allows the backend to receive kernel events and thus to drive the [`UdevHandler`].
|
F: FnMut(UdevEvent, &mut ()),
|
||||||
/// No runtime functionality can be provided without using this function.
|
{
|
||||||
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>(
|
|
||||||
udev: UdevBackend<T>,
|
|
||||||
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> {
|
|
||||||
fn process_events(&mut self) {
|
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue