use std::collections::{HashMap, HashSet};
use std::os::fd::{AsFd, BorrowedFd, RawFd};
use std::os::unix::io::AsRawFd;
use drm::Device;
use drm::control::atomic::AtomicModeReq;
use drm::control::{
AtomicCommitFlags, Device as ControlDevice, Event, PlaneType, connector, crtc, plane,
};
use gbm::Device as GbmDevice;
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use thiserror::Error;
use crate::card::Card;
use crate::monitor::{MonitorResourceAllocation, 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();
let (mut used_crtcs, mut used_primary_planes, mut used_cursor_planes) =
self.current_resource_usage();
for connector_id in connector_handles {
if self.monitors.contains_key(&connector_id) {
continue;
}
let allocation = match self.allocate_monitor_resources(
connector_id,
&used_crtcs,
&used_primary_planes,
&used_cursor_planes,
) {
Ok(allocation) => allocation,
Err(e) => {
eprintln!(
"Warning: Failed to allocate resources for monitor {:?}: {}",
connector_id, e
);
continue;
}
};
match Monitor::setup(
&self.card,
&self.gbm_device,
connector_id,
allocation,
|gl, width, height| (self.context_constructor)(gl, width, height),
) {
Ok(monitor) => {
used_crtcs.insert(monitor.crtc().handle());
used_primary_planes.insert(monitor.primary_plane());
if let Some(cursor) = monitor.cursor_plane() {
used_cursor_planes.insert(cursor);
}
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);
}
let (mut used_crtcs, mut used_primary_planes, mut used_cursor_planes) =
self.current_resource_usage();
for connector_id in newly_connected {
let allocation = match self.allocate_monitor_resources(
connector_id,
&used_crtcs,
&used_primary_planes,
&used_cursor_planes,
) {
Ok(allocation) => allocation,
Err(e) => {
eprintln!(
"Warning: Failed to allocate resources for monitor {:?}: {}",
connector_id, e
);
continue;
}
};
match Monitor::setup(
&self.card,
&self.gbm_device,
connector_id,
allocation,
|gl, width, height| (self.context_constructor)(gl, width, height),
) {
Ok(monitor) => {
used_crtcs.insert(monitor.crtc().handle());
used_primary_planes.insert(monitor.primary_plane());
if let Some(cursor) = monitor.cursor_plane() {
used_cursor_planes.insert(cursor);
}
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(())
}
fn current_resource_usage(
&self,
) -> (
HashSet<crtc::Handle>,
HashSet<plane::Handle>,
HashSet<plane::Handle>,
) {
let mut used_crtcs = HashSet::new();
let mut used_primary_planes = HashSet::new();
let mut used_cursor_planes = HashSet::new();
for monitor in self.monitors.values() {
used_crtcs.insert(monitor.crtc().handle());
used_primary_planes.insert(monitor.primary_plane());
if let Some(cursor) = monitor.cursor_plane() {
used_cursor_planes.insert(cursor);
}
}
(used_crtcs, used_primary_planes, used_cursor_planes)
}
fn allocate_monitor_resources(
&self,
connector_id: connector::Handle,
used_crtcs: &HashSet<crtc::Handle>,
used_primary_planes: &HashSet<plane::Handle>,
used_cursor_planes: &HashSet<plane::Handle>,
) -> Result<MonitorResourceAllocation, MonitorSetupError> {
let connector = self.card.get_connector(connector_id, true)?;
if connector.state() != connector::State::Connected {
return Err(MonitorSetupError::NotConnected);
}
let res = self.card.resource_handles()?;
let crtc_candidates = self.crtc_candidates_for_connector(&connector, &res, used_crtcs)?;
let planes = self.card.plane_handles()?;
let plane_handles: Vec<plane::Handle> = planes.iter().copied().collect();
let mut crtc_info = None;
for handle in crtc_candidates {
if let Ok(info) = self.card.get_crtc(handle) {
crtc_info = Some(info);
break;
}
}
let crtc_info = crtc_info.ok_or(MonitorSetupError::NoCRTCFound)?;
let primary_plane = self
.find_plane_for_crtc(
&plane_handles,
&res,
crtc_info.handle(),
PlaneType::Primary,
used_primary_planes,
)?
.ok_or(MonitorSetupError::NoPrimaryPlaneFound)?;
let cursor_plane = self.find_plane_for_crtc(
&plane_handles,
&res,
crtc_info.handle(),
PlaneType::Cursor,
used_cursor_planes,
)?;
Ok(MonitorResourceAllocation {
crtc_info,
primary_plane,
cursor_plane,
})
}
fn crtc_candidates_for_connector(
&self,
connector: &connector::Info,
res: &drm::control::ResourceHandles,
used_crtcs: &HashSet<crtc::Handle>,
) -> Result<Vec<crtc::Handle>, MonitorSetupError> {
let mut seen = HashSet::new();
let mut candidates = Vec::new();
for encoder_handle in connector.encoders() {
let Ok(encoder) = self.card.get_encoder(*encoder_handle) else {
continue;
};
for crtc_handle in res.filter_crtcs(encoder.possible_crtcs()) {
if used_crtcs.contains(&crtc_handle) {
continue;
}
if seen.insert(crtc_handle) {
candidates.push(crtc_handle);
}
}
}
Ok(candidates)
}
fn find_plane_for_crtc(
&self,
plane_handles: &[plane::Handle],
res: &drm::control::ResourceHandles,
crtc_handle: crtc::Handle,
plane_type: PlaneType,
used_planes: &HashSet<plane::Handle>,
) -> Result<Option<plane::Handle>, MonitorSetupError> {
for plane_handle in plane_handles {
if used_planes.contains(plane_handle) {
continue;
}
let Ok(plane_info) = self.card.get_plane(*plane_handle) else {
continue;
};
let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs());
if !compatible_crtcs.contains(&crtc_handle) {
continue;
}
if self.plane_is_type(*plane_handle, plane_type)? {
return Ok(Some(*plane_handle));
}
}
Ok(None)
}
fn plane_is_type(
&self,
plane_handle: plane::Handle,
plane_type: PlaneType,
) -> Result<bool, MonitorSetupError> {
let properties = self.card.get_properties(plane_handle)?;
for (&id, &value) in properties.iter() {
let Ok(info) = self.card.get_property(id) else {
continue;
};
if info
.name()
.to_str()
.map(|name| name == "type")
.unwrap_or(false)
{
return Ok(value == (plane_type as u32).into());
}
}
Ok(false)
}
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.drain_hotplug_events().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 atomic_req = AtomicModeReq::new();
let flags = AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::ALLOW_MODESET;
let mut committed = Vec::new();
for (&connector_id, monitor) in self.monitors.iter_mut() {
if monitor.was_drawn() {
monitor.swap_buffers(&self.card, &mut atomic_req)?;
monitor.reset_drawn_flag();
committed.push(connector_id);
}
}
for connector_id in committed {
self.mark_fast_group_commit(connector_id);
}
self.card
.atomic_commit(flags, atomic_req)
.map_err(|e| MonitorSetupError::DrmError(format!("Failed to commit: {}", e)))?;
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(|_, _, _| ())
}
}