From 1f71290dab4a2fa4b12a75266699161e209c2341 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 4 Apr 2022 00:28:58 +0200 Subject: [PATCH] autocommit 2022-04-04 00:28:58 CEST --- src/backend.rs | 21 +- src/backends/dummy.rs | 16 +- src/backends/metal/monitor.rs | 1 + src/backends/metal/video.rs | 45 +- src/backends/x.rs | 39 +- src/compositor.rs | 2 +- src/drm/drm.rs | 32 + src/edid.rs | 1059 +++++++++++++++++++ src/ifs/wl_output.rs | 51 +- src/ifs/wl_surface/xdg_surface/xdg_popup.rs | 2 +- src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 2 +- src/ifs/zwlr_layer_shell_v1.rs | 2 +- src/main.rs | 1 + src/render/renderer/renderer.rs | 2 +- src/state.rs | 2 +- src/tasks/backend.rs | 6 +- src/tasks/output.rs | 32 +- src/tree.rs | 2 +- src/tree/output.rs | 20 +- src/utils/stack.rs | 11 +- src/utils/syncqueue.rs | 1 + 21 files changed, 1217 insertions(+), 132 deletions(-) create mode 100644 src/edid.rs diff --git a/src/backend.rs b/src/backend.rs index b0cd5f41..aa99f406 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -9,14 +9,25 @@ pub trait Backend { fn switch_to(&self, vtnr: u32); } -pub trait Output { +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct Mode { + pub width: i32, + pub height: i32, + pub refresh_rate: u32, +} + +pub trait Connector { fn id(&self) -> OutputId; - fn removed(&self) -> bool; - fn width(&self) -> i32; - fn height(&self) -> i32; + fn event(&self) -> Option; fn on_change(&self, cb: Rc); } +#[derive(Debug)] +pub enum ConnectorEvent { + Removed, + ModeChanged(Mode), +} + pub trait InputDevice { fn id(&self) -> InputDeviceId; fn removed(&self) -> bool; @@ -49,7 +60,7 @@ pub enum InputDeviceAccelProfile { } pub enum BackendEvent { - NewOutput(Rc), + NewConnector(Rc), NewInputDevice(Rc), } diff --git a/src/backends/dummy.rs b/src/backends/dummy.rs index a5f1a6dd..b21dace8 100644 --- a/src/backends/dummy.rs +++ b/src/backends/dummy.rs @@ -1,4 +1,4 @@ -use crate::backend::{Backend, Output, OutputId}; +use crate::backend::{Backend, Connector, ConnectorEvent, OutputId}; use std::rc::Rc; pub struct DummyBackend {} @@ -13,21 +13,13 @@ pub struct DummyOutput { pub id: OutputId, } -impl Output for DummyOutput { +impl Connector for DummyOutput { fn id(&self) -> OutputId { self.id } - fn removed(&self) -> bool { - false - } - - fn width(&self) -> i32 { - 100 - } - - fn height(&self) -> i32 { - 100 + fn event(&self) -> Option { + None } fn on_change(&self, _cb: Rc) { diff --git a/src/backends/metal/monitor.rs b/src/backends/metal/monitor.rs index a6223347..0c0472f3 100644 --- a/src/backends/metal/monitor.rs +++ b/src/backends/metal/monitor.rs @@ -232,6 +232,7 @@ impl MetalBackend { } fn handle_drm_change(self: &Rc, _dev: UdevDevice) -> Option<()> { + // TODO: Handle monitor connections and connector hotplug None } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 5e7eea46..6dda3c12 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,5 +1,5 @@ use crate::async_engine::{AsyncFd, SpawnedFuture}; -use crate::backend::{BackendEvent, Output, OutputId}; +use crate::backend::{BackendEvent, Connector, ConnectorEvent, Mode, OutputId}; use crate::backends::metal::{DrmId, MetalBackend, MetalError}; use crate::drm::drm::{ drm_mode_modeinfo, Change, ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, DrmCrtc, @@ -24,6 +24,7 @@ use std::ffi::CString; use std::fmt::{Debug, Formatter}; use std::rc::Rc; use uapi::c; +use crate::utils::syncqueue::SyncQueue; pub struct PendingDrmDevice { pub id: DrmId, @@ -77,6 +78,8 @@ pub struct MetalConnector { pub modes: Vec, pub mode: CloneCell>>, + pub events: SyncQueue, + pub buffers: CloneCell>>, pub next_buffer: NumCell, @@ -110,27 +113,13 @@ impl Debug for OnChange { } } -impl Output for MetalConnector { +impl Connector for MetalConnector { fn id(&self) -> OutputId { self.output_id } - fn removed(&self) -> bool { - false - } - - fn width(&self) -> i32 { - match self.mode.get() { - Some(m) => m.hdisplay as _, - _ => 0, - } - } - - fn height(&self) -> i32 { - match self.mode.get() { - Some(m) => m.vdisplay as _, - _ => 0, - } + fn event(&self) -> Option { + self.events.pop() } fn on_change(&self, cb: Rc) { @@ -223,12 +212,22 @@ fn create_connector( } } let props = collect_properties(&dev.master, connector)?; + let mode = info.modes.first().cloned().map(Rc::new); + let events = SyncQueue::default(); + if let Some(mode) = &mode { + events.push(ConnectorEvent::ModeChanged(Mode { + width: mode.hdisplay as _, + height: mode.vdisplay as _, + refresh_rate: mode.refresh_rate(), + })); + } Ok(MetalConnector { id: connector, master: dev.master.clone(), output_id: state.output_ids.next(), crtcs, - mode: CloneCell::new(info.modes.first().cloned().map(Rc::new)), + mode: CloneCell::new(mode), + events, modes: info.modes, buffers: Default::default(), next_buffer: Default::default(), @@ -486,6 +485,9 @@ impl MetalBackend { self.init_drm_device(&slf)?; for connector in slf.connectors.values() { + self.state + .backend_events + .push(BackendEvent::NewConnector(connector.clone())); if connector.primary_plane.get().is_some() { self.start_connector(connector); } @@ -880,9 +882,6 @@ impl MetalBackend { fn start_connector(&self, connector: &Rc) { let mode = connector.mode.get().unwrap(); - self.state - .backend_events - .push(BackendEvent::NewOutput(connector.clone())); log::info!( "Initialized connector {}-{} with mode {:?}", connector.connector_type, @@ -905,7 +904,7 @@ impl MetalBackend { if let Some(node) = self.state.root.outputs.get(&connector.output_id) { buffer .egl - .render(&*node, &self.state, Some(node.position.get())); + .render(&*node, &self.state, Some(node.global.pos.get())); } let mut changes = connector.master.change(); changes.change_object(plane.id, |c| { diff --git a/src/backends/x.rs b/src/backends/x.rs index 72787beb..6a8d13eb 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -1,8 +1,5 @@ use crate::async_engine::{Phase, SpawnedFuture}; -use crate::backend::{ - Backend, BackendEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, - InputDeviceId, InputEvent, KeyState, Output, OutputId, ScrollAxis, -}; +use crate::backend::{Backend, BackendEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent, KeyState, Connector, OutputId, ScrollAxis, Mode, ConnectorEvent}; use crate::drm::drm::{Drm, DrmError}; use crate::drm::gbm::{GbmDevice, GbmError, GBM_BO_USE_RENDERING}; use crate::drm::{ModifiedFormat, INVALID_MODIFIER}; @@ -42,6 +39,7 @@ use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::rc::Rc; use thiserror::Error; +use crate::utils::syncqueue::SyncQueue; #[derive(Debug, Error)] pub enum XBackendError { @@ -396,7 +394,7 @@ impl XBackendData { id: self.state.output_ids.next(), _backend: self.clone(), window: window_id, - removed: Cell::new(false), + events: Default::default(), width: Cell::new(0), height: Cell::new(0), serial: Default::default(), @@ -479,7 +477,7 @@ impl XBackendData { self.outputs.set(window_id, output.clone()); self.state .backend_events - .push(BackendEvent::NewOutput(output.clone())); + .push(BackendEvent::NewConnector(output.clone())); self.present(&output).await; Ok(()) } @@ -636,10 +634,6 @@ impl XBackendData { } async fn present(&self, output: &Rc) { - if output.removed.get() { - return; - } - let serial = output.serial.fetch_add(1); let image = &output.images[output.next_image.fetch_add(1) % output.images.len()]; @@ -648,7 +642,7 @@ impl XBackendData { if let Some(node) = self.state.root.outputs.get(&output.id) { let fb = image.fb.get(); - fb.render(&*node, &self.state, Some(node.position.get())); + fb.render(&*node, &self.state, Some(node.global.pos.get())); } let pp = PresentPixmap { @@ -795,7 +789,7 @@ impl XBackendData { Some(o) => o, _ => return Ok(()), }; - output.removed.set(true); + output.events.push(ConnectorEvent::Removed); output.changed(); Ok(()) } @@ -820,6 +814,11 @@ impl XBackendData { old.fb.set(new.fb.get()); old.pixmap.set(new.pixmap.get()); } + output.events.push(ConnectorEvent::ModeChanged(Mode { + width, + height, + refresh_rate: 60, // TODO + })); output.changed(); } Ok(()) @@ -830,7 +829,7 @@ struct XOutput { id: OutputId, _backend: Rc, window: u32, - removed: Cell, + events: SyncQueue, width: Cell, height: Cell, serial: NumCell, @@ -856,21 +855,13 @@ impl XOutput { } } -impl Output for XOutput { +impl Connector for XOutput { fn id(&self) -> OutputId { self.id } - fn removed(&self) -> bool { - self.removed.get() - } - - fn width(&self) -> i32 { - self.width.get() - } - - fn height(&self) -> i32 { - self.height.get() + fn event(&self) -> Option { + self.events.pop() } fn on_change(&self, cb: Rc) { diff --git a/src/compositor.rs b/src/compositor.rs index 906dcd79..ad3812e5 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -120,12 +120,12 @@ fn main_(logger: Arc, _args: &RunArgs) -> Result<(), MainError> { { let dummy_output = Rc::new(OutputNode { id: state.node_ids.next(), - position: Default::default(), global: Rc::new(WlOutputGlobal::new( state.globals.name(), Rc::new(DummyOutput { id: state.output_ids.next(), }), + 0, )), workspaces: Default::default(), workspace: Default::default(), diff --git a/src/drm/drm.rs b/src/drm/drm.rs index 52db595c..37031db3 100644 --- a/src/drm/drm.rs +++ b/src/drm/drm.rs @@ -32,6 +32,7 @@ pub use sys::{ drm_mode_modeinfo, DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, }; +use crate::utils::vec_ext::VecExt; #[derive(Debug, Error)] pub enum DrmError { @@ -63,6 +64,8 @@ pub enum DrmError { GetPropBlob(#[source] OsError), #[error("Property has an invalid size")] InvalidProbSize, + #[error("Property has a size that is not a multiple of the vector type")] + UnalignedPropSize, #[error("Could not perform drm properties ioctl")] GetProperties(#[source] OsError), #[error("Could not perform drm atomic ioctl")] @@ -316,6 +319,25 @@ impl DrmMaster { } } + pub fn getblob_vec(&self, blob: DrmBlob) -> Result, DrmError> { + assert_ne!(mem::size_of::(), 0); + let mut vec = vec![]; + loop { + let (_, bytes) = vec.split_at_spare_mut_bytes_ext(); + match mode_getprobblob(self.raw(), blob.0, bytes) { + Err(e) => return Err(DrmError::GetPropBlob(e)), + Ok(n) if n % mem::size_of::() != 0 => return Err(DrmError::UnalignedPropSize), + Ok(n) if n <= bytes.len() => { + unsafe { + vec.set_len(n / mem::size_of::()); + } + return Ok(vec); + } + Ok(n) => vec.reserve_exact(n / mem::size_of::()), + } + } + } + pub fn event(&self) -> Result, DrmError> { if self.events.is_empty() { let mut buf = self.buf.borrow_mut(); @@ -570,6 +592,16 @@ impl DrmModeInfo { name, } } + + pub fn refresh_rate(&self) -> u32 { + let clock_mhz = self.clock as u64 * 1_000_000; + let htotal = self.htotal as u64; + let vtotal = self.vtotal as u64; + (((clock_mhz / htotal) + (vtotal / 2)) / vtotal) as u32 + // simplifies to + // clock_mhz / (htotal * vtotal) + 1/2 + // why round up (+1/2) instead of down? + } } #[derive(Debug)] diff --git a/src/edid.rs b/src/edid.rs new file mode 100644 index 00000000..e27e3071 --- /dev/null +++ b/src/edid.rs @@ -0,0 +1,1059 @@ +use std::fmt::{Debug, Formatter}; +use std::rc::Rc; +use bstr::{BString, ByteSlice}; +use thiserror::Error; +use crate::utils::bitflags::BitflagsExt; +use crate::utils::ptr_ext::PtrExt; +use crate::utils::stack::Stack; + +#[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, + pub vertical_screen_size_cm: Option, + pub landscape_aspect_ration: Option, + pub portrait_aspect_ration: Option, +} + +#[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, + 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, +} + +#[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; 6]), + ColorPoint(ColorPoint, Option), + Cvt3ByteCode([Cvt3ByteCode; 4]), +} + +type EdidContext = (usize, EdidParseContext); + +struct EdidParser<'a> { + data: &'a [u8], + pos: usize, + context: Rc>, + saved_ctx: Vec, + errors: Vec<(EdidError, Vec)>, +} + +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>, +} + +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(&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 { + let &[a] = self.read_n()?; + Ok(a) + } + + fn read_u16(&mut self) -> Result { + let &[lo, hi] = self.read_n()?; + Ok(((hi as u16) << 8) + lo as u16) + } + + fn read_u32(&mut self) -> Result { + 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 { + 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 { + 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 { + 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, 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 { + 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 { + 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 { + 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 { + 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; 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) { + 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; 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, 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; 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 { + 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 { + let _ctx = self.push_ctx(EdidParseContext::Extension); + self.read_n::<128>()?; + Ok(EdidExtension::Unknown) + } + + fn parse(&mut self) -> Result { + 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, + pub model_year: Option, + pub year_of_manufacture: Option, + pub edid_version: u8, + pub edid_revision: u8, + pub video_input_definition: VideoInputDefinition, + pub screen_dimensions: ScreenDimensions, + pub gamma: Option, + pub feature_support: FeatureSupport, + pub chromaticity_coordinates: ChromaticityCoordinates, + pub established_timings: EstablishedTimings, + pub standard_timings: [Option; 8], + pub descriptors: [Option; 4], + pub num_extensions: u8, +} + +#[derive(Debug)] +pub enum EdidExtension { + Unknown, +} + +#[derive(Debug)] +pub struct EdidFile { + pub base_block: EdidBaseBlock, + pub extension_blocks: Vec, +} + +#[derive(Debug, Error)] +pub enum EdidError { + #[error("Unexpected end-of-file")] + UnexpectedEof, + #[error("Invalid magic header")] + InvalidMagic(BString), +} + +pub fn parse(data: &[u8]) -> Result { + let mut parser = EdidParser { + data, + pos: 0, + context: Rc::new(Default::default()), + saved_ctx: vec![], + errors: vec![] + }; + parser.parse() +} diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index d65fa4da..3114f821 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -1,4 +1,4 @@ -use crate::backend::Output; +use crate::backend::Connector; use crate::client::{Client, ClientError, ClientId}; use crate::globals::{Global, GlobalName}; use crate::ifs::zxdg_output_v1::ZxdgOutputV1; @@ -17,6 +17,7 @@ use std::cell::{Cell, RefCell}; use std::collections::hash_map::Entry; use std::rc::Rc; use thiserror::Error; +use crate::backend; const SP_UNKNOWN: i32 = 0; #[allow(dead_code)] @@ -52,18 +53,20 @@ const MODE_PREFERRED: u32 = 2; pub struct WlOutputGlobal { name: GlobalName, - output: Rc, - pos: Cell, + pub connector: Rc, + pub pos: Cell, + pub mode: Cell, pub node: CloneCell>>, pub bindings: RefCell>>>, } impl WlOutputGlobal { - pub fn new(name: GlobalName, output: Rc) -> Self { + pub fn new(name: GlobalName, connector: Rc, x1: i32) -> Self { Self { name, - output: output.clone(), - pos: Cell::new(Rect::new_sized(0, 0, output.width(), output.height()).unwrap()), + connector: connector.clone(), + pos: Cell::new(Rect::new_empty(x1, 0)), + mode: Default::default(), node: Default::default(), bindings: Default::default(), } @@ -73,30 +76,18 @@ impl WlOutputGlobal { self.pos.get() } - pub fn update_properties(&self) { - let width = self.output.width(); - let height = self.output.height(); - - let pos = self.pos.get(); - let old_width = pos.width(); - let old_height = pos.height(); - let changed = old_width != width || old_height != height; - - if changed { - self.pos - .set(Rect::new_sized(pos.x1(), pos.y1(), width, height).unwrap()); - let bindings = self.bindings.borrow_mut(); - for binding in bindings.values() { - for binding in binding.values() { - binding.send_geometry(); - binding.send_mode(); - binding.send_scale(); - binding.send_done(); - binding.client.flush(); - let xdg = binding.xdg_outputs.lock(); - for xdg in xdg.values() { - xdg.send_updates(); - } + pub fn send_mode(&self) { + let bindings = self.bindings.borrow_mut(); + for binding in bindings.values() { + for binding in binding.values() { + binding.send_geometry(); + binding.send_mode(); + binding.send_scale(); + binding.send_done(); + binding.client.flush(); + let xdg = binding.xdg_outputs.lock(); + for xdg in xdg.values() { + xdg.send_updates(); } } } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 66256731..2fbe1129 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -102,7 +102,7 @@ impl XdgPopup { let mut rel_pos = positioner.get_position(false, false); let mut abs_pos = rel_pos.move_(parent_abs.x1(), parent_abs.y1()); if let Some(ws) = parent.workspace.get() { - let output_pos = ws.output.get().position.get(); + let output_pos = ws.output.get().global.pos.get(); let mut overflow = output_pos.get_overflow(&abs_pos); if !overflow.is_contained() { let mut flip_x = positioner.ca.contains(CA::FLIP_X) && overflow.x_overflow(); diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index 7b302f4a..10f5fa16 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -262,7 +262,7 @@ impl ZwlrLayerSurfaceV1 { if anchor == 0 { anchor = LEFT | RIGHT | TOP | BOTTOM; } - let opos = self.output.position.get(); + let opos = self.output.global.pos.get(); let mut x1 = opos.x1(); let mut y1 = opos.y1(); if anchor.contains(LEFT) { diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs index 11ae219d..7db0e3bc 100644 --- a/src/ifs/zwlr_layer_shell_v1.rs +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -82,7 +82,7 @@ impl ZwlrLayerShellV1 { } } }; - log::info!("output = {:?}", output.position.get()); + log::info!("output = {:?}", output.global.pos.get()); if req.layer > OVERLAY { return Err(GetLayerSurfaceError::UnknownLayer(req.layer)); } diff --git a/src/main.rs b/src/main.rs index 712cd369..1425346a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,6 +78,7 @@ mod wire_xcon; mod xcon; mod xkbcommon; mod xwayland; +mod edid; fn main() { cli::main(); diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index ba9f6dc5..1af3b8e8 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -29,7 +29,7 @@ pub struct Renderer<'a> { impl Renderer<'_> { pub fn render_output(&mut self, output: &OutputNode, x: i32, y: i32) { - let opos = output.position.get(); + let opos = output.global.pos.get(); macro_rules! render_layer { ($layer:expr) => { for ls in $layer.iter() { diff --git a/src/state.rs b/src/state.rs index 902e318c..2f3c29c1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -184,7 +184,7 @@ impl State { width += 2 * self.theme.border_width.get(); height += 2 * self.theme.border_width.get() + self.theme.title_height.get(); let output = workspace.output.get(); - let output_rect = output.position.get(); + let output_rect = output.global.pos.get(); let position = { let mut x1 = output_rect.x1(); let mut y1 = output_rect.y1(); diff --git a/src/tasks/backend.rs b/src/tasks/backend.rs index 137a2a29..e3fc3ce8 100644 --- a/src/tasks/backend.rs +++ b/src/tasks/backend.rs @@ -1,4 +1,4 @@ -use crate::backend::{BackendEvent, Output}; +use crate::backend::{BackendEvent, Connector}; use crate::state::State; use crate::tasks::input_device; use crate::tasks::output::OutputHandler; @@ -18,12 +18,12 @@ impl BackendEventHandler { fn handle_event(&mut self, event: BackendEvent) { match event { - BackendEvent::NewOutput(output) => self.handle_new_output(output), + BackendEvent::NewConnector(output) => self.handle_new_output(output), BackendEvent::NewInputDevice(s) => input_device::handle(&self.state, s), } } - fn handle_new_output(&mut self, output: Rc) { + fn handle_new_output(&mut self, output: Rc) { let id = output.id(); let oh = OutputHandler { state: self.state.clone(), diff --git a/src/tasks/output.rs b/src/tasks/output.rs index 93ab95ce..0c816512 100644 --- a/src/tasks/output.rs +++ b/src/tasks/output.rs @@ -1,16 +1,16 @@ -use crate::backend::Output; +use crate::backend::{Connector, ConnectorEvent}; use crate::ifs::wl_output::WlOutputGlobal; use crate::rect::Rect; use crate::state::State; use crate::tree::{OutputNode, OutputRenderData, WorkspaceNode}; use crate::utils::asyncevent::AsyncEvent; use crate::utils::clonecell::CloneCell; -use std::cell::{Cell, RefCell}; +use std::cell::{RefCell}; use std::rc::Rc; pub struct OutputHandler { pub state: Rc, - pub output: Rc, + pub output: Rc, } impl OutputHandler { @@ -21,12 +21,11 @@ impl OutputHandler { self.output.on_change(Rc::new(move || ae.trigger())); } let name = self.state.globals.name(); - let global = Rc::new(WlOutputGlobal::new(name, self.output.clone())); - let x1 = self.state.root.outputs.lock().values().map(|o| o.position.get().x2()).max().unwrap_or(0); + let x1 = self.state.root.outputs.lock().values().map(|o| o.global.pos.get().x2()).max().unwrap_or(0); + let global = Rc::new(WlOutputGlobal::new(name, self.output.clone(), x1)); let on = Rc::new(OutputNode { id: self.state.node_ids.next(), workspaces: Default::default(), - position: Cell::new(Rect::new_empty(x1, 0)), workspace: CloneCell::new(None), seat_state: Default::default(), global: global.clone(), @@ -68,20 +67,15 @@ impl OutputHandler { self.state.root.outputs.set(self.output.id(), on.clone()); self.state.add_global(&global); self.state.outputs.set(self.output.id(), global.clone()); - let mut width = 0; - let mut height = 0; - loop { - if self.output.removed() { - break; + 'outer: loop { + while let Some(event) = self.output.event() { + match event { + ConnectorEvent::Removed => break 'outer, + ConnectorEvent::ModeChanged(mode) => { + on.update_mode(mode); + } + } } - let new_width = self.output.width(); - let new_height = self.output.height(); - if new_width != width || new_height != height { - width = new_width; - height = new_height; - on.change_size(new_width, new_height); - } - global.update_properties(); ae.triggered().await; } global.node.set(None); diff --git a/src/tree.rs b/src/tree.rs index 061e043c..47296f5f 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -434,7 +434,7 @@ impl Node for DisplayNode { } let outputs = self.outputs.lock(); for output in outputs.values() { - let pos = output.position.get(); + let pos = output.global.pos.get(); if pos.contains(x, y) { let (x, y) = pos.translate(x, y); tree.push(FoundNode { diff --git a/src/tree/output.rs b/src/tree/output.rs index a7702670..32aed8f4 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -13,15 +13,15 @@ use crate::tree::{FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode}; use crate::utils::clonecell::CloneCell; use crate::utils::errorfmt::ErrorFmt; use crate::utils::linkedlist::LinkedList; -use std::cell::{Cell, RefCell}; +use std::cell::{RefCell}; use std::fmt::{Debug, Formatter}; use std::ops::{Deref, Sub}; use std::rc::Rc; +use crate::backend::Mode; tree_id!(OutputNodeId); pub struct OutputNode { pub id: OutputNodeId, - pub position: Cell, pub global: Rc, pub workspaces: LinkedList>, pub workspace: CloneCell>>, @@ -84,7 +84,7 @@ impl OutputNode { } fn workspace_rect(&self) -> Rect { - let rect = self.position.get(); + let rect = self.global.pos.get(); let th = self.state.theme.title_height.get(); Rect::new_sized( rect.x1(), @@ -95,10 +95,13 @@ impl OutputNode { .unwrap() } - pub fn change_size(&self, width: i32, height: i32) { - let pos = self.position.get(); - let rect = Rect::new_sized(pos.x1(), pos.y1(), width, height).unwrap(); - self.position.set(rect); + pub fn update_mode(&self, mode: Mode) { + if self.global.mode.get() == mode { + return; + } + let pos = self.global.pos.get(); + let rect = Rect::new_sized(pos.x1(), pos.y1(), mode.width, mode.height).unwrap(); + self.global.pos.set(rect); if let Some(c) = self.workspace.get() { c.change_extents(&self.workspace_rect()); } @@ -107,6 +110,7 @@ impl OutputNode { surface.deref().clone().change_extents(&rect); } } + self.global.send_mode(); } } @@ -166,7 +170,7 @@ impl Node for OutputNode { } fn absolute_position(&self) -> Rect { - self.position.get() + self.global.pos.get() } fn find_tree_at(&self, x: i32, mut y: i32, tree: &mut Vec) -> FindTreeResult { diff --git a/src/utils/stack.rs b/src/utils/stack.rs index d2c82491..1867fa50 100644 --- a/src/utils/stack.rs +++ b/src/utils/stack.rs @@ -1,4 +1,4 @@ -use crate::utils::ptr_ext::MutPtrExt; +use crate::utils::ptr_ext::{MutPtrExt, PtrExt}; use std::cell::UnsafeCell; pub struct Stack { @@ -23,4 +23,13 @@ impl Stack { pub fn pop(&self) -> Option { unsafe { self.vec.get().deref_mut().pop() } } + + pub fn to_vec(&self) -> Vec + where T: Clone, + { + unsafe { + let v = self.vec.get().deref(); + (*v).clone() + } + } } diff --git a/src/utils/syncqueue.rs b/src/utils/syncqueue.rs index 7d8d17ce..f580ae78 100644 --- a/src/utils/syncqueue.rs +++ b/src/utils/syncqueue.rs @@ -3,6 +3,7 @@ use std::cell::UnsafeCell; use std::collections::VecDeque; use std::mem; +#[derive(Debug)] pub struct SyncQueue { el: UnsafeCell>, }