use async_trait::async_trait;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use drm::control::{AtomicCommitFlags, atomic::AtomicModeReq};
use crate::error::{DisplayError, DisplayResult};
use crate::traits::{OutputEditable, UniversalTopology};
use crate::types::{OutputState, DisplayRotation, HdrState, HdrMode, DisplayId, Extent2D, Point2D, Rect};
pub mod displmgr_drm_sys;
pub mod displmgr_drm_api;
use self::displmgr_drm_sys::{DrmResourceIds, DrmPropertyCache, rotation_to_drm_value};
use self::displmgr_drm_api::probe_drm_hardware;
pub struct DrmTopology {
pub device: File,
pub outputs: Vec<OutputState>,
pub atomic_req: AtomicModeReq,
pub resource_map: HashMap<DisplayId, DrmResourceIds>,
pub property_cache: DrmPropertyCache,
pub dirty: bool,
}
impl DrmTopology {
pub fn find_prop_id(&self, conn: drm::control::connector::Handle, name: &str) -> Option<drm::control::property::Handle> {
self.property_cache.props.get(&conn)?.get(name).cloned()
}
pub fn snapshot_wake_inactive_outputs(&mut self, keep_active: Option<&DisplayId>) -> Vec<DisplayId> {
let mut activated = Vec::new();
for out in &mut self.outputs {
if !out.enabled {
if let Some(k) = keep_active {
if &out.identity.id == k { continue; }
}
out.enabled = true;
activated.push(out.identity.id.clone());
}
}
activated
}
pub fn restore_inactive_outputs(&mut self, inactive_ids: &[DisplayId]) {
for id in inactive_ids {
if let Some(out) = self.outputs.iter_mut().find(|o| &o.identity.id == id) {
out.enabled = false;
}
}
}
}
pub struct DrmOutputEditor<'a> {
pub topology: &'a mut DrmTopology,
pub target_id: DisplayId,
}
macro_rules! find_drm_output {
($self:expr) => {
{
let target_id = $self.target_id.clone();
$self.topology.outputs.iter_mut().find(|o| o.identity.id == target_id)
.ok_or_else(|| DisplayError::NotFound(target_id))
}
};
}
impl<'a> OutputEditable for DrmOutputEditor<'a> {
fn set_rotation(&mut self, rotation: DisplayRotation) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if out.rotation != rotation {
out.rotation = rotation;
let res = self.topology.resource_map.get(&self.target_id)
.ok_or_else(|| DisplayError::NotFound(self.target_id.clone()))?;
if let Some(prop_rot) = self.topology.find_prop_id(res.connector_id, "rotation") {
let val = rotation_to_drm_value(rotation);
self.topology.atomic_req.add_property(res.primary_plane_id, prop_rot, val);
self.topology.dirty = true;
}
}
Ok(self)
}
fn set_resolution(&mut self, extent: Extent2D) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if out.geometry.size != extent {
out.geometry.size = extent;
self.topology.dirty = true;
}
Ok(self)
}
fn set_position(&mut self, position: Point2D) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if out.geometry.origin != position {
out.geometry.origin = position;
let res = self.topology.resource_map.get(&self.target_id)
.ok_or_else(|| DisplayError::NotFound(self.target_id.clone()))?;
if let Some(prop_x) = self.topology.find_prop_id(res.connector_id, "CRTC_X") {
self.topology.atomic_req.add_property(res.primary_plane_id, prop_x, position.x as u64);
}
if let Some(prop_y) = self.topology.find_prop_id(res.connector_id, "CRTC_Y") {
self.topology.atomic_req.add_property(res.primary_plane_id, prop_y, position.y as u64);
}
self.topology.dirty = true;
}
Ok(self)
}
fn set_refresh_rate(&mut self, rate: u32) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if out.refresh_rate != rate {
out.refresh_rate = rate;
self.topology.dirty = true;
}
Ok(self)
}
fn set_primary(&mut self) -> DisplayResult<&mut dyn OutputEditable> {
for out in &mut self.topology.outputs {
out.is_primary = false;
}
let out = find_drm_output!(self)?;
out.is_primary = true;
self.topology.dirty = true;
Ok(self)
}
fn set_hdr(&mut self, state: HdrState, mode: HdrMode) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if out.hdr_state != state || out.hdr_mode != mode {
out.hdr_state = state;
out.hdr_mode = mode;
let res = self.topology.resource_map.get(&self.target_id)
.ok_or_else(|| DisplayError::NotFound(self.target_id.clone()))?;
if let Some(prop_hdr) = self.topology.find_prop_id(res.connector_id, "HDR_OUTPUT_METADATA") {
let val = if state == HdrState::Enabled { 1 } else { 0 };
self.topology.atomic_req.add_property(res.connector_id, prop_hdr, val);
self.topology.dirty = true;
}
}
Ok(self)
}
fn set_scale(&mut self, scale: f64) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if (out.scale - scale).abs() > f64::EPSILON {
out.scale = scale;
self.topology.dirty = true;
}
Ok(self)
}
fn set_enabled(&mut self, enabled: bool) -> DisplayResult<&mut dyn OutputEditable> {
let out = find_drm_output!(self)?;
if out.enabled != enabled {
out.enabled = enabled;
self.topology.dirty = true;
}
Ok(self)
}
fn clone_from(&mut self, source_id: &DisplayId) -> DisplayResult<&mut dyn OutputEditable> {
let source_state = self.topology.outputs.iter()
.find(|o| o.identity.id == *source_id)
.cloned()
.ok_or_else(|| DisplayError::NotFound(source_id.clone()))?;
let dest = find_drm_output!(self)?;
dest.geometry = source_state.geometry;
dest.refresh_rate = source_state.refresh_rate;
dest.rotation = source_state.rotation;
dest.scale = source_state.scale;
self.topology.dirty = true;
Ok(self)
}
fn get_state(&self) -> OutputState {
self.topology.outputs.iter()
.find(|o| o.identity.id == self.target_id)
.cloned()
.unwrap_or_default()
}
}
#[async_trait]
impl UniversalTopology for DrmTopology {
fn acquire() -> DisplayResult<Self> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/dri/card0")
.map_err(|e| DisplayError::Io(e))?;
let (outputs, resource_map, property_cache) = probe_drm_hardware(&file)?;
Ok(Self {
device: file,
outputs,
atomic_req: AtomicModeReq::new(),
resource_map,
property_cache,
dirty: false,
})
}
fn get_outputs(&self) -> Vec<OutputState> {
self.outputs.clone()
}
fn edit_output(&mut self, id: &DisplayId) -> DisplayResult<Box<dyn OutputEditable + '_>> {
if !self.outputs.iter().any(|o| o.identity.id == *id) {
return Err(DisplayError::NotFound(id.clone()));
}
Ok(Box::new(DrmOutputEditor {
topology: self,
target_id: id.clone(),
}))
}
fn set_persistence(&mut self, _enabled: bool) -> &mut Self {
self
}
async fn validate(&self) -> DisplayResult<()> {
let flags = AtomicCommitFlags::TEST_ONLY;
self.device.atomic_commit(flags, self.atomic_req.clone())
.map_err(|_| DisplayError::ConfigurationRejected)?;
Ok(())
}
async fn commit(&mut self) -> DisplayResult<()> {
if !self.dirty {
return Ok(());
}
let device = self.device.try_clone().map_err(|e| DisplayError::Io(e))?;
let req = self.atomic_req.clone();
tokio::task::spawn_blocking(move || -> DisplayResult<()> {
let flags = AtomicCommitFlags::ALLOW_MODESET;
device.atomic_commit(flags, req)
.map_err(|e| DisplayError::BackendError(format!("KMS Atomic Flush Failure: {}", e)))?;
Ok(())
})
.await
.map_err(|e| DisplayError::BackendError(format!("DRM thread pool panic: {}", e)))??;
self.atomic_req = AtomicModeReq::new();
self.dirty = false;
Ok(())
}
}