///|
pub fn parse_edid_hex(input : String) -> Result[Edid, EdidError] {
match parse_hex_bytes(input) {
Ok(bytes) => parse_edid_bytes(bytes)
Err(err) => Err(err)
}
}
///|
pub fn parse_edid_bytes(data : Array[Int]) -> Result[Edid, EdidError] {
if data.length() < 128 {
return Err(
EdidError::make(
InvalidLength,
"EDID input must contain at least one 128-byte base block",
Some(data.length()),
),
)
}
if !has_edid_header(data) {
return Err(
EdidError::make(
InvalidHeader,
"EDID base block does not start with 00 FF FF FF FF FF FF 00",
Some(0),
),
)
}
let diagnostics : Array[EdidDiagnostic] = []
if !checksum_valid(data, 0) {
diagnostics.push(
EdidDiagnostic::make(
InvalidChecksum,
Error,
"EDID base block checksum is invalid",
Some(127),
),
)
}
let version_major = byte_at(data, 18)
let version_minor = byte_at(data, 19)
if version_major != 1 {
diagnostics.push(
EdidDiagnostic::make(
UnsupportedVersion,
Warning,
"Only EDID version 1.x base blocks are fully decoded",
Some(18),
),
)
}
let extension_count = byte_at(data, 126)
if extension_count > 0 {
diagnostics.push(
EdidDiagnostic::make(
ExtensionBlockIgnored,
Note,
"Extension blocks are counted but not decoded by the base parser",
Some(126),
),
)
}
let edid : Edid = {
bytes: first_base_block(data),
manufacturer: parse_manufacturer(data),
product_code: u16_le(data, 10),
serial_number: u32_le(data, 12),
manufacture: parse_manufacture_date(data),
version_major,
version_minor,
video_input: parse_video_input(data),
display: parse_basic_display(data),
chromaticity: parse_chromaticity(data),
established_timings: parse_established_timings(data),
standard_timings: parse_standard_timings(data),
descriptors: parse_descriptors(data, diagnostics),
extension_count,
checksum: byte_at(data, 127),
diagnostics,
}
Ok(edid)
}
///|
fn first_base_block(data : Array[Int]) -> Array[Int] {
let out : Array[Int] = []
let mut i = 0
while i < 128 {
out.push(byte_at(data, i))
i = i + 1
}
out
}
///|
fn parse_manufacturer(data : Array[Int]) -> ManufacturerId {
let raw = u16_be(data, 8)
let c1 = (((raw >> 10) & 31) + 64).to_char().unwrap()
let c2 = (((raw >> 5) & 31) + 64).to_char().unwrap()
let c3 = ((raw & 31) + 64).to_char().unwrap()
{ raw, code: String::from_array([c1, c2, c3]) }
}
///|
fn parse_manufacture_date(data : Array[Int]) -> ManufactureDate {
let week = byte_at(data, 16)
let year = byte_at(data, 17) + 1990
{ week, year, model_year: week == 0xFF }
}
///|
fn parse_video_input(data : Array[Int]) -> VideoInput {
let value = byte_at(data, 20)
if bit_set(value, 0x80) {
{
signal: Digital,
interface_name: digital_interface_name(value & 0x0F),
bit_depth: digital_bit_depth((value >> 4) & 0x07),
voltage: "",
sync: "",
}
} else {
{
signal: Analog,
interface_name: "analog",
bit_depth: "",
voltage: analog_voltage((value >> 5) & 0x03),
sync: analog_sync(value),
}
}
}
///|
fn digital_bit_depth(code : Int) -> String {
match code {
0 => "undefined"
1 => "6-bit"
2 => "8-bit"
3 => "10-bit"
4 => "12-bit"
5 => "14-bit"
6 => "16-bit"
_ => "reserved"
}
}
///|
fn digital_interface_name(code : Int) -> String {
match code {
0 => "undefined"
1 => "DVI"
2 => "HDMI-a"
3 => "HDMI-b"
4 => "MDDI"
5 => "DisplayPort"
_ => "reserved"
}
}
///|
fn analog_voltage(code : Int) -> String {
match code {
0 => "0.700/0.300 V"
1 => "0.714/0.286 V"
2 => "1.000/0.400 V"
_ => "0.700/0.000 V"
}
}
///|
fn analog_sync(value : Int) -> String {
let buf = StringBuilder(size_hint=48)
if bit_set(value, 0x10) {
buf.write_string("separate ")
}
if bit_set(value, 0x08) {
buf.write_string("composite ")
}
if bit_set(value, 0x04) {
buf.write_string("sync-on-green ")
}
if bit_set(value, 0x02) {
buf.write_string("serration ")
}
let text = buf.to_string().trim(chars=" ").to_owned()
if text.is_empty() {
"none"
} else {
text
}
}
///|
fn parse_basic_display(data : Array[Int]) -> BasicDisplay {
let gamma_byte = byte_at(data, 23)
let features = byte_at(data, 24)
{
width_cm: byte_at(data, 21),
height_cm: byte_at(data, 22),
gamma_x100: if gamma_byte == 0xFF {
None
} else {
Some(gamma_byte + 100)
},
standby: bit_set(features, 0x80),
suspend: bit_set(features, 0x40),
active_off: bit_set(features, 0x20),
preferred_timing: bit_set(features, 0x02),
default_srgb: bit_set(features, 0x04),
}
}
///|
fn parse_chromaticity(data : Array[Int]) -> Chromaticity {
let lo1 = byte_at(data, 25)
let lo2 = byte_at(data, 26)
{
red_x: chroma_value(byte_at(data, 27), (lo1 >> 6) & 3),
red_y: chroma_value(byte_at(data, 28), (lo1 >> 4) & 3),
green_x: chroma_value(byte_at(data, 29), (lo1 >> 2) & 3),
green_y: chroma_value(byte_at(data, 30), lo1 & 3),
blue_x: chroma_value(byte_at(data, 31), (lo2 >> 6) & 3),
blue_y: chroma_value(byte_at(data, 32), (lo2 >> 4) & 3),
white_x: chroma_value(byte_at(data, 33), (lo2 >> 2) & 3),
white_y: chroma_value(byte_at(data, 34), lo2 & 3),
}
}
///|
fn chroma_value(high : Int, low : Int) -> Int {
(((high << 2) + low) * 10000 + 512) / 1024
}
///|
fn parse_established_timings(data : Array[Int]) -> Array[EstablishedTiming] {
[
established("720x400@70", 720, 400, 70, data, 35, 0x80),
established("720x400@88", 720, 400, 88, data, 35, 0x40),
established("640x480@60", 640, 480, 60, data, 35, 0x20),
established("640x480@67", 640, 480, 67, data, 35, 0x10),
established("640x480@72", 640, 480, 72, data, 35, 0x08),
established("640x480@75", 640, 480, 75, data, 35, 0x04),
established("800x600@56", 800, 600, 56, data, 35, 0x02),
established("800x600@60", 800, 600, 60, data, 35, 0x01),
established("800x600@72", 800, 600, 72, data, 36, 0x80),
established("800x600@75", 800, 600, 75, data, 36, 0x40),
established("832x624@75", 832, 624, 75, data, 36, 0x20),
established("1024x768@87i", 1024, 768, 87, data, 36, 0x10),
established("1024x768@60", 1024, 768, 60, data, 36, 0x08),
established("1024x768@70", 1024, 768, 70, data, 36, 0x04),
established("1024x768@75", 1024, 768, 75, data, 36, 0x02),
established("1280x1024@75", 1280, 1024, 75, data, 36, 0x01),
established("1152x870@75", 1152, 870, 75, data, 37, 0x80),
]
}
///|
fn established(
name : String,
width : Int,
height : Int,
refresh_hz : Int,
data : Array[Int],
byte_offset : Int,
bit_mask : Int,
) -> EstablishedTiming {
{
name,
width,
height,
refresh_hz,
enabled: bit_set(byte_at(data, byte_offset), bit_mask),
byte_offset,
bit_mask,
}
}
///|
fn parse_standard_timings(data : Array[Int]) -> Array[StandardTiming] {
let timings : Array[StandardTiming] = []
let mut offset = 38
while offset < 54 {
timings.push(parse_standard_timing(data, offset))
offset = offset + 2
}
timings
}
///|
fn parse_standard_timing(data : Array[Int], offset : Int) -> StandardTiming {
let b1 = byte_at(data, offset)
let b2 = byte_at(data, offset + 1)
if b1 == 0x01 && b2 == 0x01 {
{
width: 0,
height: 0,
refresh_hz: 0,
aspect: Ratio16_10,
byte_offset: offset,
valid: false,
}
} else {
let width = (b1 + 31) * 8
let aspect = aspect_ratio_from_code((b2 >> 6) & 3)
{
width,
height: standard_height(width, aspect),
refresh_hz: (b2 & 0x3F) + 60,
aspect,
byte_offset: offset,
valid: true,
}
}
}
///|
fn aspect_ratio_from_code(code : Int) -> AspectRatio {
match code {
0 => Ratio16_10
1 => Ratio4_3
2 => Ratio5_4
_ => Ratio16_9
}
}
///|
fn standard_height(width : Int, aspect : AspectRatio) -> Int {
match aspect {
Ratio16_10 => width * 10 / 16
Ratio4_3 => width * 3 / 4
Ratio5_4 => width * 4 / 5
Ratio16_9 => width * 9 / 16
}
}
///|
fn parse_descriptors(
data : Array[Int],
diagnostics : Array[EdidDiagnostic],
) -> Array[Descriptor] {
[
parse_descriptor(data, 54, 0, diagnostics),
parse_descriptor(data, 72, 1, diagnostics),
parse_descriptor(data, 90, 2, diagnostics),
parse_descriptor(data, 108, 3, diagnostics),
]
}
///|
fn parse_descriptor(
data : Array[Int],
offset : Int,
index : Int,
diagnostics : Array[EdidDiagnostic],
) -> Descriptor {
let pixel_clock_raw = u16_le(data, offset)
if pixel_clock_raw > 0 {
{
kind: DetailedTiming,
index,
text: None,
detailed_timing: Some(parse_detailed_timing(data, offset)),
range_limits: None,
}
} else if byte_at(data, offset + 2) == 0x00 {
parse_monitor_descriptor(data, offset, index, diagnostics)
} else {
diagnostics.push(
EdidDiagnostic::make(
UnknownDescriptor,
Warning,
"Descriptor has zero pixel clock but does not use monitor descriptor layout",
Some(offset),
),
)
{
kind: Unknown(byte_at(data, offset + 3)),
index,
text: None,
detailed_timing: None,
range_limits: None,
}
}
}
///|
fn parse_monitor_descriptor(
data : Array[Int],
offset : Int,
index : Int,
diagnostics : Array[EdidDiagnostic],
) -> Descriptor {
let tag = byte_at(data, offset + 3)
if tag == 0xFC {
text_descriptor(MonitorName, data, offset, index)
} else if tag == 0xFF {
text_descriptor(SerialNumberText, data, offset, index)
} else if tag == 0xFE {
text_descriptor(Text, data, offset, index)
} else if tag == 0xFD {
{
kind: RangeLimits,
index,
text: None,
detailed_timing: None,
range_limits: Some(parse_range_limits(data, offset)),
}
} else if tag == 0x00 {
{
kind: Empty,
index,
text: None,
detailed_timing: None,
range_limits: None,
}
} else {
diagnostics.push(
EdidDiagnostic::make(
UnknownDescriptor,
Note,
"Monitor descriptor tag is not decoded by edidkit",
Some(offset + 3),
),
)
{
kind: Unknown(tag),
index,
text: Some(descriptor_text(data, offset)),
detailed_timing: None,
range_limits: None,
}
}
}
///|
fn text_descriptor(
kind : DescriptorKind,
data : Array[Int],
offset : Int,
index : Int,
) -> Descriptor {
{
kind,
index,
text: Some(descriptor_text(data, offset)),
detailed_timing: None,
range_limits: None,
}
}
///|
fn parse_range_limits(data : Array[Int], offset : Int) -> RangeLimits {
{
min_vertical_hz: byte_at(data, offset + 5),
max_vertical_hz: byte_at(data, offset + 6),
min_horizontal_khz: byte_at(data, offset + 7),
max_horizontal_khz: byte_at(data, offset + 8),
max_pixel_clock_mhz: byte_at(data, offset + 9) * 10,
}
}
///|
fn parse_detailed_timing(data : Array[Int], offset : Int) -> DetailedTimingInfo {
let h_active = byte_at(data, offset + 2) +
(byte_at(data, offset + 4) >> 4 << 8)
let h_blanking = byte_at(data, offset + 3) +
((byte_at(data, offset + 4) & 0x0F) << 8)
let v_active = byte_at(data, offset + 5) +
(byte_at(data, offset + 7) >> 4 << 8)
let v_blanking = byte_at(data, offset + 6) +
((byte_at(data, offset + 7) & 0x0F) << 8)
let h_sync_offset = byte_at(data, offset + 8) +
(((byte_at(data, offset + 11) >> 6) & 3) << 8)
let h_sync_pulse = byte_at(data, offset + 9) +
(((byte_at(data, offset + 11) >> 4) & 3) << 8)
let v_sync_offset = ((byte_at(data, offset + 10) >> 4) & 0x0F) +
(((byte_at(data, offset + 11) >> 2) & 3) << 4)
let v_sync_pulse = (byte_at(data, offset + 10) & 0x0F) +
((byte_at(data, offset + 11) & 3) << 4)
let h_size_mm = byte_at(data, offset + 12) +
(byte_at(data, offset + 14) >> 4 << 8)
let v_size_mm = byte_at(data, offset + 13) +
((byte_at(data, offset + 14) & 0x0F) << 8)
let flags = byte_at(data, offset + 17)
{
pixel_clock_khz: u16_le(data, offset) * 10,
h_active,
h_blanking,
v_active,
v_blanking,
h_sync_offset,
h_sync_pulse,
v_sync_offset,
v_sync_pulse,
h_size_mm,
v_size_mm,
h_border: byte_at(data, offset + 15),
v_border: byte_at(data, offset + 16),
interlaced: bit_set(flags, 0x80),
positive_hsync: bit_set(flags, 0x02),
positive_vsync: bit_set(flags, 0x04),
}
}