Piaf
A Rust library for decoding binary capability data into a clean, typed model, specialized for EDID.
Install / Use
/learn @DracoWhitefire/PiafREADME
PIAF
A Rust library for decoding binary capability data into a clean, typed model, specialized for EDID.
PIAF reads raw EDID bytes — from a file, a kernel sysfs node, or a direct I²C read —
and produces a DisplayCapabilities value with all the information a display or
HDMI-adjacent application typically needs: identity, input type, supported modes,
color characteristics, HDR metadata, audio capabilities, and more.
Decoding happens in two steps. parse_edid validates the raw bytes and returns a
ParsedEdidRef<'_> — a zero-copy view that borrows the block structure directly from the
input slice. capabilities_from_edid then runs the registered extension handlers over that
structure and produces a DisplayCapabilities — the typed, stable model your application
works with. Keeping these steps separate means you can inspect the raw parse result for
debugging, or run multiple handler configurations over the same parsed data without re-parsing.
Use parse_edid_owned to get an owned ParsedEdid that can outlive the input buffer.
use piaf::{parse_edid, capabilities_from_edid, ExtensionLibrary, ScreenSize};
let bytes = std::fs::read("/sys/class/drm/card0-HDMI-A-1/edid")?;
let library = ExtensionLibrary::with_standard_handlers();
let parsed = parse_edid(&bytes, &library)?;
let caps = capabilities_from_edid(&parsed, &library);
println!("Display: {}", caps.display_name.as_deref().unwrap_or("unknown"));
if let Some(ScreenSize::Physical { width_cm, height_cm }) = caps.screen_size {
println!("{}×{} cm", width_cm, height_cm);
}
for mode in &caps.supported_modes {
println!(" {}×{}@{}", mode.width, mode.height, mode.refresh_rate);
}
See examples/inspect_displays.rs for a more complete example.
flowchart LR
bytes["&[u8]"]
ref["ParsedEdidRef<'_>"]
bytes -->|"parse_edid"| ref
ref -->|"capabilities_from_edid\n+ ExtensionLibrary"| dc["DisplayCapabilities\nalloc / std"]
ref -->|"capabilities_from_edid_static\n+ STANDARD_HANDLERS"| sc["StaticDisplayCapabilities<N>\nall tiers"]
Why PIAF
Complete extension coverage. Most EDID libraries decode the base block and stop. PIAF decodes 20+ CEA-861 data block types — HDR static and dynamic metadata, HDMI 1.x and HDMI Forum VSDBs, colorimetry, speaker allocation, video timing blocks, and the HDMI Forum Sink Capability block — and all 20 defined DisplayID 1.x block types, covering panel identity, color characteristics, device data, power sequencing, transfer characteristics, and every defined timing format. If the information is in the EDID, PIAF exposes it as typed fields rather than raw bytes.
Pluggable handlers. The extension handler system lets you register your own handler
for any extension block tag — override either built-in handler (CEA-861 or DisplayID),
add support for a proprietary block, or attach typed custom data to DisplayCapabilities
for downstream consumers. Base block parsing is pluggable too.
Honest diagnostics. PIAF distinguishes between hard parse errors (invalid header, checksum failure, truncated input) and non-fatal warnings (unknown extension block, malformed data block, out-of-range manufacturer ID). You decide how strict to be; nothing is silently discarded.
no_std support. The library runs on bare metal. The static extension handler
pipeline — capabilities_from_edid_static with STANDARD_HANDLERS — works at all
build tiers, including bare no_std without an allocator. Custom handlers implement
StaticExtensionHandler using static references instead of Box — see
no_std builds below.
Stable consumer model. ParsedEdidRef and ParsedEdid preserve raw bytes;
DisplayCapabilities is the typed, stable output. Both implement EdidSource and work
directly with the capability pipelines. Parser improvements don't change the consumer-facing API.
Extension system
Cea861Handler covers the common case. Write your own handler to support a proprietary
extension block, augment CEA-861 decoding with application-specific logic, or attach typed
custom data to DisplayCapabilities for downstream consumers.
Dynamic handlers (std/alloc)
Register via ExtensionLibrary. Uses Box<dyn ExtensionHandler> internally, so requires
heap allocation:
use piaf::{ExtensionHandler, DisplayCapabilities, ParseWarning, ExtensionLibrary};
#[derive(Debug)]
struct MyHandler;
impl ExtensionHandler for MyHandler {
fn process(&self, blocks: &[&[u8; 128]], caps: &mut DisplayCapabilities, warnings: &mut Vec<ParseWarning>) {
// inspect blocks, set fields on caps
}
}
let mut library = ExtensionLibrary::new();
library.register(ExtensionMetadata {
tag: 0xAB,
display_name: String::from("My Extension"),
handler: Some(Box::new(MyHandler)),
});
Typed data can be attached to DisplayCapabilities and retrieved by tag:
caps.set_extension_data(0xAB, MyCeaData { version: block[1] });
if let Some(data) = caps.get_extension_data::<MyCeaData>(0xAB) {
println!("version: {}", data.version);
}
Static handlers (no-alloc)
Use StaticExtensionHandler when heap allocation is unavailable. Pass a static slice
to capabilities_from_edid_static:
use piaf::{StaticExtensionHandler, ModeSink, StaticDisplayCapabilities, STANDARD_HANDLERS};
struct MyHandler;
impl StaticExtensionHandler for MyHandler {
fn tag(&self) -> u8 { 0xAB }
fn process(&self, blocks: &[&[u8; 128]], ctx: &mut StaticContext<'_>) {
// push modes via ctx.push_mode(...) and warnings via ctx.push_warning(...)
}
}
static MY_HANDLER: MyHandler = MyHandler;
static HANDLERS: &[&dyn StaticExtensionHandler] = &[STANDARD_HANDLERS[0], &MY_HANDLER];
let caps: StaticDisplayCapabilities<64> =
piaf::capabilities_from_edid_static(&parsed, HANDLERS);
Static handlers extract modes only — audio, VSDB, colorimetry, and similar rich metadata require the dynamic pipeline.
Features
| Feature | Default | Description |
|---------|---------|----------------------------------------------------------|
| std | yes | Enables std-backed types and the full extension system |
| alloc | no | Enables dynamic allocation without std |
| serde | no | Derives Serialize/Deserialize on public types |
All output types (DisplayCapabilities, VideoMode, ManufacturerId, etc.) are defined in
the display-types crate and re-exported from
piaf. Importing from piaf::* is sufficient; adding display-types as a direct dependency
is only needed if you want to use the types in an API shared between piaf and other crates.
no_std builds
Bare no_std (neither std nor alloc) is supported. The dynamic extension handler
pipeline (ExtensionLibrary, capabilities_from_edid) requires alloc or std.
The static pipeline (capabilities_from_edid_static) is available unconditionally.
In bare no_std, parse_edid returns a ParsedEdidRef<'_> that borrows extension blocks
directly from the input slice — no allocator needed. Both base-block fields and extension-block
modes are available through capabilities_from_edid_static at all build tiers.
parse_edid_owned returns a ParsedEdid that copies block bytes into owned storage; in bare
no_std the extension block field is absent (alloc-gated), so prefer ParsedEdidRef from
parse_edid when extension block access matters.
Fields in DisplayCapabilities available in all build configurations
| Field | Type |
|-------------------------------------|-------------------------------------------------------------|
| manufacturer | Option<ManufacturerId> |
| manufacture_date | Option<ManufactureDate> |
| edid_version | Option<EdidVersion> |
| product_code | Option<u16> |
| serial_number | Option<u32> |
| serial_number_string | Option<MonitorString> |
| display_name | Option<MonitorString> |
| unspecified_text | [Option<MonitorString>; 4] |
| white_points | [Option<WhitePoint>; 2] |
| digital | bool |
| color_bit_depth | Option<ColorBitDepth> |
| video_interface | Option<VideoInterface> |
| analog_sync_level | Option<AnalogSyncLevel> |
| chromaticity | Chromaticity |
| gamma | Option<DisplayGamma> |
| display_features | `Optio
