use std::os::fd::{AsFd, AsRawFd as _, BorrowedFd, OwnedFd, RawFd};
use std::path::Path;
use drm::ClientCapability;
use drm::control::Device as ControlDevice;
use rustix::io::Errno;
use crate::error::{CoreError, Result};
#[derive(Debug)]
enum Fd {
Owned(OwnedFd),
Borrowed(RawFd),
}
#[derive(Debug)]
pub struct Device {
fd: Fd,
}
impl Device {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let file = rustix::fs::open(
path,
rustix::fs::OFlags::RDWR | rustix::fs::OFlags::CLOEXEC,
rustix::fs::Mode::empty(),
)
.map_err(CoreError::Io)?;
let device = Self {
fd: Fd::Owned(file),
};
if drm::Device::get_driver(&device).is_err() {
return Err(CoreError::NotADrmDevice {
path: path.display().to_string(),
});
}
if let Err(e) = drm::Device::acquire_master_lock(&device) {
drmkit_log::log_debug!(
"could not become DRM master on {}: {e} (expected under a compositor)",
path.display()
);
}
Ok(device)
}
#[must_use]
pub const fn from_owned_fd(fd: OwnedFd) -> Self {
Self { fd: Fd::Owned(fd) }
}
#[must_use]
pub const unsafe fn from_borrowed_fd(fd: RawFd) -> Self {
Self {
fd: Fd::Borrowed(fd),
}
}
#[must_use]
pub fn raw_fd(&self) -> RawFd {
match &self.fd {
Fd::Owned(fd) => fd.as_raw_fd(),
Fd::Borrowed(fd) => *fd,
}
}
#[must_use]
pub const fn owns_fd(&self) -> bool {
matches!(self.fd, Fd::Owned(_))
}
pub fn enable_universal_planes(&self) -> Result<()> {
self.set_client_cap(ClientCapability::UniversalPlanes, true)
}
pub fn enable_atomic(&self) -> Result<()> {
self.set_client_cap(ClientCapability::Atomic, true)
}
pub fn set_client_cap(&self, cap: ClientCapability, enable: bool) -> Result<()> {
drm::Device::set_client_capability(self, cap, enable).map_err(|e| CoreError::from_io(&e))
}
pub fn set_master(&self) -> Result<()> {
drm::Device::acquire_master_lock(self).map_err(|e| CoreError::from_io(&e))
}
pub fn drop_master(&self) -> Result<()> {
drm::Device::release_master_lock(self).map_err(|e| CoreError::from_io(&e))
}
pub fn property_blob(&self, blob_id: u64) -> Result<Vec<u8>> {
self.get_property_blob(blob_id)
.map_err(|e| CoreError::from_io(&e))
}
}
pub struct PropertyBlob<'a> {
id: u64,
device: &'a Device,
}
impl PropertyBlob<'_> {
#[must_use]
pub const fn id(&self) -> u64 {
self.id
}
pub fn forget(self) {
core::mem::forget(self);
}
}
impl core::fmt::Debug for PropertyBlob<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PropertyBlob")
.field("id", &self.id)
.finish()
}
}
impl Drop for PropertyBlob<'_> {
fn drop(&mut self) {
let _ = drm::control::Device::destroy_property_blob(self.device, self.id);
}
}
impl Device {
pub fn create_property_blob<T: ?Sized>(&self, data: &T) -> Result<PropertyBlob<'_>> {
let value = drm::control::Device::create_property_blob(self, data)
.map_err(|e| CoreError::Io(Errno::from_io_error(&e).unwrap_or(Errno::INVAL)))?;
let drm::control::property::Value::Blob(id) = value else {
return Err(CoreError::Io(Errno::INVAL));
};
Ok(PropertyBlob { id, device: self })
}
}
impl AsFd for Device {
fn as_fd(&self) -> BorrowedFd<'_> {
match &self.fd {
Fd::Owned(fd) => fd.as_fd(),
Fd::Borrowed(raw) => unsafe { BorrowedFd::borrow_raw(*raw) },
}
}
}
impl drm::Device for Device {}
impl drm::control::Device for Device {}
#[must_use]
pub fn framebuffer_modifier(device: &impl drm::Device, modifier: u64) -> Option<u64> {
declared_modifier(modifier, || supports_framebuffer_modifiers(device))
}
pub(crate) fn declared_modifier(
modifier: u64,
takes_modifiers: impl FnOnce() -> bool,
) -> Option<u64> {
const LINEAR: u64 = 0;
const INVALID: u64 = (1 << 56) - 1;
match modifier {
INVALID => None,
LINEAR => takes_modifiers().then_some(LINEAR),
_ => Some(modifier),
}
}
#[must_use]
pub fn supports_framebuffer_modifiers(device: &impl drm::Device) -> bool {
device
.get_driver_capability(drm::DriverCapability::AddFB2Modifiers)
.is_ok_and(|value| value != 0)
}