1079 lines
36 KiB
Rust
1079 lines
36 KiB
Rust
use crate::utils::bitflags::BitflagsExt;
|
||
use crate::utils::ptr_ext::PtrExt;
|
||
use crate::utils::stack::Stack;
|
||
use bstr::{BString, ByteSlice};
|
||
use std::fmt::{Debug, Formatter};
|
||
use std::rc::Rc;
|
||
use thiserror::Error;
|
||
|
||
#[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(BString),
|
||
AlphanumericDataString(BString),
|
||
DisplayProductName(BString),
|
||
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 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 = unsafe { self.data[self.pos..].as_ptr().cast::<[u8; N]>().deref() };
|
||
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 s = &b[5..];
|
||
match s.find_byte(b'\n') {
|
||
Some(n) => s[..n].as_bstr().to_owned(),
|
||
_ => s.as_bstr().to_owned(),
|
||
}
|
||
};
|
||
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 i in 0..4 {
|
||
res[i] = 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_extension(&mut self) -> Result<EdidExtension, EdidError> {
|
||
let _ctx = self.push_ctx(EdidParseContext::Extension);
|
||
self.read_n::<128>()?;
|
||
Ok(EdidExtension::Unknown)
|
||
}
|
||
|
||
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,
|
||
}
|
||
|
||
#[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()
|
||
}
|