use libdisplay_info::edid::DisplayDescriptorRef;
use libdisplay_info::info::Info;
pub use crate::color::{Chromaticity, ColorimetryInfo};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[expect(
clippy::struct_excessive_bools,
reason = "the shape is CTA-861's, not a design choice"
)]
#[non_exhaustive]
pub struct HdrStaticMetadata {
pub desired_content_max_luminance: f32,
pub desired_content_max_frame_avg_luminance: f32,
pub desired_content_min_luminance: f32,
pub type1: bool,
pub traditional_sdr: bool,
pub traditional_hdr: bool,
pub pq: bool,
pub hlg: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[expect(
clippy::struct_excessive_bools,
reason = "one field per encoding in the CTA colorimetry data block"
)]
#[non_exhaustive]
pub struct SupportedColorimetry {
pub bt2020_cycc: bool,
pub bt2020_ycc: bool,
pub bt2020_rgb: bool,
pub st2113_rgb: bool,
pub ictcp: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(
clippy::exhaustive_structs,
reason = "a value defined by its fields; there is nothing to add to it"
)]
pub struct VrefreshRange {
pub min_hz: i32,
pub max_hz: i32,
}
impl VrefreshRange {
#[must_use]
pub const fn is_variable(&self) -> bool {
self.max_hz > self.min_hz
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct ConnectorInfo {
pub name: String,
pub make: String,
pub model: String,
pub serial: Option<String>,
pub width_mm: i32,
pub height_mm: i32,
pub colorimetry: Option<ColorimetryInfo>,
pub hdr: Option<HdrStaticMetadata>,
pub wide_gamut: Option<SupportedColorimetry>,
pub vrefresh_range: Option<VrefreshRange>,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum EdidError {
#[error("EDID blob could not be parsed")]
Unparseable,
}
pub fn parse_edid(blob: &[u8]) -> Result<ConnectorInfo, EdidError> {
let info = Info::parse_edid(blob).map_err(|_| EdidError::Unparseable)?;
let make = info.make().unwrap_or_default();
let model = info.model().unwrap_or_default();
let name = match (make.is_empty(), model.is_empty()) {
(false, false) => format!("{make} {model}"),
(false, true) => make.clone(),
(true, false) => model.clone(),
(true, true) => String::new(),
};
let (mut width_mm, mut height_mm) = (0, 0);
if let Some(edid) = info.edid() {
let size = edid.screen_size();
width_mm = size.width_cm.unwrap_or(0) * 10;
height_mm = size.height_cm.unwrap_or(0) * 10;
}
let primaries = info.default_color_primaries();
let colorimetry =
(primaries.has_primaries || primaries.has_default_white_point).then(|| ColorimetryInfo {
red: Chromaticity {
x: primaries.primary[0].x,
y: primaries.primary[0].y,
},
green: Chromaticity {
x: primaries.primary[1].x,
y: primaries.primary[1].y,
},
blue: Chromaticity {
x: primaries.primary[2].x,
y: primaries.primary[2].y,
},
white: Chromaticity {
x: primaries.default_white.x,
y: primaries.default_white.y,
},
has_primaries: primaries.has_primaries,
has_default_white: primaries.has_default_white_point,
});
let raw_hdr = info.hdr_static_metadata();
let advertises_hdr = raw_hdr.type1 || raw_hdr.traditional_hdr || raw_hdr.pq || raw_hdr.hlg;
let hdr = advertises_hdr.then_some(HdrStaticMetadata {
desired_content_max_luminance: raw_hdr.desired_content_max_luminance,
desired_content_max_frame_avg_luminance: raw_hdr.desired_content_max_frame_avg_luminance,
desired_content_min_luminance: raw_hdr.desired_content_min_luminance,
type1: raw_hdr.type1,
traditional_sdr: raw_hdr.traditional_sdr,
traditional_hdr: raw_hdr.traditional_hdr,
pq: raw_hdr.pq,
hlg: raw_hdr.hlg,
});
let raw_gamut = info.supported_signal_colorimetry();
let any_gamut = raw_gamut.bt2020_cycc
|| raw_gamut.bt2020_ycc
|| raw_gamut.bt2020_rgb
|| raw_gamut.st2113_rgb
|| raw_gamut.ictcp;
let wide_gamut = any_gamut.then_some(SupportedColorimetry {
bt2020_cycc: raw_gamut.bt2020_cycc,
bt2020_ycc: raw_gamut.bt2020_ycc,
bt2020_rgb: raw_gamut.bt2020_rgb,
st2113_rgb: raw_gamut.st2113_rgb,
ictcp: raw_gamut.ictcp,
});
let vrefresh_range = info
.edid()
.and_then(|edid| {
edid.display_descriptors()
.iter()
.find_map(DisplayDescriptorRef::range_limits)
})
.map(|limits| VrefreshRange {
min_hz: limits.min_vert_rate_hz,
max_hz: limits.max_vert_rate_hz,
})
.filter(|range| range.max_hz > 0 && range.min_hz <= range.max_hz);
Ok(ConnectorInfo {
name,
make,
model,
serial: info.serial(),
width_mm,
height_mm,
colorimetry,
hdr,
wide_gamut,
vrefresh_range,
})
}