use std::collections::{HashMap, HashSet};
use std::os::fd::{AsFd, BorrowedFd, RawFd};
use std::os::unix::io::AsRawFd;
use drm::Device;
use drm::control::{Device as ControlDevice, Event, connector};
use gbm::Device as GbmDevice;
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use thiserror::Error;
use crate::card::Card;
use crate::monitor::MonitorSetupError;
mod card;
mod gles_context;
mod hotplug;
mod monitor;
pub use monitor::Monitor;
#[allow(clippy::all, warnings)]
pub mod gl {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
#[derive(Debug, Error)]
pub enum EasyDRMError {
#[error("IO Error: {0}")]
IOError(#[from] std::io::Error),
#[error("Monitor setup error: {0}")]
MonitorSetup(#[from] MonitorSetupError),
}
pub struct EasyDRM<T> {
card: Card,
gbm_device: GbmDevice<std::fs::File>,
monitors: HashMap<connector::Handle, Monitor<T>>,
refresh_rate_groups: HashMap<u32, Vec<connector::Handle>>, fastest_group_refresh: Option<u32>,
fastest_group_pending: HashSet<connector::Handle>,
should_update_flag: bool,
context_constructor: Box<dyn Fn(&crate::gl::Gles2, usize, usize) -> T + 'static>,
uevent_socket: Option<hotplug::UEventSocket>,
}
impl<T> EasyDRM<T> {
pub fn init<F>(context_constructor: F) -> Result<Self, EasyDRMError>
where
F: Fn(&crate::gl::Gles2, usize, usize) -> T + 'static,
{
let card = Card::open_default_card();
card.set_client_capability(drm::ClientCapability::UniversalPlanes, true)
.expect("Unable to request UniversalPlanes capability");
card.set_client_capability(drm::ClientCapability::Atomic, true)
.expect("Unable to request Atomic capability");
let fd = card.as_raw_fd();
let gbm_device = unsafe {
use std::os::unix::io::FromRawFd;
GbmDevice::new(std::fs::File::from_raw_fd(libc::dup(fd)))
.expect("Failed to create GBM device")
};
let mut easydrm = EasyDRM {
card,
gbm_device,
monitors: HashMap::new(),
refresh_rate_groups: HashMap::new(),
fastest_group_refresh: None,
fastest_group_pending: HashSet::new(),
should_update_flag: false,
context_constructor: Box::new(context_constructor),
uevent_socket: hotplug::UEventSocket::open().ok(),
};
if easydrm.uevent_socket.is_none() {
eprintln!(
"[WARNING] Failed to open uevent socket, monitors plugged in afterwards won't be able to be detected"
);
}
easydrm.discover_monitors()?;
Ok(easydrm)
}
fn discover_monitors(&mut self) -> Result<(), EasyDRMError> {
let res = self.card.resource_handles()?;
let connector_handles: Vec<connector::Handle> = res.connectors().to_vec();
for connector_id in connector_handles {
match Monitor::setup(
&self.card,
&self.gbm_device,
connector_id,
|gl, width, height| (self.context_constructor)(gl, width, height),
) {
Ok(monitor) => {
self.monitors.insert(connector_id, monitor);
}
Err(e) => {
eprintln!("Warning: Failed to setup monitor {:?}: {}", connector_id, e);
}
}
}
self.update_refresh_rate_groups();
Ok(())
}
fn update_refresh_rate_groups(&mut self) {
self.refresh_rate_groups.clear();
for (&connector_id, monitor) in self.monitors.iter() {
let refresh_rate = monitor.active_mode().vrefresh();
self.refresh_rate_groups
.entry(refresh_rate)
.or_default()
.push(connector_id);
}
self.fastest_group_refresh = self.refresh_rate_groups.keys().max().copied();
self.reset_fastest_group_pending();
}
fn reset_fastest_group_pending(&mut self) {
self.fastest_group_pending.clear();
if let Some(refresh) = self.fastest_group_refresh {
if let Some(connectors) = self.refresh_rate_groups.get(&refresh) {
self.fastest_group_pending
.extend(connectors.iter().copied());
}
}
self.should_update_flag =
self.fastest_group_pending.is_empty() && self.fastest_group_refresh.is_some();
}
fn mark_fast_group_commit(&mut self, connector_id: connector::Handle) {
if self.fastest_group_pending.remove(&connector_id) && self.fastest_group_pending.is_empty()
{
self.should_update_flag = true;
}
}
fn handle_hotplug(&mut self) -> Result<(), EasyDRMError> {
let res = self.card.resource_handles()?;
let drm_connected = res
.connectors()
.into_iter()
.copied()
.collect::<HashSet<_>>();
let current_monitors: HashSet<connector::Handle> = self.monitors.keys().copied().collect();
let disconnected: Vec<connector::Handle> = current_monitors
.difference(&drm_connected)
.copied()
.collect();
let newly_connected: Vec<connector::Handle> = drm_connected
.difference(¤t_monitors)
.copied()
.collect();
let needs_update = !disconnected.is_empty() || !newly_connected.is_empty();
for connector_id in disconnected {
self.monitors.retain(|&c, _| c != connector_id);
}
for connector_id in newly_connected {
match Monitor::setup(
&self.card,
&self.gbm_device,
connector_id,
|gl, width, height| (self.context_constructor)(gl, width, height),
) {
Ok(monitor) => {
self.monitors.insert(connector_id, monitor);
}
Err(e) => {
eprintln!("Warning: Failed to setup monitor {:?}: {}", connector_id, e);
}
}
}
if needs_update {
self.update_refresh_rate_groups();
}
Ok(())
}
pub fn poll_events(&mut self) -> Result<(), EasyDRMError> {
self.poll_events_ex([])
}
pub fn poll_events_ex(
&mut self,
extra_fds: impl IntoIterator<Item = RawFd>,
) -> Result<(), EasyDRMError> {
let drm_fd = self.card.as_fd();
let uevents_socket = self.uevent_socket.as_ref();
let mut fds = vec![PollFd::new(drm_fd, PollFlags::POLLIN)];
fds.extend(
extra_fds
.into_iter()
.map(|f| PollFd::new(unsafe { BorrowedFd::borrow_raw(f) }, PollFlags::POLLIN)),
);
if let Some(uevents_socket) = uevents_socket {
fds.push(PollFd::new(uevents_socket.fd.as_fd(), PollFlags::POLLIN));
}
poll(&mut fds, PollTimeout::NONE).ok();
let drm_ready = fds[0]
.revents()
.unwrap_or(PollFlags::empty())
.contains(PollFlags::POLLIN);
if let Some(uevents_socket) = uevents_socket {
let hotplug_ready = fds
.iter()
.find(|p| p.as_fd().as_raw_fd() == uevents_socket.fd.as_raw_fd())
.and_then(|p| p.revents())
.unwrap_or(PollFlags::empty())
.contains(PollFlags::POLLIN);
if hotplug_ready && uevents_socket.has_hotplug_event().unwrap_or(false) {
println!("[INFO] Hotplug detected, refreshing monitors.");
self.handle_hotplug()?;
}
}
if drm_ready {
self.handle_drm_events()?;
}
Ok(())
}
pub fn monitors(&self) -> impl Iterator<Item = &Monitor<T>> {
self.monitors.values()
}
pub fn monitors_mut(&mut self) -> impl Iterator<Item = &mut Monitor<T>> {
self.monitors.values_mut()
}
pub fn get_monitor_mut(&mut self, connector_id: connector::Handle) -> Option<&mut Monitor<T>> {
self.monitors.get_mut(&connector_id)
}
pub fn get_monitor(&self, connector_id: connector::Handle) -> Option<&Monitor<T>> {
self.monitors.get(&connector_id)
}
pub fn swap_buffers(&mut self) -> Result<(), EasyDRMError> {
let mut committed = Vec::new();
for (&connector_id, monitor) in self.monitors.iter_mut() {
if monitor.was_drawn() {
monitor.swap_buffers(&self.card)?;
monitor.reset_drawn_flag();
committed.push(connector_id);
}
}
for connector_id in committed {
self.mark_fast_group_commit(connector_id);
}
Ok(())
}
pub fn monitor_count(&self) -> usize {
self.monitors.len()
}
pub fn has_monitors(&self) -> bool {
!self.monitors.is_empty()
}
pub fn any_can_render(&self) -> bool {
self.monitors.values().any(|m| m.can_render())
}
pub fn refresh_rate_groups(&self) -> &HashMap<u32, Vec<connector::Handle>> {
&self.refresh_rate_groups
}
pub fn should_update(&mut self) -> bool {
if self.should_update_flag {
self.should_update_flag = false;
self.reset_fastest_group_pending();
true
} else {
false
}
}
fn handle_drm_events(&mut self) -> std::io::Result<()> {
for event in self.card.receive_events()? {
match event {
Event::PageFlip(page_flip_event) => {
let crtc_handle = page_flip_event.crtc;
for monitor in self.monitors.values_mut() {
if monitor.crtc().handle() == crtc_handle {
monitor.set_can_render(true);
}
}
}
_ => {}
}
}
Ok(())
}
}
impl EasyDRM<()> {
pub fn init_empty() -> Result<Self, EasyDRMError> {
Self::init(|_, _, _| ())
}
}