use std::ffi::{CStr, CString};
use std::os::raw::c_int;
use std::ptr;
use libdrmtap_sys as ffi;
#[derive(Debug)]
pub struct Error {
pub code: i32,
pub message: String,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "drmtap error {}: {}", self.code, self.message)
}
}
impl std::error::Error for Error {}
type Result<T> = std::result::Result<T, Error>;
fn check(ctx: *const ffi::drmtap_ctx, ret: c_int) -> Result<()> {
if ret >= 0 {
Ok(())
} else {
let msg = unsafe {
let p = ffi::drmtap_error(ctx);
if p.is_null() {
"unknown error".to_string()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
Err(Error {
code: ret as i32,
message: msg,
})
}
}
#[derive(Debug, Clone)]
pub struct Display {
pub crtc_id: u32,
pub connector_id: u32,
pub name: String,
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub refresh_hz: u32,
pub active: bool,
}
pub struct Config {
pub device_path: Option<String>,
pub crtc_id: u32,
pub helper_path: Option<String>,
pub debug: bool,
}
impl Default for Config {
fn default() -> Self {
Config {
device_path: None,
crtc_id: 0,
helper_path: None,
debug: false,
}
}
}
pub struct DrmTap {
ctx: *mut ffi::drmtap_ctx,
}
unsafe impl Send for DrmTap {}
impl DrmTap {
pub fn open(config: Option<Config>) -> Result<Self> {
let ctx = match config {
None => unsafe { ffi::drmtap_open(ptr::null()) },
Some(cfg) => {
let device = cfg.device_path.as_ref().map(|s| CString::new(s.as_str()).unwrap());
let helper = cfg.helper_path.as_ref().map(|s| CString::new(s.as_str()).unwrap());
let raw_cfg = ffi::drmtap_config {
device_path: device.as_ref().map_or(ptr::null(), |c| c.as_ptr()),
crtc_id: cfg.crtc_id,
helper_path: helper.as_ref().map_or(ptr::null(), |c| c.as_ptr()),
debug: if cfg.debug { 1 } else { 0 },
};
unsafe { ffi::drmtap_open(&raw_cfg) }
}
};
if ctx.is_null() {
let msg = unsafe {
let p = ffi::drmtap_error(ptr::null());
if p.is_null() {
"failed to open DRM device".to_string()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
Err(Error {
code: -1,
message: msg,
})
} else {
Ok(DrmTap { ctx })
}
}
pub fn list_displays(&mut self) -> Result<Vec<Display>> {
let mut raw = vec![unsafe { std::mem::zeroed::<ffi::drmtap_display>() }; 16];
let n = unsafe { ffi::drmtap_list_displays(self.ctx, raw.as_mut_ptr(), 16) };
check(self.ctx, n)?;
let count = n as usize;
Ok(raw[..count]
.iter()
.map(|d| {
let name = unsafe {
CStr::from_ptr(d.name.as_ptr())
.to_string_lossy()
.into_owned()
};
Display {
crtc_id: d.crtc_id,
connector_id: d.connector_id,
name,
x: d.x,
y: d.y,
width: d.width,
height: d.height,
refresh_hz: d.refresh_hz,
active: d.active != 0,
}
})
.collect())
}
pub fn displays_changed(&mut self) -> bool {
unsafe { ffi::drmtap_displays_changed(self.ctx) != 0 }
}
pub fn grab(&mut self) -> Result<Frame> {
let mut raw = unsafe { std::mem::zeroed::<ffi::drmtap_frame_info>() };
let ret = unsafe { ffi::drmtap_grab(self.ctx, &mut raw) };
check(self.ctx, ret)?;
Ok(Frame {
ctx: self.ctx,
raw,
})
}
pub fn grab_mapped(&mut self) -> Result<Frame> {
let mut raw = unsafe { std::mem::zeroed::<ffi::drmtap_frame_info>() };
let ret = unsafe { ffi::drmtap_grab_mapped(self.ctx, &mut raw) };
check(self.ctx, ret)?;
Ok(Frame {
ctx: self.ctx,
raw,
})
}
pub fn gpu_driver(&mut self) -> Option<String> {
let p = unsafe { ffi::drmtap_gpu_driver(self.ctx) };
if p.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() })
}
}
pub fn error(&self) -> Option<String> {
let p = unsafe { ffi::drmtap_error(self.ctx) };
if p.is_null() {
None
} else {
let s = unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() };
if s.is_empty() {
None
} else {
Some(s)
}
}
}
pub fn get_cursor(&mut self) -> Result<Cursor> {
let mut raw = unsafe { std::mem::zeroed::<ffi::drmtap_cursor_info>() };
let ret = unsafe { ffi::drmtap_get_cursor(self.ctx, &mut raw) };
check(self.ctx, ret)?;
Ok(Cursor {
ctx: self.ctx,
raw,
})
}
}
impl Drop for DrmTap {
fn drop(&mut self) {
unsafe { ffi::drmtap_close(self.ctx) };
}
}
pub struct Frame {
ctx: *mut ffi::drmtap_ctx,
raw: ffi::drmtap_frame_info,
}
impl Frame {
pub fn width(&self) -> u32 {
self.raw.width
}
pub fn height(&self) -> u32 {
self.raw.height
}
pub fn stride(&self) -> u32 {
self.raw.stride
}
pub fn format(&self) -> u32 {
self.raw.format
}
pub fn modifier(&self) -> u64 {
self.raw.modifier
}
pub fn dma_buf_fd(&self) -> i32 {
self.raw.dma_buf_fd as i32
}
pub fn data(&self) -> Option<&[u8]> {
if self.raw.data.is_null() {
None
} else {
let len = self.raw.stride as usize * self.raw.height as usize;
Some(unsafe { std::slice::from_raw_parts(self.raw.data as *const u8, len) })
}
}
}
impl Drop for Frame {
fn drop(&mut self) {
unsafe { ffi::drmtap_frame_release(self.ctx, &mut self.raw) };
}
}
pub struct Cursor {
ctx: *mut ffi::drmtap_ctx,
raw: ffi::drmtap_cursor_info,
}
impl Cursor {
pub fn x(&self) -> i32 {
self.raw.x
}
pub fn y(&self) -> i32 {
self.raw.y
}
pub fn hot_x(&self) -> i32 {
self.raw.hot_x
}
pub fn hot_y(&self) -> i32 {
self.raw.hot_y
}
pub fn width(&self) -> u32 {
self.raw.width
}
pub fn height(&self) -> u32 {
self.raw.height
}
pub fn visible(&self) -> bool {
self.raw.visible != 0
}
pub fn pixels(&self) -> Option<&[u32]> {
if self.raw.pixels.is_null() {
None
} else {
let len = self.raw.width as usize * self.raw.height as usize;
Some(unsafe { std::slice::from_raw_parts(self.raw.pixels, len) })
}
}
}
impl Drop for Cursor {
fn drop(&mut self) {
unsafe { ffi::drmtap_cursor_release(self.ctx, &mut self.raw) };
}
}
pub fn version() -> (u8, u8, u8) {
let v = unsafe { ffi::drmtap_version() } as u32;
((v >> 16) as u8, ((v >> 8) & 0xFF) as u8, (v & 0xFF) as u8)
}