use std::ffi::{c_int, c_void};
use std::io;
use std::os::fd::{AsFd, AsRawFd};
type Req = *mut c_void;
struct Lib {
alloc: unsafe extern "C" fn() -> Req,
add: unsafe extern "C" fn(Req, u32, u32, u64) -> c_int,
commit: unsafe extern "C" fn(c_int, Req, u32, *mut c_void) -> c_int,
free: unsafe extern "C" fn(Req),
}
impl Lib {
fn load() -> io::Result<Lib> {
let handle = unsafe { libc::dlopen(c"libdrm.so.2".as_ptr(), libc::RTLD_NOW | libc::RTLD_GLOBAL) };
if handle.is_null() {
return Err(io::Error::other("libdrm.so.2 not found"));
}
let sym = |name: &std::ffi::CStr| {
let p = unsafe { libc::dlsym(handle, name.as_ptr()) };
if p.is_null() { Err(io::Error::other(format!("libdrm.so.2 lacks {name:?}"))) } else { Ok(p) }
};
unsafe {
Ok(Lib {
alloc: std::mem::transmute::<*mut c_void, unsafe extern "C" fn() -> Req>(sym(c"drmModeAtomicAlloc")?),
add: std::mem::transmute::<*mut c_void, unsafe extern "C" fn(Req, u32, u32, u64) -> c_int>(
sym(c"drmModeAtomicAddProperty")?,
),
commit: std::mem::transmute::<*mut c_void, unsafe extern "C" fn(c_int, Req, u32, *mut c_void) -> c_int>(
sym(c"drmModeAtomicCommit")?,
),
free: std::mem::transmute::<*mut c_void, unsafe extern "C" fn(Req)>(sym(c"drmModeAtomicFree")?),
})
}
}
}
pub struct RequestSlot {
lib: Lib,
slot: Box<Req>,
}
impl RequestSlot {
pub fn new() -> io::Result<RequestSlot> {
let lib = Lib::load()?;
let req = unsafe { (lib.alloc)() };
if req.is_null() {
return Err(io::Error::other("drmModeAtomicAlloc failed"));
}
Ok(RequestSlot { lib, slot: Box::new(req) })
}
pub fn mpv_ptr(&mut self) -> *mut *mut c_void {
&mut *self.slot
}
pub fn add(&mut self, object: u32, property: u32, value: u64) -> io::Result<()> {
if unsafe { (self.lib.add)(*self.slot, object, property, value) } < 0 {
return Err(io::Error::other("drmModeAtomicAddProperty failed"));
}
Ok(())
}
pub fn commit(&mut self, card: &impl AsFd) -> io::Result<()> {
let fresh = unsafe { (self.lib.alloc)() };
if fresh.is_null() {
return Err(io::Error::other("drmModeAtomicAlloc failed"));
}
let req = std::mem::replace(&mut *self.slot, fresh);
let ret = unsafe { (self.lib.commit)(card.as_fd().as_raw_fd(), req, 0, std::ptr::null_mut()) };
let error = io::Error::last_os_error();
unsafe { (self.lib.free)(req) };
if ret != 0 { Err(error) } else { Ok(()) }
}
}
impl Drop for RequestSlot {
fn drop(&mut self) {
unsafe { (self.lib.free)(*self.slot) };
}
}
#[cfg(test)]
mod tests {
#[test]
fn slot_replaces_request_on_commit() {
let Ok(mut slot) = super::RequestSlot::new() else { return }; let first = unsafe { *slot.mpv_ptr() };
slot.add(1, 2, 3).unwrap();
let null = std::fs::File::open("/dev/null").unwrap();
assert!(slot.commit(&null).is_err());
let second = unsafe { *slot.mpv_ptr() };
assert!(!second.is_null());
slot.add(1, 2, 4).unwrap();
let _ = first;
}
}