display_types/capabilities.rs
1/// A reference-counted, type-erased warning value.
2///
3/// Any type that implements [`core::error::Error`] + [`Send`] + [`Sync`] + `'static` can be
4/// wrapped in a `ParseWarning`. The built-in library variants use `EdidWarning`, but
5/// custom handlers may push their own error types without wrapping them in `EdidWarning`.
6///
7/// Using [`Arc`][crate::prelude::Arc] (rather than `Box`) means `ParseWarning` is
8/// [`Clone`], which lets warnings be copied from a parsed representation into
9/// [`DisplayCapabilities`] without consuming the parsed result.
10///
11/// To inspect a specific variant, use the inherent `downcast_ref` method available on
12/// `dyn core::error::Error + Send + Sync + 'static` in `std` builds:
13///
14/// ```text
15/// for w in caps.iter_warnings() {
16/// if let Some(ew) = (**w).downcast_ref::<EdidWarning>() { ... }
17/// }
18/// ```
19#[cfg(any(feature = "alloc", feature = "std"))]
20pub type ParseWarning = crate::prelude::Arc<dyn core::error::Error + Send + Sync + 'static>;
21
22/// Stereo viewing support decoded from DTD byte 17 bits 6, 5, and 0.
23#[non_exhaustive]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub enum StereoMode {
27 /// Normal display; no stereo (bits 6–5 = `0b00`; bit 0 is don't-care).
28 #[default]
29 None,
30 /// Field-sequential stereo, right image when stereo sync = 1 (bits 6–5 = `0b01`, bit 0 = 0).
31 FieldSequentialRightFirst,
32 /// Field-sequential stereo, left image when stereo sync = 1 (bits 6–5 = `0b10`, bit 0 = 0).
33 FieldSequentialLeftFirst,
34 /// 2-way interleaved stereo, right image on even lines (bits 6–5 = `0b01`, bit 0 = 1).
35 TwoWayInterleavedRightEven,
36 /// 2-way interleaved stereo, left image on even lines (bits 6–5 = `0b10`, bit 0 = 1).
37 TwoWayInterleavedLeftEven,
38 /// 4-way interleaved stereo (bits 6–5 = `0b11`, bit 0 = 0).
39 FourWayInterleaved,
40 /// Side-by-side interleaved stereo (bits 6–5 = `0b11`, bit 0 = 1).
41 SideBySideInterleaved,
42}
43
44/// Sync signal definition decoded from DTD byte 17 bits 4–1.
45#[non_exhaustive]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum SyncDefinition {
49 /// Analog composite sync (bit 4 = 0, bit 3 = 0).
50 AnalogComposite {
51 /// H-sync pulse present during V-sync (serrations).
52 serrations: bool,
53 /// Sync on all three RGB signals (`true`) or green only (`false`).
54 sync_on_all_rgb: bool,
55 },
56 /// Bipolar analog composite sync (bit 4 = 0, bit 3 = 1).
57 BipolarAnalogComposite {
58 /// H-sync pulse present during V-sync (serrations).
59 serrations: bool,
60 /// Sync on all three RGB signals (`true`) or green only (`false`).
61 sync_on_all_rgb: bool,
62 },
63 /// Digital composite sync on H-sync pin (bit 4 = 1, bit 3 = 0).
64 DigitalComposite {
65 /// H-sync pulse present during V-sync (serrations).
66 serrations: bool,
67 /// H-sync polarity outside V-sync: `true` = positive.
68 h_sync_positive: bool,
69 },
70 /// Digital separate sync (bit 4 = 1, bit 3 = 1).
71 DigitalSeparate {
72 /// V-sync polarity: `true` = positive.
73 v_sync_positive: bool,
74 /// H-sync polarity: `true` = positive.
75 h_sync_positive: bool,
76 },
77}
78
79/// The source from which a [`VideoMode`] was decoded.
80///
81/// Populated automatically by [`vic_to_mode`][crate::cea861::vic_to_mode] and
82/// [`dmt_to_mode`][crate::cea861::dmt_to_mode]; parsers that decode Detailed Timing
83/// Descriptors should set it via [`VideoMode::with_source`]. `None` for modes
84/// constructed directly via [`VideoMode::new`].
85#[non_exhaustive]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum ModeSource {
89 /// A CTA-861 Video Identification Code, as used in Short Video Descriptors,
90 /// the Y420 Video Data Block, and the Y420 Capability Map Data Block.
91 Vic(u8),
92 /// A VESA Display Monitor Timings identifier (0x01–0x58).
93 DmtId(u16),
94 /// Zero-based index of a Detailed Timing Descriptor within its containing EDID block.
95 DtdIndex(u8),
96}
97
98/// A display video mode expressed as resolution, refresh rate, and scan type.
99///
100/// Use [`VideoMode::new`] to construct a mode with only identity fields (the common case
101/// for modes decoded from standard timing or SVD entries). Use
102/// [`VideoMode::with_detailed_timing`] to add the blanking-interval and signal fields
103/// available from a Detailed Timing Descriptor or equivalent.
104#[non_exhaustive]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106#[derive(Debug, Clone, PartialEq, Default)]
107pub struct VideoMode {
108 /// Horizontal resolution in pixels.
109 pub width: u16,
110 /// Vertical resolution in pixels.
111 pub height: u16,
112 /// Refresh rate in Hz.
113 pub refresh_rate: u16,
114 /// `true` for interlaced modes; `false` for progressive (the common case).
115 pub interlaced: bool,
116 /// Horizontal front porch in pixels (0 when not decoded from a DTD).
117 pub h_front_porch: u16,
118 /// Horizontal sync pulse width in pixels (0 when not decoded from a DTD).
119 pub h_sync_width: u16,
120 /// Vertical front porch in lines (0 when not decoded from a DTD).
121 pub v_front_porch: u16,
122 /// Vertical sync pulse width in lines (0 when not decoded from a DTD).
123 pub v_sync_width: u16,
124 /// Horizontal border width in pixels on each side of the active area (0 when not from a DTD).
125 pub h_border: u8,
126 /// Vertical border height in lines on each side of the active area (0 when not from a DTD).
127 pub v_border: u8,
128 /// Stereo viewing support (default [`StereoMode::None`] for non-DTD modes).
129 pub stereo: StereoMode,
130 /// Sync signal definition (`None` for non-DTD modes).
131 pub sync: Option<SyncDefinition>,
132 /// Pixel clock in kHz (`None` for modes not decoded from a Detailed Timing Descriptor).
133 pub pixel_clock_khz: Option<u32>,
134 /// The source from which this mode was decoded, if known.
135 ///
136 /// `None` for modes constructed directly via [`VideoMode::new`] without a table lookup.
137 pub source: Option<ModeSource>,
138}
139
140impl VideoMode {
141 /// Constructs a `VideoMode` with the given identity fields.
142 ///
143 /// All blanking-interval fields (`h_front_porch`, `h_sync_width`, `v_front_porch`,
144 /// `v_sync_width`, `h_border`, `v_border`) default to `0`, `stereo` defaults to
145 /// [`StereoMode::None`], and `sync` defaults to `None`. Use
146 /// [`with_detailed_timing`][Self::with_detailed_timing] to set those fields when
147 /// decoding from a Detailed Timing Descriptor.
148 pub fn new(width: u16, height: u16, refresh_rate: u16, interlaced: bool) -> Self {
149 Self {
150 width,
151 height,
152 refresh_rate,
153 interlaced,
154 ..Self::default()
155 }
156 }
157
158 /// Sets the exact pixel clock in kHz, returning the updated mode.
159 ///
160 /// Use this when constructing a [`VideoMode`] from hardware timing registers or a
161 /// known-good mode table entry, where the exact pixel clock is available but full
162 /// Detailed Timing Descriptor fields are not. The supplied clock is returned verbatim
163 /// by [`pixel_clock_khz`][crate::pixel_clock_khz], bypassing the CVT-RB fallback
164 /// estimate.
165 ///
166 /// ```
167 /// use display_types::VideoMode;
168 /// use display_types::pixel_clock_khz;
169 ///
170 /// // Custom panel: 1920×1200 @ 60 Hz, exact pixel clock from PLL register.
171 /// let mode = VideoMode::new(1920, 1200, 60, false).with_pixel_clock(154_000);
172 /// assert_eq!(pixel_clock_khz(&mode), 154_000);
173 /// ```
174 pub fn with_pixel_clock(mut self, pixel_clock_khz: u32) -> Self {
175 self.pixel_clock_khz = Some(pixel_clock_khz);
176 self
177 }
178
179 /// Sets the mode source, returning the updated mode.
180 ///
181 /// Called automatically by [`vic_to_mode`][crate::cea861::vic_to_mode] and
182 /// [`dmt_to_mode`][crate::cea861::dmt_to_mode]. Parsers decoding Detailed Timing
183 /// Descriptors should call `.with_source(ModeSource::DtdIndex(n))` so that the
184 /// descriptor's position survives into negotiated output.
185 pub fn with_source(mut self, source: ModeSource) -> Self {
186 self.source = Some(source);
187 self
188 }
189
190 /// Adds blanking-interval and signal fields decoded from a Detailed Timing Descriptor
191 /// or equivalent source, returning the updated mode.
192 ///
193 /// The 9-parameter count mirrors the DTD fields directly (EDID §3.10.3 / DisplayID §4.4).
194 #[allow(clippy::too_many_arguments)]
195 pub fn with_detailed_timing(
196 mut self,
197 pixel_clock_khz: u32,
198 h_front_porch: u16,
199 h_sync_width: u16,
200 v_front_porch: u16,
201 v_sync_width: u16,
202 h_border: u8,
203 v_border: u8,
204 stereo: StereoMode,
205 sync: Option<SyncDefinition>,
206 ) -> Self {
207 self.pixel_clock_khz = Some(pixel_clock_khz);
208 self.h_front_porch = h_front_porch;
209 self.h_sync_width = h_sync_width;
210 self.v_front_porch = v_front_porch;
211 self.v_sync_width = v_sync_width;
212 self.h_border = h_border;
213 self.v_border = v_border;
214 self.stereo = stereo;
215 self.sync = sync;
216 self
217 }
218}
219
220/// EDID specification version and revision, decoded from base block bytes 18–19.
221///
222/// Most displays in use report version 1 with revision 3 or 4.
223#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub struct EdidVersion {
226 /// EDID version number (byte 18). Always `1` for all current displays.
227 pub version: u8,
228 /// EDID revision number (byte 19).
229 pub revision: u8,
230}
231
232impl core::fmt::Display for EdidVersion {
233 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
234 write!(f, "{}.{}", self.version, self.revision)
235 }
236}
237
238/// Trait for typed data stored in [`DisplayCapabilities::extension_data`] by custom handlers.
239///
240/// A blanket implementation covers any type that is `Any + Debug + Send + Sync`, so consumers
241/// do not need to implement this trait manually — `#[derive(Debug)]` on a `Send + Sync` type
242/// is sufficient.
243#[cfg(any(feature = "alloc", feature = "std"))]
244pub trait ExtensionData: core::any::Any + core::fmt::Debug + Send + Sync {
245 /// Returns `self` as `&dyn Any` to enable downcasting.
246 fn as_any(&self) -> &dyn core::any::Any;
247}
248
249#[cfg(any(feature = "alloc", feature = "std"))]
250impl<T: core::any::Any + core::fmt::Debug + Send + Sync> ExtensionData for T {
251 fn as_any(&self) -> &dyn core::any::Any {
252 self
253 }
254}
255
256/// Consumer-facing display capability model produced by a display data parser.
257///
258/// All fields defined by the relevant specification are decoded and exposed here.
259/// No field is omitted because it appears obscure or unlikely to be needed — that
260/// judgement belongs to the consumer, not the library.
261///
262/// Fields are `Option` where the underlying data may be absent or undecodable.
263/// `None` means the value was not present or could not be reliably determined; it does
264/// not imply the field is unimportant. The library never invents or defaults data.
265#[non_exhaustive]
266#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
267#[derive(Debug, Clone, Default)]
268pub struct DisplayCapabilities {
269 /// Three-character PNP manufacturer ID (e.g. `GSM` for LG, `SAM` for Samsung).
270 pub manufacturer: Option<crate::manufacture::ManufacturerId>,
271 /// Manufacture date or model year.
272 pub manufacture_date: Option<crate::manufacture::ManufactureDate>,
273 /// EDID specification version and revision.
274 pub edid_version: Option<EdidVersion>,
275 /// Manufacturer-assigned product code.
276 pub product_code: Option<u16>,
277 /// Manufacturer-assigned serial number, if encoded numerically in the base block.
278 pub serial_number: Option<u32>,
279 /// Serial number string from the monitor serial number descriptor (`0xFF`), if present.
280 pub serial_number_string: Option<crate::manufacture::MonitorString>,
281 /// Human-readable display name from the monitor name descriptor, if present.
282 pub display_name: Option<crate::manufacture::MonitorString>,
283 /// Unspecified ASCII text strings from `0xFE` descriptors, in descriptor slot order.
284 ///
285 /// Up to four entries (one per descriptor slot). Each slot is `None` if the corresponding
286 /// descriptor was not a `0xFE` entry.
287 pub unspecified_text: [Option<crate::manufacture::MonitorString>; 4],
288 /// Additional white points from the `0xFB` descriptor.
289 ///
290 /// Up to two entries (the EDID `0xFB` descriptor has two fixed slots). Each slot is
291 /// `None` if the corresponding entry was unused (index byte `0x00`).
292 pub white_points: [Option<crate::color::WhitePoint>; 2],
293 /// `true` if the display uses a digital input interface.
294 pub digital: bool,
295 /// Color bit depth per primary channel.
296 /// `None` for analog displays or when the field is undefined or reserved.
297 pub color_bit_depth: Option<crate::color::ColorBitDepth>,
298 /// Physical display technology (e.g. TFT, OLED, PDP).
299 /// `None` when the Display Device Data Block is absent.
300 pub display_technology: Option<crate::panel::DisplayTechnology>,
301 /// Technology-specific sub-type code (raw, 0–15).
302 /// `None` when the Display Device Data Block is absent.
303 pub display_subtype: Option<u8>,
304 /// Panel operating mode (continuous or non-continuous refresh).
305 /// `None` when the Display Device Data Block is absent.
306 pub operating_mode: Option<crate::panel::OperatingMode>,
307 /// Backlight type.
308 /// `None` when the Display Device Data Block is absent.
309 pub backlight_type: Option<crate::panel::BacklightType>,
310 /// Whether the panel uses a Data Enable (DE) signal.
311 /// `None` when the Display Device Data Block is absent.
312 pub data_enable_used: Option<bool>,
313 /// Data Enable signal polarity: `true` = positive, `false` = negative.
314 /// Valid only when `data_enable_used` is `Some(true)`.
315 /// `None` when the Display Device Data Block is absent.
316 pub data_enable_positive: Option<bool>,
317 /// Native pixel format `(width_px, height_px)`.
318 /// `None` when the Display Device Data Block is absent or either dimension is zero.
319 pub native_pixels: Option<(u16, u16)>,
320 /// Panel aspect ratio encoded as `(AR − 1) × 100` (raw byte).
321 /// For example `77` represents approximately 16:9 (AR ≈ 1.77). `None` when the block is absent.
322 pub panel_aspect_ratio_100: Option<u8>,
323 /// Physical mounting orientation of the panel.
324 /// `None` when the Display Device Data Block is absent.
325 pub physical_orientation: Option<crate::panel::PhysicalOrientation>,
326 /// Panel rotation capability.
327 /// `None` when the Display Device Data Block is absent.
328 pub rotation_capability: Option<crate::panel::RotationCapability>,
329 /// Location of the zero (origin) pixel in the framebuffer.
330 /// `None` when the Display Device Data Block is absent.
331 pub zero_pixel_location: Option<crate::panel::ZeroPixelLocation>,
332 /// Fast-scan direction relative to H-sync.
333 /// `None` when the Display Device Data Block is absent.
334 pub scan_direction: Option<crate::panel::ScanDirection>,
335 /// Sub-pixel color filter arrangement.
336 /// `None` when the Display Device Data Block is absent.
337 pub subpixel_layout: Option<crate::panel::SubpixelLayout>,
338 /// Pixel pitch `(horizontal_hundredths_mm, vertical_hundredths_mm)` in 0.01 mm units.
339 /// `None` when the Display Device Data Block is absent or either pitch is zero.
340 pub pixel_pitch_hundredths_mm: Option<(u8, u8)>,
341 /// Pixel response time in milliseconds.
342 /// `None` when the Display Device Data Block is absent or the value is zero.
343 pub pixel_response_time_ms: Option<u8>,
344 /// Interface power sequencing timing parameters.
345 /// `None` when the Interface Power Sequencing Block is absent.
346 pub power_sequencing: Option<crate::panel::PowerSequencing>,
347 /// Display luminance transfer function.
348 /// `None` when the Transfer Characteristics Block is absent.
349 #[cfg(any(feature = "alloc", feature = "std"))]
350 pub transfer_characteristic: Option<crate::transfer::DisplayIdTransferCharacteristic>,
351 /// Physical display interface capabilities.
352 /// `None` when the Display Interface Data Block is absent.
353 pub display_id_interface: Option<crate::panel::DisplayIdInterface>,
354 /// Stereo display interface parameters.
355 /// `None` when the Stereo Display Interface Data Block is absent.
356 pub stereo_interface: Option<crate::panel::DisplayIdStereoInterface>,
357 /// Tiled display topology.
358 /// `None` when the Tiled Display Topology Data Block is absent.
359 pub tiled_topology: Option<crate::panel::DisplayIdTiledTopology>,
360 /// CIE xy chromaticity coordinates for the color primaries and white point.
361 pub chromaticity: crate::color::Chromaticity,
362 /// Display gamma. `None` if the display did not specify a gamma value.
363 pub gamma: Option<crate::color::DisplayGamma>,
364 /// Display feature support flags.
365 pub display_features: Option<crate::features::DisplayFeatureFlags>,
366 /// Supported color encoding formats. Only populated for EDID 1.4+ digital displays.
367 pub digital_color_encoding: Option<crate::color::DigitalColorEncoding>,
368 /// Color type for analog displays; `None` for the undefined value (`0b11`).
369 pub analog_color_type: Option<crate::color::AnalogColorType>,
370 /// Video interface type.
371 /// `None` for analog displays or when the field is undefined or reserved.
372 pub video_interface: Option<crate::input::VideoInterface>,
373 /// Analog sync and video white levels. Only populated for analog displays.
374 pub analog_sync_level: Option<crate::input::AnalogSyncLevel>,
375 /// Physical screen dimensions or aspect ratio.
376 /// `None` when both bytes are zero (undefined).
377 pub screen_size: Option<crate::screen::ScreenSize>,
378 /// Minimum supported vertical refresh rate in Hz.
379 pub min_v_rate: Option<u16>,
380 /// Maximum supported vertical refresh rate in Hz.
381 pub max_v_rate: Option<u16>,
382 /// Minimum supported horizontal scan rate in kHz.
383 pub min_h_rate_khz: Option<u16>,
384 /// Maximum supported horizontal scan rate in kHz.
385 pub max_h_rate_khz: Option<u16>,
386 /// Maximum pixel clock in MHz.
387 pub max_pixel_clock_mhz: Option<u16>,
388 /// Physical image area dimensions in millimetres `(width_mm, height_mm)`.
389 ///
390 /// More precise than [`screen_size`][Self::screen_size] (which is in cm).
391 /// `None` when all DTD image-size fields are zero.
392 pub preferred_image_size_mm: Option<(u16, u16)>,
393 /// Video timing formula reported in the display range limits descriptor.
394 pub timing_formula: Option<crate::timing::TimingFormula>,
395 /// DCM polynomial coefficients.
396 pub color_management: Option<crate::color::ColorManagementData>,
397 /// Video modes decoded from the display data.
398 #[cfg(any(feature = "alloc", feature = "std"))]
399 pub supported_modes: crate::prelude::Vec<VideoMode>,
400 /// Non-fatal conditions collected from the parser and all handlers.
401 ///
402 /// Not serialized — use a custom handler to map warnings to a serializable form.
403 #[cfg(any(feature = "alloc", feature = "std"))]
404 #[cfg_attr(feature = "serde", serde(skip))]
405 pub warnings: crate::prelude::Vec<ParseWarning>,
406 /// Typed data attached by extension handlers, keyed by extension tag byte.
407 ///
408 /// Uses a `Vec` of `(tag, data)` pairs rather than a `HashMap` so that this field is
409 /// available in `alloc`-only (no_std) builds. The number of distinct extension tags in
410 /// any real EDID is small enough that linear scan is negligible.
411 ///
412 /// Not serialized — use a custom handler to map this to a serializable form.
413 #[cfg(any(feature = "alloc", feature = "std"))]
414 #[cfg_attr(feature = "serde", serde(skip))]
415 pub extension_data: crate::prelude::Vec<(u8, crate::prelude::Arc<dyn ExtensionData>)>,
416}
417
418#[cfg(any(feature = "alloc", feature = "std"))]
419impl DisplayCapabilities {
420 /// Returns an iterator over all collected warnings.
421 pub fn iter_warnings(&self) -> impl Iterator<Item = &ParseWarning> {
422 self.warnings.iter()
423 }
424
425 /// Appends a warning, wrapping it in a [`ParseWarning`].
426 pub fn push_warning(&mut self, w: impl core::error::Error + Send + Sync + 'static) {
427 self.warnings.push(crate::prelude::Arc::new(w));
428 }
429
430 /// Store typed data from a handler, keyed by an extension tag.
431 /// Replaces any previously stored entry for the same tag.
432 pub fn set_extension_data<T: ExtensionData>(&mut self, tag: u8, data: T) {
433 if let Some(entry) = self.extension_data.iter_mut().find(|(t, _)| *t == tag) {
434 entry.1 = crate::prelude::Arc::new(data);
435 } else {
436 self.extension_data
437 .push((tag, crate::prelude::Arc::new(data)));
438 }
439 }
440
441 /// Retrieve typed data previously stored by a handler for the given tag.
442 /// Returns `None` if no data is stored for the tag or the type does not match.
443 pub fn get_extension_data<T: core::any::Any>(&self, tag: u8) -> Option<&T> {
444 self.extension_data
445 .iter()
446 .find(|(t, _)| *t == tag)
447 // `**data` deref-chains through `&` then through Arc's Deref to reach
448 // `dyn ExtensionData`, forcing vtable dispatch for `as_any()`.
449 // Calling `.as_any()` on `&Arc<dyn ExtensionData>` would hit the blanket
450 // `ExtensionData` impl for Arc itself and return the wrong TypeId.
451 .and_then(|(_, data)| (**data).as_any().downcast_ref::<T>())
452 }
453}