1
0
Fork 0
forked from wry/wry
wry/crates/edid/src/lib.rs

1312 lines
44 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use {
bstr::{BString, ByteSlice},
std::{
cell::RefCell,
fmt::{Debug, Formatter},
rc::Rc,
},
thiserror::Error,
};
trait BitflagsExt {
fn contains(self, other: Self) -> bool;
}
impl BitflagsExt for u8 {
fn contains(self, other: Self) -> bool {
self & other == other
}
}
struct Stack<T>(RefCell<Vec<T>>);
impl<T> Default for Stack<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> Stack<T> {
fn push(&self, v: T) {
self.0.borrow_mut().push(v);
}
fn pop(&self) -> Option<T> {
self.0.borrow_mut().pop()
}
fn to_vec(&self) -> Vec<T>
where
T: Clone,
{
self.0.borrow().clone()
}
}
#[derive(Copy, Clone, Debug)]
pub enum ColorBitDepth {
Undefined,
Bits6,
Bits8,
Bits10,
Bits12,
Bits14,
Bits16,
Reserved,
}
#[derive(Copy, Clone, Debug)]
pub enum DigitalVideoInterfaceStandard {
Undefined,
Dvi,
HdmiA,
HdmiB,
MDDI,
DisplayPort,
Unknown(u8),
}
#[derive(Copy, Clone)]
pub struct SignalLevelStandard(u8);
impl Debug for SignalLevelStandard {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self.0 {
0 => "+0.7/0.3 V",
1 => "+0.714/0.286 V",
2 => "+1.0/0.4 V",
_ => "+0.7/0 V",
};
Debug::fmt(s, f)
}
}
#[derive(Copy, Clone, Debug)]
pub enum VideoInputDefinition {
Analog {
signal_level_standard: SignalLevelStandard,
blank_to_black_setup_or_pedestal: bool,
separate_h_v_sync_supported: bool,
composite_sync_on_horizontal_supported: bool,
composite_sync_on_green_supported: bool,
serration_on_vertical_sync_supported: bool,
},
Digital {
bit_depth: ColorBitDepth,
video_interface: DigitalVideoInterfaceStandard,
},
}
#[derive(Copy, Clone, Debug)]
pub struct ScreenDimensions {
pub horizontal_screen_size_cm: Option<u8>,
pub vertical_screen_size_cm: Option<u8>,
pub landscape_aspect_ration: Option<f64>,
pub portrait_aspect_ration: Option<f64>,
}
#[derive(Copy, Clone, Debug)]
pub struct ChromaticityCoordinates {
pub red_x: u16,
pub red_y: u16,
pub green_x: u16,
pub green_y: u16,
pub blue_x: u16,
pub blue_y: u16,
pub white_x: u16,
pub white_y: u16,
}
#[derive(Copy, Clone, Debug)]
pub struct EstablishedTimings {
pub s_720x400_70: bool,
pub s_720x400_88: bool,
pub s_640x480_60: bool,
pub s_640x480_67: bool,
pub s_640x480_72: bool,
pub s_640x480_75: bool,
pub s_800x600_56: bool,
pub s_800x600_60: bool,
pub s_800x600_72: bool,
pub s_800x600_75: bool,
pub s_832x624_75: bool,
pub s_1024x768_87: bool,
pub s_1024x768_60: bool,
pub s_1024x768_70: bool,
pub s_1024x768_75: bool,
pub s_1280x1024_75: bool,
pub s_1152x870_75: bool,
}
#[derive(Copy, Clone, Debug)]
pub enum AspectRatio {
A1_1,
A16_10,
A4_3,
A5_4,
A16_9,
}
#[derive(Copy, Clone, Debug)]
pub struct StandardTiming {
pub x_resolution: u16,
pub aspect_ratio: AspectRatio,
pub vertical_frequency: u8,
}
#[derive(Copy, Clone, Debug)]
pub enum AnalogSyncType {
AnalogComposite,
BipolarAnalogComposite,
}
#[derive(Copy, Clone, Debug)]
pub enum SyncSignal {
Analog {
ty: AnalogSyncType,
with_serrations: bool,
sync_on_all_signals: bool,
},
DigitalComposite {
with_serration: bool,
horizontal_sync_is_positive: bool,
},
DigitalSeparate {
vertical_sync_is_positive: bool,
horizontal_sync_is_positive: bool,
},
}
#[derive(Copy, Clone)]
pub enum StereoViewingSupport {
None,
FieldSequentialRightDuringStereoSync,
FieldSequentialLeftDuringStereoSync,
TwoWayInterleavedRightImageOnEvenLines,
TwoWayInterleavedLeftImageOnEvenLines,
FourWayInterleaved,
SideBySideInterleaved,
}
impl Debug for StereoViewingSupport {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let msg = match *self {
StereoViewingSupport::None => "none",
StereoViewingSupport::FieldSequentialRightDuringStereoSync => {
"field sequential, right during stereo sync"
}
StereoViewingSupport::FieldSequentialLeftDuringStereoSync => {
"field sequential, left during stereo sync"
}
StereoViewingSupport::TwoWayInterleavedRightImageOnEvenLines => {
"2-way interleaved, right image on even lines"
}
StereoViewingSupport::TwoWayInterleavedLeftImageOnEvenLines => {
"2-way interleaved, left image on even lines"
}
StereoViewingSupport::FourWayInterleaved => "4-way interleaved",
StereoViewingSupport::SideBySideInterleaved => "side-by-side interleaved",
};
write!(f, "\"{}\"", msg)
}
}
#[derive(Copy, Clone, Debug)]
pub struct DisplayRangeLimitsAndAdditionalTiming {
pub vertical_field_rate_min: u16,
pub vertical_field_rate_max: u16,
pub horizontal_field_rate_min: u16,
pub horizontal_field_rate_max: u16,
pub maximum_pixel_clock_mhz: u16,
pub extended_timing_information: ExtendedTimingInformation,
}
#[derive(Copy, Clone, Debug)]
pub enum AspectRatioPreference {
A4_3,
A16_9,
A16_10,
A5_4,
A15_9,
Unknown(u8),
}
#[derive(Copy, Clone, Debug)]
pub enum ExtendedTimingInformation {
DefaultGtf,
NoTimingInformation,
SecondaryGtf {
start_frequency: u16,
c_value: u16,
m_value: u16,
k_value: u8,
j_value: u16,
},
Cvt {
cvt_major_version: u8,
cvt_minor_version: u8,
additional_clock_precision: u8,
maximum_active_pixels_per_line: Option<u16>,
ar_4_3: bool,
ar_16_9: bool,
ar_16_10: bool,
ar_5_4: bool,
ar_15_9: bool,
ar_preference: AspectRatioPreference,
cvt_rb_reduced_blanking_preferred: bool,
cvt_standard_blanking: bool,
scaling_support_horizontal_shrink: bool,
scaling_support_horizontal_stretch: bool,
scaling_support_vertical_shrink: bool,
scaling_support_vertical_stretch: bool,
preferred_vertical_refresh_rate_hz: u8,
},
Unknown(u8),
}
#[derive(Copy, Clone, Debug, Default)]
pub struct ColorPoint {
pub white_point_index: u8,
pub white_point_x: u16,
pub white_point_y: u16,
pub gamma: Option<f64>,
}
#[derive(Copy, Clone, Debug)]
pub struct EstablishedTimings3 {
pub s640x350_85: bool,
pub s640x400_85: bool,
pub s720x400_85: bool,
pub s640x480_85: bool,
pub s848x480_60: bool,
pub s800x600_85: bool,
pub s1024x768_85: bool,
pub s1152x864_75: bool,
pub s1280x768_60_rb: bool,
pub s1280x768_60: bool,
pub s1280x768_75: bool,
pub s1280x768_85: bool,
pub s1280x960_60: bool,
pub s1280x960_85: bool,
pub s1280x1024_60: bool,
pub s1280x1024_85: bool,
pub s1360x768_60: bool,
pub s1440x900_60_rb: bool,
pub s1440x900_60: bool,
pub s1440x900_75: bool,
pub s1440x900_85: bool,
pub s1400x1050_60_rb: bool,
pub s1400x1050_60: bool,
pub s1400x1050_75: bool,
pub s1400x1050_85: bool,
pub s1680x1050_60_rb: bool,
pub s1680x1050_60: bool,
pub s1680x1050_75: bool,
pub s1680x1050_85: bool,
pub s1600x1200_60: bool,
pub s1600x1200_65: bool,
pub s1600x1200_70: bool,
pub s1600x1200_75: bool,
pub s1600x1200_85: bool,
pub s1792x1344_60: bool,
pub s1792x1344_75: bool,
pub s1856x1392_60: bool,
pub s1856x1392_75: bool,
pub s1920x1200_60_rb: bool,
pub s1920x1200_60: bool,
pub s1920x1200_75: bool,
pub s1920x1200_85: bool,
pub s1920x1440_60: bool,
pub s1920x1440_75: bool,
}
#[derive(Copy, Clone, Debug)]
pub struct ColorManagementData {
pub red_a3: u16,
pub red_a2: u16,
pub green_a3: u16,
pub green_a2: u16,
pub blue_a3: u16,
pub blue_a2: u16,
}
#[derive(Copy, Clone, Debug)]
pub enum CvtAspectRatio {
A4_3,
A16_9,
A16_10,
A15_9,
}
#[derive(Copy, Clone, Debug)]
pub enum CvtPreferredVerticalRate {
R50,
R60,
R75,
R85,
}
#[derive(Copy, Clone, Debug)]
pub struct Cvt3ByteCode {
pub addressable_lines_per_field: u16,
pub aspect_ration: CvtAspectRatio,
pub preferred_vertical_rate: CvtPreferredVerticalRate,
pub r50: bool,
pub r60: bool,
pub r75: bool,
pub r85: bool,
pub r60_reduced_blanking: bool,
}
#[derive(Copy, Clone, Debug)]
pub struct DetailedTimingDescriptor {
pub pixel_clock_khz: u32,
pub horizontal_addressable_pixels: u16,
pub horizontal_blanking_pixels: u16,
pub vertical_addressable_lines: u16,
pub vertical_blanking_lines: u16,
pub horizontal_front_porch_pixels: u16,
pub horizontal_sync_pulse_pixels: u16,
pub vertical_front_porch_lines: u8,
pub vertical_sync_pulse_lines: u8,
pub horizontal_addressable_mm: u16,
pub vertical_addressable_mm: u16,
pub horizontal_left_border_pixels: u8,
pub vertical_top_border_pixels: u8,
pub interlaced: bool,
pub stereo_viewing_support: StereoViewingSupport,
pub sync: SyncSignal,
}
#[derive(Clone, Debug)]
pub enum Descriptor {
Unknown(u8),
DetailedTimingDescriptor(DetailedTimingDescriptor),
DisplayProductSerialNumber(String),
AlphanumericDataString(String),
DisplayProductName(String),
DisplayRangeLimitsAndAdditionalTiming(DisplayRangeLimitsAndAdditionalTiming),
EstablishedTimings3(EstablishedTimings3),
ColorManagementData(ColorManagementData),
StandardTimingIdentifier([Option<StandardTiming>; 6]),
ColorPoint(ColorPoint, Option<ColorPoint>),
Cvt3ByteCode([Cvt3ByteCode; 4]),
}
type EdidContext = (usize, EdidParseContext);
struct EdidParser<'a> {
data: &'a [u8],
pos: usize,
context: Rc<Stack<EdidContext>>,
saved_ctx: Vec<EdidContext>,
errors: Vec<(EdidError, Vec<EdidContext>)>,
}
macro_rules! bail {
($slf:expr, $err:expr) => {{
$slf.saved_ctx = $slf.context.to_vec();
return Err($err);
}};
}
#[derive(Clone, Debug)]
pub enum EdidParseContext {
ReadingBytes(usize),
BaseBlock,
Descriptors,
Descriptor,
ChromaticityCoordinates,
EstablishedTimings,
StandardTimings,
ScreenDimensions,
Gamma,
FeatureSupport,
Magic,
Extension,
IdManufacturerName,
VideoInputDefinition,
}
struct EdidPushedContext {
stack: Rc<Stack<(usize, EdidParseContext)>>,
}
impl Drop for EdidPushedContext {
fn drop(&mut self) {
self.stack.pop();
}
}
impl<'a> EdidParser<'a> {
fn push_ctx(&self, pc: EdidParseContext) -> EdidPushedContext {
self.context.push((self.pos, pc));
EdidPushedContext {
stack: self.context.clone(),
}
}
fn nest(&self, data: &'a [u8]) -> Self {
Self {
data,
pos: 0,
context: self.context.clone(),
saved_ctx: vec![],
errors: vec![],
}
}
fn store_error(&mut self, error: EdidError) {
self.errors.push((error, self.saved_ctx.clone()));
}
fn is_empty(&self) -> bool {
self.pos >= self.data.len()
}
fn read_n<const N: usize>(&mut self) -> Result<&'a [u8; N], EdidError> {
let _ctx = self.push_ctx(EdidParseContext::ReadingBytes(N));
if self.data.len() - self.pos < N {
bail!(self, EdidError::UnexpectedEof);
}
let v = self.data[self.pos..self.pos + N].try_into().unwrap();
self.pos += N;
Ok(v)
}
fn read_var_n(&mut self, n: usize) -> Result<&'a [u8], EdidError> {
let _ctx = self.push_ctx(EdidParseContext::ReadingBytes(n));
if self.data.len() - self.pos < n {
bail!(self, EdidError::UnexpectedEof);
}
let v = &self.data[self.pos..self.pos + n];
self.pos += n;
Ok(v)
}
fn read_u8(&mut self) -> Result<u8, EdidError> {
let &[a] = self.read_n()?;
Ok(a)
}
fn read_u16(&mut self) -> Result<u16, EdidError> {
let &[lo, hi] = self.read_n()?;
Ok(((hi as u16) << 8) + lo as u16)
}
fn read_u32(&mut self) -> Result<u32, EdidError> {
let &[a, b, c, d] = self.read_n()?;
Ok(((d as u32) << 24) + ((c as u32) << 16) + ((b as u32) << 8) + a as u32)
}
fn parse_magic(&mut self) -> Result<(), EdidError> {
let _ctx = self.push_ctx(EdidParseContext::Magic);
let magic = self.read_n::<8>()?;
if magic != &[0, 255, 255, 255, 255, 255, 255, 0] {
bail!(self, EdidError::InvalidMagic(magic.as_bstr().to_owned()));
}
Ok(())
}
fn parse_id_manufacturer_name(&mut self) -> Result<BString, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::IdManufacturerName);
let name = self.read_n::<2>()?;
let a = (name[0] >> 2) & 0b11111;
let b = ((name[0] & 0b11) << 3) | (name[1] >> 5);
let c = name[1] & 0b11111;
let name = [a + b'@', b + b'@', c + b'@'].as_bstr().to_owned();
Ok(name)
}
fn parse_video_input_definition(&mut self) -> Result<VideoInputDefinition, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::VideoInputDefinition);
let val = self.read_u8()?;
let res = if val.contains(0x80) {
VideoInputDefinition::Digital {
bit_depth: match (val >> 4) & 0b111 {
0b000 => ColorBitDepth::Undefined,
0b001 => ColorBitDepth::Bits6,
0b010 => ColorBitDepth::Bits8,
0b011 => ColorBitDepth::Bits10,
0b100 => ColorBitDepth::Bits12,
0b101 => ColorBitDepth::Bits14,
0b110 => ColorBitDepth::Bits16,
_ => ColorBitDepth::Reserved,
},
video_interface: match val & 0b1111 {
0b0000 => DigitalVideoInterfaceStandard::Undefined,
0b0001 => DigitalVideoInterfaceStandard::Dvi,
0b0010 => DigitalVideoInterfaceStandard::HdmiA,
0b0011 => DigitalVideoInterfaceStandard::HdmiB,
0b0100 => DigitalVideoInterfaceStandard::MDDI,
0b0101 => DigitalVideoInterfaceStandard::DisplayPort,
n => DigitalVideoInterfaceStandard::Unknown(n),
},
}
} else {
VideoInputDefinition::Analog {
signal_level_standard: SignalLevelStandard((val >> 5) & 0b11),
blank_to_black_setup_or_pedestal: (val >> 4).contains(1),
separate_h_v_sync_supported: (val >> 3).contains(1),
composite_sync_on_horizontal_supported: (val >> 2).contains(1),
composite_sync_on_green_supported: (val >> 1).contains(1),
serration_on_vertical_sync_supported: (val >> 0).contains(1),
}
};
Ok(res)
}
fn parse_screen_dimensions(&mut self) -> Result<ScreenDimensions, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::ScreenDimensions);
let &[hor, vert] = self.read_n()?;
let mut res = ScreenDimensions {
horizontal_screen_size_cm: None,
vertical_screen_size_cm: None,
landscape_aspect_ration: None,
portrait_aspect_ration: None,
};
if hor != 0 && vert != 0 {
res.horizontal_screen_size_cm = Some(hor);
res.vertical_screen_size_cm = Some(vert);
} else if vert != 0 {
res.portrait_aspect_ration = Some(100.0 / (vert as f64 + 99.0));
} else if hor != 0 {
res.landscape_aspect_ration = Some((hor as f64 + 99.0) / 100.0);
}
Ok(res)
}
fn parse_gamma(&mut self) -> Result<Option<f64>, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::Gamma);
let val = self.read_u8()?;
if val == 0xff {
Ok(None)
} else {
Ok(Some((val as f64 + 100.0) / 100.0))
}
}
fn parse_feature_support(&mut self, digital: bool) -> Result<FeatureSupport, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::FeatureSupport);
let val = self.read_u8()?;
Ok(FeatureSupport {
standby_supported: val.contains(0x80),
suspend_supported: val.contains(0x40),
active_off_supported: val.contains(0x20),
features: if digital {
FeatureSupport2::Digital {
rgb444_supported: true,
ycrcb422_supported: val.contains(0x10),
ycrcb444_supported: val.contains(0x08),
}
} else {
FeatureSupport2::Analog {
display_color_type: match (val >> 3) & 0b11 {
0b00 => DisplayColorType::Monochrome,
0b01 => DisplayColorType::Rgb,
0b10 => DisplayColorType::NonRgb,
_ => DisplayColorType::Undefined,
},
}
},
srgb_is_default_color_space: val.contains(0x04),
preferred_mode_is_native: val.contains(0x02),
display_is_continuous_frequency: val.contains(0x01),
})
}
fn parse_chromaticity_coordinates(&mut self) -> Result<ChromaticityCoordinates, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::ChromaticityCoordinates);
let b = self.read_n::<10>()?;
let rx = ((b[0] as u16 >> 6) & 0b11) + ((b[2] as u16) << 2);
let ry = ((b[0] as u16 >> 4) & 0b11) + ((b[3] as u16) << 2);
let gx = ((b[0] as u16 >> 2) & 0b11) + ((b[4] as u16) << 2);
let gy = ((b[0] as u16 >> 0) & 0b11) + ((b[5] as u16) << 2);
let bx = ((b[1] as u16 >> 6) & 0b11) + ((b[6] as u16) << 2);
let by = ((b[1] as u16 >> 4) & 0b11) + ((b[7] as u16) << 2);
let wx = ((b[1] as u16 >> 2) & 0b11) + ((b[8] as u16) << 2);
let wy = ((b[1] as u16 >> 0) & 0b11) + ((b[9] as u16) << 2);
Ok(ChromaticityCoordinates {
red_x: rx,
red_y: ry,
green_x: gx,
green_y: gy,
blue_x: bx,
blue_y: by,
white_x: wx,
white_y: wy,
})
}
fn parse_established_timings(&mut self) -> Result<EstablishedTimings, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::EstablishedTimings);
let b = self.read_n::<3>()?;
Ok(EstablishedTimings {
s_720x400_70: b[0].contains(0x80),
s_720x400_88: b[0].contains(0x40),
s_640x480_60: b[0].contains(0x20),
s_640x480_67: b[0].contains(0x10),
s_640x480_72: b[0].contains(0x08),
s_640x480_75: b[0].contains(0x04),
s_800x600_56: b[0].contains(0x02),
s_800x600_60: b[0].contains(0x01),
s_800x600_72: b[0].contains(0x80),
s_800x600_75: b[0].contains(0x40),
s_832x624_75: b[0].contains(0x20),
s_1024x768_87: b[0].contains(0x10),
s_1024x768_60: b[0].contains(0x08),
s_1024x768_70: b[0].contains(0x04),
s_1024x768_75: b[0].contains(0x02),
s_1280x1024_75: b[0].contains(0x01),
s_1152x870_75: b[0].contains(0x80),
})
}
fn parse_standard_timing(&mut self, revision: u8, a: u8, b: u8) -> Option<StandardTiming> {
if a == 0 {
return None;
}
Some(StandardTiming {
x_resolution: (a as u16 + 31) * 8,
aspect_ratio: match b >> 6 {
0b00 if revision < 3 => AspectRatio::A1_1,
0b00 => AspectRatio::A16_10,
0b01 => AspectRatio::A4_3,
0b10 => AspectRatio::A5_4,
_ => AspectRatio::A16_9,
},
vertical_frequency: 60 + (b & 0b111111),
})
}
fn parse_standard_timings2(
&mut self,
revision: u8,
b: &[u8; 18],
) -> [Option<StandardTiming>; 6] {
let mut res = [None; 6];
for i in 0..6 {
let x = b[5 + 2 * i];
let y = b[5 + 2 * i + 1];
res[i] = self.parse_standard_timing(revision, x, y);
}
res
}
fn parse_color_point(&mut self, b: &[u8; 18]) -> (ColorPoint, Option<ColorPoint>) {
let mut res = [Default::default(); 2];
for n in 0..2 {
let b = &b[5 * (n + 1)..];
res[n] = ColorPoint {
white_point_index: b[0],
white_point_x: ((b[2] as u16) << 2) | ((b[1] as u16) >> 2),
white_point_y: ((b[3] as u16) << 2) | ((b[1] as u16) & 0b11),
gamma: if b[4] == 0xff {
None
} else {
Some((b[5] as f64 + 100.0) / 100.0)
},
};
}
let second = if res[1].white_point_index != 0 {
Some(res[1])
} else {
None
};
(res[0], second)
}
fn parse_standard_timings(
&mut self,
revision: u8,
) -> Result<[Option<StandardTiming>; 8], EdidError> {
let _ctx = self.push_ctx(EdidParseContext::StandardTimings);
let bytes = self.read_n::<16>()?;
let mut res = [None; 8];
for i in 0..8 {
let a = bytes[2 * i];
let b = bytes[2 * i + 1];
if (a, b) != (1, 1) {
res[i] = self.parse_standard_timing(revision, a, b);
}
}
Ok(res)
}
fn parse_detailed_timing_descriptor(&self, b: &[u8; 18]) -> DetailedTimingDescriptor {
let l = b[17];
DetailedTimingDescriptor {
pixel_clock_khz: u16::from_le_bytes([b[0], b[1]]) as u32 * 10_000,
horizontal_addressable_pixels: u16::from_le_bytes([b[2], b[4] >> 4]),
horizontal_blanking_pixels: u16::from_le_bytes([b[3], b[4] & 0b1111]),
vertical_addressable_lines: u16::from_le_bytes([b[5], b[7] >> 4]),
vertical_blanking_lines: u16::from_le_bytes([b[6], b[7] & 0b1111]),
horizontal_front_porch_pixels: u16::from_le_bytes([b[8], b[11] >> 6]),
horizontal_sync_pulse_pixels: u16::from_le_bytes([b[9], (b[11] >> 4) & 0b11]),
vertical_front_porch_lines: (b[10] >> 4) | ((b[11] & 0b1100) << 2),
vertical_sync_pulse_lines: (b[10] & 0b1111) | ((b[11] & 0b11) << 4),
horizontal_addressable_mm: u16::from_le_bytes([b[12], b[14] >> 4]),
vertical_addressable_mm: u16::from_le_bytes([b[13], b[14] & 0b1111]),
horizontal_left_border_pixels: b[15],
vertical_top_border_pixels: b[16],
interlaced: l.contains(0x80),
stereo_viewing_support: match ((l >> 4) & 0b110) | (l & 0b1) {
0b010 => StereoViewingSupport::FieldSequentialRightDuringStereoSync,
0b100 => StereoViewingSupport::FieldSequentialLeftDuringStereoSync,
0b011 => StereoViewingSupport::TwoWayInterleavedRightImageOnEvenLines,
0b101 => StereoViewingSupport::TwoWayInterleavedLeftImageOnEvenLines,
0b110 => StereoViewingSupport::FourWayInterleaved,
0b111 => StereoViewingSupport::SideBySideInterleaved,
_ => StereoViewingSupport::None,
},
sync: if l.contains(0b10000) {
if l.contains(0b01000) {
SyncSignal::DigitalSeparate {
vertical_sync_is_positive: l.contains(0b100),
horizontal_sync_is_positive: l.contains(0b10),
}
} else {
SyncSignal::DigitalComposite {
with_serration: l.contains(0b100),
horizontal_sync_is_positive: l.contains(0b10),
}
}
} else {
SyncSignal::Analog {
ty: if l.contains(0b1000) {
AnalogSyncType::BipolarAnalogComposite
} else {
AnalogSyncType::AnalogComposite
},
with_serrations: l.contains(0b100),
sync_on_all_signals: l.contains(0b10),
}
},
}
}
fn parse_display_range_limits_and_additional_timing(
&self,
b: &[u8; 18],
) -> DisplayRangeLimitsAndAdditionalTiming {
let min_vert_off = b[4].contains(0b0001);
let max_vert_off = min_vert_off || b[4].contains(0b0010);
let min_horz_off = b[4].contains(0b0100);
let max_horz_off = min_horz_off || b[4].contains(0b1000);
DisplayRangeLimitsAndAdditionalTiming {
vertical_field_rate_min: b[5] as u16 + if min_vert_off { 255 } else { 0 },
vertical_field_rate_max: b[6] as u16 + if max_vert_off { 255 } else { 0 },
horizontal_field_rate_min: b[7] as u16 + if min_horz_off { 255 } else { 0 },
horizontal_field_rate_max: b[8] as u16 + if max_horz_off { 255 } else { 0 },
maximum_pixel_clock_mhz: b[9] as u16 * 10,
extended_timing_information: match b[10] {
0x0 => ExtendedTimingInformation::DefaultGtf,
0x1 => ExtendedTimingInformation::NoTimingInformation,
0x2 => ExtendedTimingInformation::SecondaryGtf {
start_frequency: b[12] as u16,
c_value: b[13] as u16,
m_value: u16::from_le_bytes([b[14], b[15]]),
k_value: b[16],
j_value: b[17] as u16,
},
0x4 => ExtendedTimingInformation::Cvt {
cvt_major_version: b[11] >> 4,
cvt_minor_version: b[11] & 0b1111,
additional_clock_precision: b[12] >> 2,
maximum_active_pixels_per_line: if b[13] == 0 {
None
} else {
Some((((b[12] as u16 & 0b11) << 8) | b[13] as u16) * 8)
},
ar_4_3: b[14].contains(0x80),
ar_16_9: b[14].contains(0x40),
ar_16_10: b[14].contains(0x20),
ar_5_4: b[14].contains(0x10),
ar_15_9: b[14].contains(0x08),
ar_preference: match b[15] >> 5 {
0b000 => AspectRatioPreference::A4_3,
0b001 => AspectRatioPreference::A16_9,
0b010 => AspectRatioPreference::A16_10,
0b011 => AspectRatioPreference::A5_4,
0b100 => AspectRatioPreference::A15_9,
n => AspectRatioPreference::Unknown(n),
},
cvt_rb_reduced_blanking_preferred: b[15].contains(0b10000),
cvt_standard_blanking: b[15].contains(0b1000),
scaling_support_horizontal_shrink: b[16].contains(0x80),
scaling_support_horizontal_stretch: b[16].contains(0x40),
scaling_support_vertical_shrink: b[16].contains(0x20),
scaling_support_vertical_stretch: b[16].contains(0x10),
preferred_vertical_refresh_rate_hz: b[17],
},
n => ExtendedTimingInformation::Unknown(n),
},
}
}
fn parse_established_timings3(&self, b: &[u8; 18]) -> EstablishedTimings3 {
EstablishedTimings3 {
s640x350_85: b[6].contains(0x80),
s640x400_85: b[6].contains(0x40),
s720x400_85: b[6].contains(0x20),
s640x480_85: b[6].contains(0x10),
s848x480_60: b[6].contains(0x08),
s800x600_85: b[6].contains(0x04),
s1024x768_85: b[6].contains(0x02),
s1152x864_75: b[6].contains(0x01),
s1280x768_60_rb: b[7].contains(0x80),
s1280x768_60: b[7].contains(0x40),
s1280x768_75: b[7].contains(0x20),
s1280x768_85: b[7].contains(0x10),
s1280x960_60: b[7].contains(0x08),
s1280x960_85: b[7].contains(0x04),
s1280x1024_60: b[7].contains(0x02),
s1280x1024_85: b[7].contains(0x01),
s1360x768_60: b[8].contains(0x80),
s1440x900_60_rb: b[8].contains(0x40),
s1440x900_60: b[8].contains(0x20),
s1440x900_75: b[8].contains(0x10),
s1440x900_85: b[8].contains(0x08),
s1400x1050_60_rb: b[8].contains(0x04),
s1400x1050_60: b[8].contains(0x02),
s1400x1050_75: b[8].contains(0x01),
s1400x1050_85: b[9].contains(0x80),
s1680x1050_60_rb: b[9].contains(0x40),
s1680x1050_60: b[9].contains(0x20),
s1680x1050_75: b[9].contains(0x10),
s1680x1050_85: b[9].contains(0x08),
s1600x1200_60: b[9].contains(0x04),
s1600x1200_65: b[9].contains(0x02),
s1600x1200_70: b[9].contains(0x01),
s1600x1200_75: b[10].contains(0x80),
s1600x1200_85: b[10].contains(0x40),
s1792x1344_60: b[10].contains(0x20),
s1792x1344_75: b[10].contains(0x10),
s1856x1392_60: b[10].contains(0x08),
s1856x1392_75: b[10].contains(0x04),
s1920x1200_60_rb: b[10].contains(0x02),
s1920x1200_60: b[10].contains(0x01),
s1920x1200_75: b[11].contains(0x80),
s1920x1200_85: b[11].contains(0x40),
s1920x1440_60: b[11].contains(0x20),
s1920x1440_75: b[11].contains(0x10),
}
}
fn parse_color_management_data(&self, b: &[u8; 18]) -> ColorManagementData {
ColorManagementData {
red_a3: u16::from_le_bytes([b[6], b[7]]),
red_a2: u16::from_le_bytes([b[8], b[9]]),
green_a3: u16::from_le_bytes([b[10], b[11]]),
green_a2: u16::from_le_bytes([b[12], b[13]]),
blue_a3: u16::from_le_bytes([b[14], b[15]]),
blue_a2: u16::from_le_bytes([b[16], b[17]]),
}
}
fn parse_cvt3_byte_codes(&self, b: &[u8; 18]) -> [Cvt3ByteCode; 4] {
let parse = |n: usize| {
let b = &b[6 + 3 * n..];
Cvt3ByteCode {
addressable_lines_per_field: u16::from_le_bytes([b[0], b[1] >> 4]),
aspect_ration: match (b[1] >> 2) & 0b11 {
0 => CvtAspectRatio::A4_3,
1 => CvtAspectRatio::A16_9,
2 => CvtAspectRatio::A16_10,
_ => CvtAspectRatio::A15_9,
},
preferred_vertical_rate: match (b[2] >> 5) & 0b11 {
0 => CvtPreferredVerticalRate::R50,
1 => CvtPreferredVerticalRate::R60,
2 => CvtPreferredVerticalRate::R75,
_ => CvtPreferredVerticalRate::R85,
},
r50: b[2].contains(0b10000),
r60: b[2].contains(0b01000),
r75: b[2].contains(0b00100),
r85: b[2].contains(0b00010),
r60_reduced_blanking: b[2].contains(0b00001),
}
};
[parse(0), parse(1), parse(2), parse(3)]
}
fn parse_descriptor(&mut self, revision: u8) -> Result<Option<Descriptor>, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::Descriptor);
let b = self.read_n::<18>()?;
let str = || {
let mut s = &b[5..];
if let Some(n) = s.find_byte(b'\n') {
s = &s[..n];
};
let mut res = String::new();
for &b in s {
res.push_str(CP437[b as usize]);
}
res
};
let res = if (b[0], b[1]) == (0, 0) {
match b[3] {
0xff => Descriptor::DisplayProductSerialNumber(str()),
0xfe => Descriptor::AlphanumericDataString(str()),
0xfd => Descriptor::DisplayRangeLimitsAndAdditionalTiming(
self.parse_display_range_limits_and_additional_timing(b),
),
0xfc => Descriptor::DisplayProductName(str()),
0xfb => {
let (first, second) = self.parse_color_point(b);
Descriptor::ColorPoint(first, second)
}
0xfa => {
Descriptor::StandardTimingIdentifier(self.parse_standard_timings2(revision, b))
}
0xf9 => Descriptor::ColorManagementData(self.parse_color_management_data(b)),
0xf8 => Descriptor::Cvt3ByteCode(self.parse_cvt3_byte_codes(b)),
0xf7 => Descriptor::EstablishedTimings3(self.parse_established_timings3(b)),
0x10 => return Ok(None),
n => Descriptor::Unknown(n),
}
} else {
Descriptor::DetailedTimingDescriptor(self.parse_detailed_timing_descriptor(b))
};
Ok(Some(res))
}
fn parse_descriptors(&mut self, revision: u8) -> Result<[Option<Descriptor>; 4], EdidError> {
let _ctx = self.push_ctx(EdidParseContext::Descriptors);
let mut res = [None, None, None, None];
for res in &mut res {
*res = self.parse_descriptor(revision)?;
}
Ok(res)
}
fn parse_base_block(&mut self) -> Result<EdidBaseBlock, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::BaseBlock);
self.parse_magic()?;
let id_manufacturer_name = self.parse_id_manufacturer_name()?;
let id_product_code = self.read_u16()?;
let id_serial_number = self.read_u32()?;
let mut week_of_manufacture = None;
let mut model_year = None;
let mut year_of_manufacture = None;
{
let &[a, b] = self.read_n()?;
if matches!(a, 1..=0x36) {
week_of_manufacture = Some(a);
}
let year = b as u16 + 1990;
if a == 0xff {
model_year = Some(year);
} else {
year_of_manufacture = Some(year);
}
}
let &[edid_version, edid_revision] = self.read_n()?;
let video_input_definition = self.parse_video_input_definition()?;
let is_digital = matches!(video_input_definition, VideoInputDefinition::Digital { .. });
let screen_dimensions = self.parse_screen_dimensions()?;
let gamma = self.parse_gamma()?;
let feature_support = self.parse_feature_support(is_digital)?;
let chromaticity_coordinates = self.parse_chromaticity_coordinates()?;
let established_timings = self.parse_established_timings()?;
let standard_timings = self.parse_standard_timings(edid_revision)?;
let descriptors = self.parse_descriptors(edid_revision)?;
let num_extensions = self.read_u8()?;
let _checksum = self.read_u8()?;
Ok(EdidBaseBlock {
id_manufacturer_name,
id_product_code,
id_serial_number,
week_of_manufacture,
model_year,
year_of_manufacture,
edid_version,
edid_revision,
video_input_definition,
screen_dimensions,
gamma,
feature_support,
chromaticity_coordinates,
established_timings,
standard_timings,
descriptors,
num_extensions,
})
}
fn parse_cta_amd_vendor_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
let _ = self.read_n::<2>()?;
Ok(CtaDataBlock::VendorAmd(CtaAmdVendorDataBlock {
minimum_refresh_hz: self.read_u8()?,
maximum_refresh_hz: self.read_u8()?,
}))
}
fn parse_cta_vendor_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
match self.read_n::<3>()? {
[0x1A, 0x00, 0x00] => self.parse_cta_amd_vendor_data_block(),
_ => Ok(CtaDataBlock::Unknown),
}
}
fn parse_cta_colorimetry_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
let [lo, hi] = *self.read_n::<2>()?;
Ok(CtaDataBlock::Colorimetry(CtaColorimetryDataBlock {
bt2020_rgb: lo.contains(0x80),
bt2020_ycc: lo.contains(0x40),
bt2020_cycc: lo.contains(0x20),
op_rgb: lo.contains(0x10),
op_ycc_601601: lo.contains(0x08),
s_ycc_601: lo.contains(0x04),
xv_ycc_709: lo.contains(0x02),
xv_ycc_601: lo.contains(0x01),
dci_p3: hi.contains(0x80),
}))
}
fn parse_cta_hdr_static_metadata_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
let et = self.read_u8()?;
let _ = self.read_u8()?;
let mut read_luminance = |min: bool| {
let v = self.read_u8().unwrap_or_default();
if v == 0 {
None
} else if min {
Some((v as f64 / 255.0).powi(2) / 100.0)
} else {
Some(50.0 * 2.0f64.powf(v as f64 / 32.0))
}
};
Ok(CtaDataBlock::StaticHdrMetadata(
CtaStaticHdrMetadataDataBlock {
traditional_gamma_sdr_luminance: et.contains(0x01),
traditional_gamma_hdr_luminance: et.contains(0x02),
smpte_st_2084: et.contains(0x04),
hlg: et.contains(0x08),
max_luminance: read_luminance(false),
max_frame_average_luminance: read_luminance(false),
min_luminance: read_luminance(true),
},
))
}
fn parse_cta_extended_data_block(&mut self) -> Result<CtaDataBlock, EdidError> {
match self.read_u8()? {
0x5 => self.parse_cta_colorimetry_data_block(),
0x6 => self.parse_cta_hdr_static_metadata_data_block(),
_ => Ok(CtaDataBlock::Unknown),
}
}
fn parse_cta_data_block(&mut self, tag: u8) -> Result<CtaDataBlock, EdidError> {
match tag {
0x3 => self.parse_cta_vendor_data_block(),
0x7 => self.parse_cta_extended_data_block(),
_ => Ok(CtaDataBlock::Unknown),
}
}
fn parse_cta_extension_v3(&mut self) -> Result<EdidExtension, EdidError> {
let detailed_timing_descriptors_offset = self.read_u8()? as usize;
let _ = self.read_u8()?;
let mut data_blocks = vec![];
while self.pos < detailed_timing_descriptors_offset {
let b1 = self.read_u8()?;
let data = self.read_var_n(b1 as usize & 0x1f)?;
let mut parser = self.nest(data);
match parser.parse_cta_data_block(b1 >> 5) {
Ok(d) => data_blocks.push(d),
Err(e) => {
self.saved_ctx = parser.saved_ctx;
self.store_error(e);
}
}
}
Ok(EdidExtension::CtaV3(CtaExtensionV3 { data_blocks }))
}
fn parse_cta_extension(&mut self) -> Result<EdidExtension, EdidError> {
// https://web.archive.org/web/20171201033424/https://standards.cta.tech/kwspub/published_docs/CTA-861-G_FINAL_revised_2017.pdf
match self.read_u8()? {
0x3 => self.parse_cta_extension_v3(),
_ => Ok(EdidExtension::Unknown),
}
}
fn parse_extension_impl(&mut self) -> Result<EdidExtension, EdidError> {
match self.read_u8()? {
0x2 => self.parse_cta_extension(),
_ => Ok(EdidExtension::Unknown),
}
}
fn parse_extension(&mut self) -> Result<EdidExtension, EdidError> {
let _ctx = self.push_ctx(EdidParseContext::Extension);
let data = self.read_n::<128>()?;
let mut parser = self.nest(data);
let res = parser.parse_extension_impl();
if res.is_err() {
self.saved_ctx = parser.saved_ctx;
}
res
}
fn parse(&mut self) -> Result<EdidFile, EdidError> {
let bb = self.parse_base_block()?;
let mut exts = vec![];
while !self.is_empty() {
match self.parse_extension() {
Ok(e) => exts.push(e),
Err(e) => self.store_error(e),
}
}
Ok(EdidFile {
base_block: bb,
extension_blocks: exts,
})
}
}
#[derive(Debug)]
pub enum DisplayColorType {
Monochrome,
Rgb,
NonRgb,
Undefined,
}
#[derive(Debug)]
pub enum FeatureSupport2 {
Analog {
display_color_type: DisplayColorType,
},
Digital {
rgb444_supported: bool,
ycrcb444_supported: bool,
ycrcb422_supported: bool,
},
}
#[derive(Debug)]
pub struct FeatureSupport {
pub standby_supported: bool,
pub suspend_supported: bool,
pub active_off_supported: bool,
pub features: FeatureSupport2,
pub srgb_is_default_color_space: bool,
pub preferred_mode_is_native: bool,
pub display_is_continuous_frequency: bool,
}
#[derive(Debug)]
pub struct EdidBaseBlock {
pub id_manufacturer_name: BString,
pub id_product_code: u16,
pub id_serial_number: u32,
pub week_of_manufacture: Option<u8>,
pub model_year: Option<u16>,
pub year_of_manufacture: Option<u16>,
pub edid_version: u8,
pub edid_revision: u8,
pub video_input_definition: VideoInputDefinition,
pub screen_dimensions: ScreenDimensions,
pub gamma: Option<f64>,
pub feature_support: FeatureSupport,
pub chromaticity_coordinates: ChromaticityCoordinates,
pub established_timings: EstablishedTimings,
pub standard_timings: [Option<StandardTiming>; 8],
pub descriptors: [Option<Descriptor>; 4],
pub num_extensions: u8,
}
#[derive(Debug)]
pub enum EdidExtension {
Unknown,
CtaV3(CtaExtensionV3),
}
#[derive(Debug)]
pub struct CtaExtensionV3 {
pub data_blocks: Vec<CtaDataBlock>,
}
#[derive(Debug)]
pub enum CtaDataBlock {
Unknown,
VendorAmd(CtaAmdVendorDataBlock),
Colorimetry(CtaColorimetryDataBlock),
StaticHdrMetadata(CtaStaticHdrMetadataDataBlock),
}
#[derive(Debug)]
pub struct CtaAmdVendorDataBlock {
pub minimum_refresh_hz: u8,
pub maximum_refresh_hz: u8,
}
#[derive(Copy, Clone, Debug)]
pub struct CtaColorimetryDataBlock {
pub bt2020_rgb: bool,
pub bt2020_ycc: bool,
pub bt2020_cycc: bool,
pub op_rgb: bool,
pub op_ycc_601601: bool,
pub s_ycc_601: bool,
pub xv_ycc_709: bool,
pub xv_ycc_601: bool,
pub dci_p3: bool,
}
#[derive(Copy, Clone, Debug)]
pub struct CtaStaticHdrMetadataDataBlock {
pub traditional_gamma_sdr_luminance: bool,
pub traditional_gamma_hdr_luminance: bool,
pub smpte_st_2084: bool,
pub hlg: bool,
pub max_luminance: Option<f64>,
pub max_frame_average_luminance: Option<f64>,
pub min_luminance: Option<f64>,
}
#[derive(Debug)]
pub struct EdidFile {
pub base_block: EdidBaseBlock,
pub extension_blocks: Vec<EdidExtension>,
}
#[derive(Debug, Error)]
pub enum EdidError {
#[error("Unexpected end-of-file")]
UnexpectedEof,
#[error("Invalid magic header")]
InvalidMagic(BString),
}
pub fn parse(data: &[u8]) -> Result<EdidFile, EdidError> {
let mut parser = EdidParser {
data,
pos: 0,
context: Rc::new(Default::default()),
saved_ctx: vec![],
errors: vec![],
};
parser.parse()
}
const CP437: &[&str] = &[
"\u{0}", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "§", "", "", "", "", "", "", "", "", "", "", " ", "!", "\"", "#", "$",
"%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]",
"^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "", "Ç", "ü", "é", "â",
"ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å", "É", "æ", "Æ", "ô", "ö", "ò", "û",
"ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "", "ƒ", "á", "í", "ó", "ú", "ñ", "Ñ", "ª", "º", "¿", "",
"¬", "½", "¼", "¡", "«", "»", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "α", "ß", "Γ",
"π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "", "φ", "ε", "", "", "±", "", "", "", "",
"÷", "", "°", "", "·", "", "", "²", "", "\u{a0}",
];