namespace XFESystem.Desktop;
/// Trusted EDID metadata for one exact framebuffer resolution, not a mode switch.
public readonly record struct DisplayTimingResult(
bool IsValid,
bool IsComplete,
string FailureReason,
int Width,
int Height,
int AdvertisedRefreshMilliHz,
int PhysicalWidthMillimeters,
int PhysicalHeightMillimeters,
string Source,
int MatchedTimingCount,
int DeclaredExtensionBlocks,
int ValidatedExtensionBlocks)
{
public bool HasAdvertisedTiming => AdvertisedRefreshMilliHz > 0;
}
///
/// Bounded EDID 1.3/1.4, CTA detailed-timing and DisplayID 1.3 Type I decoding.
/// A monitor range limit is deliberately never treated as an advertised timing.
/// Valid base metadata survives missing or invalid extensions, which are reported
/// by IsComplete. Only independently validated extension blocks contribute modes.
///
public static class DisplayTiming
{
public const int BlockLength = 128;
public const int MaximumExtensionBlocks = 8;
public static DisplayTimingResult Read(ReadOnlySpan edid, int nativeWidth, int nativeHeight)
{
if (nativeWidth <= 0 || nativeHeight <= 0 || nativeWidth > 65536 || nativeHeight > 65536)
return Invalid("Invalid native framebuffer dimensions", nativeWidth, nativeHeight);
if (edid.Length < BlockLength)
return Invalid("EDID base block is missing or truncated", nativeWidth, nativeHeight);
ReadOnlySpan baseBlock = edid[..BlockLength];
ReadOnlySpan signature = [0, 255, 255, 255, 255, 255, 255, 0];
if (!baseBlock[..8].SequenceEqual(signature))
return Invalid("Invalid EDID signature", nativeWidth, nativeHeight);
if (!ChecksumIsValid(baseBlock))
return Invalid("Invalid EDID base checksum", nativeWidth, nativeHeight);
if (baseBlock[18] != 1 || baseBlock[19] is < 3 or > 4)
return Invalid("Unsupported EDID base version", nativeWidth, nativeHeight);
var timings = new TimingAccumulator(nativeWidth, nativeHeight);
if (baseBlock[21] != 0 && baseBlock[22] != 0)
{
timings.PhysicalWidth = baseBlock[21] * 10;
timings.PhysicalHeight = baseBlock[22] * 10;
}
for (int offset = 54; offset + 18 <= 126; offset += 18)
ReadDetailedTiming(baseBlock.Slice(offset, 18), "EDID base DTD", ref timings);
int declared = baseBlock[126];
int available = Math.Max(0, edid.Length / BlockLength - 1);
int bounded = Math.Min(declared, MaximumExtensionBlocks);
int validated = 0;
bool complete = declared <= MaximumExtensionBlocks && available >= declared;
string reason = declared > MaximumExtensionBlocks ? "EDID extension count exceeds parser bound" :
available < declared ? "One or more declared EDID extensions are missing" : "";
for (int index = 1; index <= bounded && index <= available; index++)
{
ReadOnlySpan extension = edid.Slice(index * BlockLength, BlockLength);
if (!ChecksumIsValid(extension))
{
complete = false;
reason = "One or more EDID extension checksums are invalid";
continue;
}
bool valid = extension[0] switch
{
0x02 => CtaIsValid(extension),
0x70 => DisplayIdIsValid(extension),
_ => true // Unknown tags contribute no timings; their block checksum is still checked.
};
if (!valid)
{
complete = false;
reason = "One or more EDID extensions have unsupported versions or malformed lengths";
continue;
}
validated++;
if (extension[0] == 0x02)
{
int offset = extension[2];
if (offset != 0)
for (; offset + 18 <= 127; offset += 18)
ReadDetailedTiming(extension.Slice(offset, 18), $"EDID CTA DTD (extension {index})", ref timings);
}
else if (extension[0] == 0x70)
ReadDisplayId(extension, index, ref timings);
}
return new(true, complete, reason, nativeWidth, nativeHeight, timings.MaximumMilliHz,
timings.PhysicalWidth, timings.PhysicalHeight,
timings.MaximumMilliHz > 0 ? timings.Source : "No advertised timing for the exact framebuffer resolution",
timings.Matches, declared, validated);
}
private static DisplayTimingResult Invalid(string reason, int width, int height) =>
new(false, false, reason, width, height, 0, 0, 0, "Unavailable", 0, 0, 0);
private static bool ChecksumIsValid(ReadOnlySpan bytes)
{
int sum = 0;
foreach (byte value in bytes) sum += value;
return (sum & 255) == 0;
}
private static bool CtaIsValid(ReadOnlySpan block)
{
if (block[1] is < 1 or > 3) return false;
int end = block[2];
if (end == 0) return true;
if (end < 4 || end > 127) return false;
for (int offset = 4; offset < end;)
{
int length = block[offset] & 31;
if (offset + 1 + length > end) return false;
offset += 1 + length;
}
return true;
}
private static bool DisplayIdIsValid(ReadOnlySpan block)
{
if (block[1] != 0x13) return false;
int payloadEnd = 5 + block[2];
if (payloadEnd > 126 || !ChecksumIsValid(block.Slice(1, payloadEnd))) return false;
for (int offset = 5; offset < payloadEnd;)
{
if (AllZero(block.Slice(offset, payloadEnd - offset))) break;
if (offset + 3 > payloadEnd) return false;
int length = block[offset + 2];
if (length == 0 || offset + 3 + length > payloadEnd) return false;
if (block[offset] == 0x03 &&
((block[offset + 1] & 0xF8) != 0 || (block[offset + 1] & 7) > 1 || length % 20 != 0))
return false;
offset += 3 + length;
}
return true;
}
private static bool AllZero(ReadOnlySpan bytes)
{
foreach (byte value in bytes) if (value != 0) return false;
return true;
}
private static void ReadDetailedTiming(ReadOnlySpan timing, string source, ref TimingAccumulator result)
{
int clockUnits = timing[0] | timing[1] << 8;
// Descriptor metadata (including 0xFD range limits) has a zero pixel clock.
// Interlaced timings are excluded: the desktop framebuffer is progressive.
if (clockUnits == 0 || (timing[17] & 0x80) != 0) return;
int width = timing[2] | (timing[4] & 0xF0) << 4;
int horizontalBlank = timing[3] | (timing[4] & 0x0F) << 8;
int height = timing[5] | (timing[7] & 0xF0) << 4;
int verticalBlank = timing[6] | (timing[7] & 0x0F) << 8;
int physicalWidth = timing[12] | (timing[14] & 0xF0) << 4;
int physicalHeight = timing[13] | (timing[14] & 0x0F) << 8;
result.Add(width, height, horizontalBlank, verticalBlank, clockUnits * 10000L,
source, physicalWidth, physicalHeight);
}
private static void ReadDisplayId(ReadOnlySpan block, int extensionIndex, ref TimingAccumulator result)
{
int payloadEnd = 5 + block[2];
for (int offset = 5; offset + 3 <= payloadEnd;)
{
int length = block[offset + 2];
if (length == 0) break;
if (block[offset] == 0x03)
{
for (int position = offset + 3; position < offset + 3 + length; position += 20)
{
ReadOnlySpan timing = block.Slice(position, 20);
if ((timing[3] & 0x10) != 0) continue; // Interlaced.
// DisplayID Type I fields encode values minus one, including pixel clock.
// QEMU v11.1.0 writes the direct clock count, introducing <1mHz quantization
// difference at 4K240; decoding follows the protocol for physical monitors.
long clockUnits = (timing[0] | timing[1] << 8 | timing[2] << 16) + 1L;
result.Add(LittleEndian16(timing, 4) + 1, LittleEndian16(timing, 12) + 1,
LittleEndian16(timing, 6) + 1, LittleEndian16(timing, 14) + 1,
clockUnits * 10000, $"EDID DisplayID 1.3 Type I (extension {extensionIndex})", 0, 0);
}
}
offset += 3 + length;
}
}
private static int LittleEndian16(ReadOnlySpan bytes, int offset) => bytes[offset] | bytes[offset + 1] << 8;
private struct TimingAccumulator(int requestedWidth, int requestedHeight)
{
public int MaximumMilliHz;
public int PhysicalWidth;
public int PhysicalHeight;
public int Matches;
public string Source = "";
public void Add(int width, int height, int horizontalBlank, int verticalBlank, long pixelClockHz,
string source, int physicalWidth, int physicalHeight)
{
if (width != requestedWidth || height != requestedHeight || horizontalBlank <= 0 || verticalBlank <= 0)
return;
long totalPixels = (width + (long)horizontalBlank) * (height + (long)verticalBlank);
long milliHz = (pixelClockHz * 1000 + totalPixels / 2) / totalPixels;
if (milliHz <= 0 || milliHz > int.MaxValue) return;
Matches++;
if (milliHz > MaximumMilliHz)
{
MaximumMilliHz = (int)milliHz;
Source = source;
}
if (physicalWidth > 0 && physicalHeight > 0)
{
PhysicalWidth = physicalWidth;
PhysicalHeight = physicalHeight;
}
}
}
}