#![warn(clippy::cargo)]
use std::ffi::CStr;
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::os::raw::{c_char, c_void};
pub use drm_fourcc::{DrmFormat, UnrecognizedFourcc};
pub use evdi_sys as ffi;
use lazy_static::lazy_static;
use regex::Regex;
use std::fmt::{Debug, Formatter};
use std::ptr::null_mut;
use std::slice;
use std::sync::Once;
use tracing::{info, instrument};
pub mod buffer;
pub mod device_config;
pub mod device_node;
pub mod events;
pub mod handle;
pub mod prelude;
#[cfg(test)]
mod test_common;
#[tracing::instrument]
pub fn check_kernel_mod() -> KernelModStatus {
let mod_version = KernelModVersion::get();
if let Some(mod_version) = mod_version {
let lib_version = LibVersion::get();
if lib_version.is_compatible_with(mod_version) {
KernelModStatus::Compatible
} else {
KernelModStatus::Outdated
}
} else {
KernelModStatus::NotInstalled
}
}
#[cfg_attr(
feature = "serde",
derive(serde_crate::Serialize, serde_crate::Deserialize),
serde(crate = "serde_crate")
)]
pub enum KernelModStatus {
NotInstalled,
Outdated,
Compatible,
}
static LOGS_SETUP: Once = Once::new();
pub(crate) fn ensure_logs_setup() {
LOGS_SETUP.call_once(|| unsafe {
ffi::wrapper_evdi_set_logging(ffi::wrapper_log_cb {
function: Some(logs_cb),
user_data: null_mut(),
});
});
}
extern "C" fn logs_cb(_user_data: *mut c_void, msg: *const c_char) {
let msg = unsafe { CStr::from_ptr(msg) }.to_str().unwrap().to_owned();
info!(libevdi_unknown_level = true, "libevdi: {}", msg);
}
const MOD_VERSION_FILE: &str = "/sys/devices/evdi/version";
#[cfg_attr(
feature = "serde",
derive(serde_crate::Serialize, serde_crate::Deserialize),
serde(crate = "serde_crate")
)]
pub struct KernelModVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl KernelModVersion {
#[instrument]
pub fn get() -> Option<Self> {
lazy_static! {
static ref RE: Regex =
Regex::new(r"(?P<maj>[0-9]+)\.(?P<min>[0-9]+)\.(?P<pat>[0-9]+)").unwrap();
}
let mut file = File::open(MOD_VERSION_FILE).ok()?;
let mut contents = String::new();
file.read_to_string(&mut contents).ok()?;
let caps = RE.captures(&contents)?;
let major = caps.name("maj")?.as_str().parse().ok()?;
let minor = caps.name("min")?.as_str().parse().ok()?;
let patch = caps.name("pat")?.as_str().parse().ok()?;
Some(Self {
major,
minor,
patch,
})
}
}
#[cfg_attr(
feature = "serde",
derive(serde_crate::Serialize, serde_crate::Deserialize),
serde(crate = "serde_crate")
)]
pub struct LibVersion {
pub major: i32,
pub minor: i32,
pub patch: i32,
compatible_mod_major: u32,
compatible_mod_minor: u32,
}
impl LibVersion {
#[instrument]
pub fn get() -> Self {
let sys = unsafe {
ensure_logs_setup();
let mut out = ffi::evdi_lib_version {
version_major: -1,
version_minor: -1,
version_patchlevel: -1,
};
ffi::evdi_get_lib_version(&mut out);
out
};
let version = Self::new(
&sys,
ffi::EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR,
ffi::EVDI_MODULE_COMPATIBILITY_VERSION_MINOR,
);
assert_ne!(version.major, -1);
assert_ne!(version.minor, -1);
assert_ne!(version.patch, -1);
version
}
pub fn is_compatible_with(&self, other: KernelModVersion) -> bool {
other.major == self.compatible_mod_major && other.minor >= self.compatible_mod_minor
}
fn new(
sys: &ffi::evdi_lib_version,
compatible_mod_major: u32,
compatible_mod_minor: u32,
) -> Self {
Self {
major: sys.version_major,
minor: sys.version_minor,
patch: sys.version_patchlevel,
compatible_mod_major,
compatible_mod_minor,
}
}
}
pub(crate) struct PreallocatedArray<T> {
data: Box<[T]>,
len: usize,
max_len: usize,
}
impl<T> AsRef<[T]> for PreallocatedArray<T> {
fn as_ref(&self) -> &[T] {
if self.len > self.max_len {
panic!(
"SizedArray has length {}, but was allocated with length {}",
self.len, self.max_len
);
}
&self.data[0..self.len]
}
}
impl<T> PreallocatedArray<T> {
pub(crate) fn new(data: Box<[T]>, len: usize) -> Self {
let max_len = data.len();
Self { data, len, max_len }
}
pub(crate) fn data_ptr_mut(&mut self) -> *mut T {
self.data.as_mut_ptr()
}
pub(crate) fn len_ptr_mut(&mut self) -> *mut usize {
&mut self.len as _
}
}
impl<T: Debug> Debug for PreallocatedArray<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.as_ref()).finish()
}
}
pub(crate) struct OwnedLibcArray<T> {
ptr: *const T,
len: usize,
}
impl<T> OwnedLibcArray<T> {
pub(crate) unsafe fn new(ptr: *const T, len: usize) -> Self {
Self { ptr, len }
}
pub(crate) fn as_slice(&self) -> &[T] {
unsafe {
slice::from_raw_parts(self.ptr, self.len)
}
}
}
unsafe impl<T> Send for OwnedLibcArray<T> {}
unsafe impl<T> Sync for OwnedLibcArray<T> {}
impl<T> Drop for OwnedLibcArray<T> {
fn drop(&mut self) {
unsafe {
libc::free(self.ptr as *mut c_void);
}
}
}
impl<T: Debug> Debug for OwnedLibcArray<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.as_slice().iter()).finish()
}
}
#[cfg(test)]
mod tests {
use crate::test_common::*;
use crate::*;
#[ltest]
fn get_lib_version_works() {
LibVersion::get();
}
#[ltest]
fn get_mod_version_works() {
let result = KernelModVersion::get();
assert!(result.is_some())
}
#[ltest]
fn is_compatible_with_works() {
let mod_version = KernelModVersion::get().unwrap();
let lib_version = LibVersion::get();
assert!(lib_version.is_compatible_with(mod_version));
}
}