use std::collections::HashMap;
use std::os::fd::AsFd as _;
use drm::control::Device as ControlDevice;
use crate::device::Device;
use crate::error::{CoreError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ObjectType {
Crtc,
Connector,
Encoder,
Plane,
Framebuffer,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PropertyInfo {
pub id: u32,
pub name: String,
pub value: u64,
pub immutable: bool,
pub range: Option<(u64, u64)>,
}
#[derive(Debug, Clone, Default)]
pub struct PropertyStore {
store: HashMap<u32, Vec<PropertyInfo>>,
}
impl PropertyStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn cache_properties(
&mut self,
device: &Device,
object_id: u32,
object_type: ObjectType,
) -> Result<()> {
let values = get_properties_raw(device, object_id, object_type)?;
let (handles, raw_values) = values.as_props_and_values();
let mut entries = Vec::with_capacity(handles.len());
for (handle, value) in handles.iter().zip(raw_values.iter()) {
let Ok(info) = device.get_property(*handle) else {
continue;
};
let range = match info.value_type() {
drm::control::property::ValueType::UnsignedRange(lo, hi) => Some((*lo, *hi)),
_ => None,
};
entries.push(PropertyInfo {
id: (*handle).into(),
name: info.name().to_string_lossy().into_owned(),
value: *value,
immutable: !info.mutable(),
range,
});
}
self.store.insert(object_id, entries);
Ok(())
}
#[must_use]
pub fn properties(&self, object_id: u32) -> Option<&[PropertyInfo]> {
self.store.get(&object_id).map(Vec::as_slice)
}
fn find(&self, object_id: u32, name: &str) -> Result<&PropertyInfo> {
let entries = self
.store
.get(&object_id)
.ok_or(CoreError::NoSuchObject { object_id })?;
entries
.iter()
.find(|p| p.name == name)
.ok_or_else(|| CoreError::NoSuchProperty {
object_id,
name: name.to_owned(),
})
}
pub fn property_id(&self, object_id: u32, name: &str) -> Result<u32> {
self.find(object_id, name).map(|p| p.id)
}
pub fn property_value(&self, object_id: u32, name: &str) -> Result<u64> {
self.find(object_id, name).map(|p| p.value)
}
pub fn enum_value(
&self,
device: &Device,
object_id: u32,
property: &str,
variant: &str,
) -> Result<u64> {
let id = self.property_id(object_id, property)?;
let missing = || CoreError::NoSuchProperty {
object_id,
name: property.to_owned(),
};
let handle = drm::control::RawResourceHandle::new(id).ok_or_else(missing)?;
let info =
drm::control::Device::get_property(device, handle.into()).map_err(|_| missing())?;
let drm::control::property::ValueType::Enum(values) = info.value_type() else {
return Err(missing());
};
let (raw, enums) = values.values();
enums
.iter()
.zip(raw.iter())
.find(|(entry, _)| entry.name().to_string_lossy() == variant)
.map(|(_, value)| *value)
.ok_or_else(missing)
}
pub fn bitmask_bits(&self, device: &Device, object_id: u32, property: &str) -> Result<u64> {
let id = self.property_id(object_id, property)?;
let missing = || CoreError::NoSuchProperty {
object_id,
name: property.to_owned(),
};
let mut values = Vec::new();
let mut enums = Vec::new();
let info =
drm_ffi::mode::get_property(device.as_fd(), id, Some(&mut values), Some(&mut enums))
.map_err(|_| missing())?;
if info.flags & drm_ffi::DRM_MODE_PROP_BITMASK == 0 {
return Err(missing());
}
Ok(enums
.iter()
.filter(|entry| entry.value < u64::BITS.into())
.fold(0_u64, |bits, entry| bits | (1_u64 << entry.value)))
}
pub fn is_immutable(&self, object_id: u32, name: &str) -> Result<bool> {
self.find(object_id, name).map(|p| p.immutable)
}
pub fn range(&self, object_id: u32, name: &str) -> Result<Option<(u64, u64)>> {
self.find(object_id, name).map(|p| p.range)
}
pub fn clear(&mut self) {
self.store.clear();
}
pub fn forget(&mut self, object_id: u32) {
self.store.remove(&object_id);
}
#[must_use]
pub fn len(&self) -> usize {
self.store.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.store.is_empty()
}
}
fn get_properties_raw(
device: &Device,
object_id: u32,
object_type: ObjectType,
) -> Result<drm::control::PropertyValueSet> {
use drm::control::{connector, crtc, encoder, framebuffer, plane};
let raw: drm::control::RawResourceHandle = object_id
.try_into()
.map_err(|_| CoreError::NoSuchObject { object_id })?;
let result = match object_type {
ObjectType::Crtc => device.get_properties(crtc::Handle::from(raw)),
ObjectType::Connector => device.get_properties(connector::Handle::from(raw)),
ObjectType::Encoder => device.get_properties(encoder::Handle::from(raw)),
ObjectType::Plane => device.get_properties(plane::Handle::from(raw)),
ObjectType::Framebuffer => device.get_properties(framebuffer::Handle::from(raw)),
};
result.map_err(|e| CoreError::from_io(&e))
}