use nix::sys::stat::{dev_t, stat};
use std::{
collections::HashSet,
ffi::OsString,
os::unix::io::{AsRawFd, RawFd},
path::{Path, PathBuf},
};
use udev::{Context, Enumerator, EventType, MonitorBuilder, MonitorSocket, Result as UdevResult};
use wayland_server::calloop::{
generic::{EventedFd, Generic},
mio::Ready,
InsertError, LoopHandle, Source,
};
pub struct UdevBackend<T: UdevHandler + 'static> {
devices: HashSet<dev_t>,
monitor: MonitorSocket,
handler: T,
logger: ::slog::Logger,
}
impl<T: UdevHandler + 'static> AsRawFd for UdevBackend<T> {
fn as_raw_fd(&self) -> RawFd {
self.monitor.as_raw_fd()
}
}
impl<T: UdevHandler + 'static> UdevBackend<T> {
pub fn new<L, S: AsRef<str>>(
context: &Context,
mut handler: T,
seat: S,
logger: L,
) -> UdevResult<UdevBackend<T>>
where
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));
let devices = all_gpus(context, seat)?
.into_iter()
.flat_map(|path| match stat(&path) {
Ok(stat) => {
handler.device_added(stat.st_rdev, path);
Some(stat.st_rdev)
}
Err(err) => {
warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err);
None
}
})
.collect();
let mut builder = MonitorBuilder::new(context)?;
builder.match_subsystem("drm")?;
let monitor = builder.listen()?;
Ok(UdevBackend {
devices,
monitor,
handler,
logger: log,
})
}
}
impl<T: UdevHandler + 'static> Drop for UdevBackend<T> {
fn drop(&mut self) {
for device in &self.devices {
self.handler.device_removed(*device);
}
}
}
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>(
udev: UdevBackend<T>,
handle: &LoopHandle<Data>,
) -> Result<Source<Generic<EventedFd<UdevBackend<T>>>>, InsertError<Generic<EventedFd<UdevBackend<T>>>>> {
let mut source = Generic::from_fd_source(udev);
source.set_interest(Ready::readable());
handle.insert_source(source, |evt, _| {
evt.source.borrow_mut().0.process_events();
})
}
impl<T: UdevHandler + 'static> UdevBackend<T> {
fn process_events(&mut self) {
let monitor = self.monitor.clone();
for event in monitor {
match event.event_type() {
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());
}
}
}
EventType::Remove => {
info!(self.logger, "Device Remove");
if let Some(devnum) = event.devnum() {
if self.devices.remove(&devnum) {
self.handler.device_removed(devnum);
}
}
}
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");
}
}
_ => {}
}
}
}
}
pub trait UdevHandler {
fn device_added(&mut self, device: dev_t, path: PathBuf);
fn device_changed(&mut self, device: dev_t);
fn device_removed(&mut self, device: dev_t);
}
pub fn primary_gpu<S: AsRef<str>>(context: &Context, seat: S) -> UdevResult<Option<PathBuf>> {
let mut enumerator = Enumerator::new(context)?;
enumerator.match_subsystem("drm")?;
enumerator.match_sysname("card[0-9]*")?;
let mut result = None;
for device in enumerator.scan_devices()? {
if device
.property_value("ID_SEAT")
.map(|x| x.to_os_string())
.unwrap_or(OsString::from("seat0"))
== *seat.as_ref()
{
if let Some(pci) = device.parent_with_subsystem(Path::new("pci"))? {
if let Some(id) = pci.attribute_value("boot_vga") {
if id == "1" {
result = Some(device);
}
}
} else if result.is_none() {
result = Some(device);
}
}
}
Ok(result.and_then(|device| device.devnode().map(PathBuf::from)))
}
pub fn all_gpus<S: AsRef<str>>(context: &Context, seat: S) -> UdevResult<Vec<PathBuf>> {
let mut enumerator = Enumerator::new(context)?;
enumerator.match_subsystem("drm")?;
enumerator.match_sysname("card[0-9]*")?;
Ok(enumerator
.scan_devices()?
.filter(|device| {
device
.property_value("ID_SEAT")
.map(|x| x.to_os_string())
.unwrap_or(OsString::from("seat0"))
== *seat.as_ref()
})
.flat_map(|device| device.devnode().map(PathBuf::from))
.collect())
}