use crate::traits::ToJson;
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::{
fmt::Display,
fs::{read, read_dir, read_to_string},
path::Path,
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Video {
pub devices: Vec<DRM>,
}
impl Video {
pub fn new() -> Result<Self> {
let prefix = Path::new("/sys/class/drm/");
let mut devices = vec![];
for i in 0..=u8::MAX {
let path = prefix.join(format!("card{i}"));
if !path.is_dir() {
continue;
}
let dir_contents = read_dir(path)?.filter(|dir| match &dir {
Ok(dir) => dir.path().is_dir(),
Err(_) => false,
});
for d in dir_contents {
let d = d?.path(); let fname = match d.file_name() {
Some(fname) => fname.to_str().unwrap_or(""),
None => "",
};
if d.is_dir() && fname.contains("card") {
let drm = DRM::new(d)?;
if !drm.is_empty_info() {
devices.push(drm);
}
}
}
}
Ok(Self { devices })
}
}
impl ToJson for Video {}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DRM {
pub enabled: bool,
pub edid: Option<EDID>,
pub modes: Vec<String>,
}
impl DRM {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let enabled = {
let txt = read_to_string(path.join("enabled"));
match txt {
Ok(txt) => {
let contents = txt.trim();
if contents == "enabled" { true } else { false }
}
Err(_) => false,
}
};
let modes = read_to_string(path.join("modes"))?
.lines()
.map(|s| s.to_string())
.collect::<Vec<_>>();
let edid = EDID::new(path);
Ok(Self {
enabled,
edid: match edid {
Ok(edid) => Some(edid),
Err(why) => {
if enabled {
return Err(why);
} else {
None
}
}
},
modes,
})
}
pub fn is_empty_info(&self) -> bool {
!self.enabled && self.edid.is_none() && self.modes.is_empty()
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct EDID {
pub manufacturer: String,
pub product_code: u16,
pub serial_number: u32,
pub week: u8,
pub year: u16,
pub edid_version: u8,
pub edid_revision: u8,
pub video_input: VideoInputParams,
pub hscreen_size: u8,
pub vscreen_size: u8,
pub display_gamma: u8, }
impl EDID {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
let data = read(path.as_ref().join("edid"))?;
if data.len() < 128 || data[0..8] != [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00] {
return Err(anyhow!(
"Invalid EDID header on path {}",
path.as_ref().display(),
));
}
let manufacturer = {
let word = ((data[8] as u16) << 8) | data[9] as u16;
let c1 = ((word >> 10) & 0x1F) as u8 + 64;
let c2 = ((word >> 5) & 0x1f) as u8 + 64;
let c3 = (word & 0x1f) as u8 + 64;
format!("{}{}{}", c1 as char, c2 as char, c3 as char)
};
let product_code = u16::from_le_bytes([data[10], data[11]]);
let serial_number = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let week = data[16];
let year = data[17] as u16 + 1990;
let edid_version = data[18];
let edid_revision = data[19];
let video_input = VideoInputParams::new(&data);
let hscreen_size = data[21];
let vscreen_size = data[22];
let display_gamma = data[23];
Ok(Self {
manufacturer,
product_code,
serial_number,
week,
year,
edid_version,
edid_revision,
video_input,
hscreen_size,
vscreen_size,
display_gamma,
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum VideoInputParams {
Digital(VideoInputParamsDigital),
Analog(VideoInputParamsAnalog),
}
impl VideoInputParams {
pub fn new(data: &[u8]) -> Self {
let d = data[20];
let bit_depth = ((d >> 7) & 0b00000111) as u8;
if bit_depth == 1 {
Self::Digital(VideoInputParamsDigital::new(data))
} else if bit_depth == 0 {
Self::Analog(VideoInputParamsAnalog::new(data))
} else {
panic!("Unknown 7 bit of 20 byte ({bit_depth})!")
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct VideoInputParamsDigital {
pub bit_depth: BitDepth,
pub video_interface: VideoInterface,
}
impl VideoInputParamsDigital {
pub fn new(data: &[u8]) -> Self {
let d = data[20];
let bit_depth = BitDepth::from(((d >> 4) & 0b00000111) as u8);
let video_interface = VideoInterface::from((d & 0b00000111) as u8);
Self {
bit_depth,
video_interface,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum BitDepth {
Undefined,
B6,
B8,
B10,
B12,
B14,
B16,
Reserved,
Unknown(u8),
}
impl From<u8> for BitDepth {
fn from(value: u8) -> Self {
match value {
0b000 => Self::Undefined,
0b001 => Self::B6,
0b010 => Self::B8,
0b011 => Self::B10,
0b100 => Self::B12,
0b101 => Self::B14,
0b110 => Self::B16,
0b111 => Self::Reserved,
_ => Self::Unknown(value),
}
}
}
impl Display for BitDepth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Undefined => "Undefined".to_string(),
Self::B6 => "6 bits".to_string(),
Self::B8 => "8 bits".to_string(),
Self::B10 => "10 bits".to_string(),
Self::B12 => "12 bits".to_string(),
Self::B14 => "14 bits".to_string(),
Self::B16 => "16 bits".to_string(),
Self::Reserved => "Reserved value".to_string(),
Self::Unknown(val) => format!("Unknown ({val})"),
}
)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum VideoInterface {
Undefined,
DVI,
HDMIa,
HDMIb,
MDDI,
DisplayPort,
Unknown(u8),
}
impl From<u8> for VideoInterface {
fn from(value: u8) -> Self {
match value {
0b0000 => Self::Undefined,
0b0001 => Self::DVI,
0b0010 => Self::HDMIa,
0b0011 => Self::HDMIb,
0b0100 => Self::MDDI,
0b0101 => Self::DisplayPort,
_ => Self::Unknown(value),
}
}
}
impl Display for VideoInterface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Undefined => "Undefined".to_string(),
Self::DVI => "DVI".to_string(),
Self::HDMIa => "HDMI-a".to_string(),
Self::HDMIb => "HDMI-b".to_string(),
Self::MDDI => "MDDI".to_string(),
Self::DisplayPort => "Display Port".to_string(),
Self::Unknown(val) => format!("Unknown (code: {val})"),
}
)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct VideoInputParamsAnalog {
pub white_sync_levels: u8,
pub blank_to_black_setup: u8,
pub separate_sync_supported: u8,
pub composite_sync_supported: u8,
pub sync_on_green_supported: u8,
pub sync_on_green_isused: u8,
}
impl VideoInputParamsAnalog {
pub fn new(data: &[u8]) -> Self {
let d = data[20];
let white_sync_levels = ((d >> 5) & 0b00000011) as u8;
let blank_to_black_setup = (d >> 4) as u8;
let separate_sync_supported = (d >> 3) as u8;
let composite_sync_supported = (d >> 2) as u8;
let sync_on_green_supported = (d >> 1) as u8;
let sync_on_green_isused = (d >> 0) as u8;
Self {
white_sync_levels,
blank_to_black_setup,
separate_sync_supported,
composite_sync_supported,
sync_on_green_supported,
sync_on_green_isused,
}
}
}