Serenity Operating System
at master 1122 lines 45 kB view raw
1/* 2 * Copyright (c) 2022, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Concepts.h> 8#include <AK/Function.h> 9#include <AK/QuickSort.h> 10#include <AK/Try.h> 11#include <LibEDID/EDID.h> 12 13#ifdef KERNEL 14# include <Kernel/StdLib.h> 15#else 16# include <AK/ScopeGuard.h> 17# include <Kernel/API/Graphics.h> 18# include <fcntl.h> 19# include <unistd.h> 20 21# ifdef ENABLE_PNP_IDS_DATA 22# include <LibEDID/PnpIDs.h> 23# endif 24#endif 25 26namespace EDID { 27 28// clang doesn't like passing around pointers to members in packed structures, 29// even though we're only using them for arithmetic purposes 30#if defined(AK_COMPILER_CLANG) 31# pragma clang diagnostic ignored "-Waddress-of-packed-member" 32#endif 33 34static_assert(sizeof(Definitions::EDID) == Parser::BufferSize); 35 36class CEA861ExtensionBlock final { 37 friend class Parser; 38 39public: 40 enum class DataBlockTag : u8 { 41 Reserved = 0, 42 Audio, 43 Video, 44 VendorSpecific, 45 SpeakerAllocation, 46 VesaDTC, 47 Reserved2, 48 Extended 49 }; 50 51 ErrorOr<IterationDecision> for_each_short_video_descriptor(Function<IterationDecision(bool, VIC::Details const&)> callback) const 52 { 53 return for_each_data_block([&](DataBlockTag tag, ReadonlyBytes bytes) -> ErrorOr<IterationDecision> { 54 if (tag != DataBlockTag::Video) 55 return IterationDecision::Continue; 56 57 // Short video descriptors are one byte values 58 for (size_t i = 0; i < bytes.size(); i++) { 59 u8 byte = m_edid.read_host(&bytes[i]); 60 bool is_native = (byte & 0x80) != 0; 61 u8 vic_id = byte & 0x7f; 62 63 auto* vic_details = VIC::find_details_by_vic_id(vic_id); 64 if (!vic_details) 65 return Error::from_string_view_or_print_error_and_return_errno("CEA 861 extension block has invalid short video descriptor"sv, EINVAL); 66 67 IterationDecision decision = callback(is_native, *vic_details); 68 if (decision != IterationDecision::Continue) 69 return decision; 70 } 71 return IterationDecision::Continue; 72 }); 73 } 74 75 ErrorOr<IterationDecision> for_each_dtd(Function<IterationDecision(Parser::DetailedTiming const&)> callback) const 76 { 77 u8 dtd_start = m_edid.read_host(&m_block->cea861extension.dtd_start_offset); 78 if (dtd_start < 4) { 79 // dtd_start == 4 means there are no data blocks, but there are still DTDs 80 return IterationDecision::Continue; 81 } 82 83 if (dtd_start > offsetof(Definitions::ExtensionBlock, checksum) - sizeof(Definitions::DetailedTiming)) 84 return Error::from_string_view_or_print_error_and_return_errno("CEA 861 extension block has invalid DTD list"sv, EINVAL); 85 86 for (size_t offset = dtd_start; offset <= offsetof(Definitions::ExtensionBlock, checksum) - sizeof(Definitions::DetailedTiming); offset += sizeof(Definitions::DetailedTiming)) { 87 auto& dtd = *(Definitions::DetailedTiming const*)((u8 const*)m_block + offset); 88 if (m_edid.read_host(&dtd.pixel_clock) == 0) 89 break; 90 91 IterationDecision decision = callback(Parser::DetailedTiming(m_edid, &dtd)); 92 if (decision != IterationDecision::Continue) 93 return decision; 94 } 95 return IterationDecision::Continue; 96 } 97 98private: 99 CEA861ExtensionBlock(Parser const& edid, Definitions::ExtensionBlock const* block) 100 : m_edid(edid) 101 , m_block(block) 102 { 103 } 104 105 ErrorOr<IterationDecision> for_each_data_block(Function<ErrorOr<IterationDecision>(DataBlockTag, ReadonlyBytes)> callback) const 106 { 107 u8 dtd_start = m_edid.read_host(&m_block->cea861extension.dtd_start_offset); 108 if (dtd_start <= 4) 109 return IterationDecision::Continue; 110 111 if (dtd_start > offsetof(Definitions::ExtensionBlock, checksum)) 112 return Error::from_string_view_or_print_error_and_return_errno("CEA 861 extension block has invalid DTD start offset"sv, EINVAL); 113 114 auto* data_block_header = &m_block->cea861extension.bytes[0]; 115 auto* data_block_end = (u8 const*)m_block + dtd_start; 116 while (data_block_header < data_block_end) { 117 auto header_byte = m_edid.read_host(data_block_header); 118 size_t payload_size = header_byte & 0x1f; 119 auto tag = (DataBlockTag)((header_byte >> 5) & 0x7); 120 if (tag == DataBlockTag::Extended && payload_size == 0) 121 return Error::from_string_view_or_print_error_and_return_errno("CEA 861 extension block has invalid extended data block size"sv, EINVAL); 122 123 auto decision = TRY(callback(tag, m_edid.m_bytes.slice(data_block_header - m_edid.m_bytes.data() + 1, payload_size))); 124 if (decision != IterationDecision::Continue) 125 return decision; 126 127 data_block_header += 1 + payload_size; 128 } 129 return IterationDecision::Continue; 130 } 131 132 ErrorOr<IterationDecision> for_each_display_descriptor(Function<IterationDecision(u8, Definitions::DisplayDescriptor const&)> callback) const 133 { 134 u8 dtd_start = m_edid.read_host(&m_block->cea861extension.dtd_start_offset); 135 if (dtd_start <= 4) 136 return IterationDecision::Continue; 137 138 if (dtd_start > offsetof(Definitions::ExtensionBlock, checksum) - sizeof(Definitions::DetailedTiming)) 139 return Error::from_string_view_or_print_error_and_return_errno("CEA 861 extension block has invalid DTD list"sv, EINVAL); 140 141 for (size_t offset = dtd_start; offset <= offsetof(Definitions::ExtensionBlock, checksum) - sizeof(Definitions::DisplayDescriptor); offset += sizeof(Definitions::DisplayDescriptor)) { 142 auto& dd = *(Definitions::DisplayDescriptor const*)((u8 const*)m_block + offset); 143 if (m_edid.read_host(&dd.zero) != 0 || m_edid.read_host(&dd.reserved1) != 0) 144 continue; 145 146 u8 tag = m_edid.read_host(&dd.tag); 147 IterationDecision decision = callback(tag, dd); 148 if (decision != IterationDecision::Continue) 149 return decision; 150 } 151 return IterationDecision::Continue; 152 } 153 154 Parser const& m_edid; 155 Definitions::ExtensionBlock const* m_block; 156}; 157 158template<typename T> 159T Parser::read_host(T const* field) const 160{ 161 VERIFY((u8 const*)field >= m_bytes.data() && (u8 const*)field + sizeof(T) <= m_bytes.data() + m_bytes.size()); 162 size_t offset = (u8 const*)field - m_bytes.data(); 163 T value; 164 if constexpr (sizeof(T) > 1) 165 ByteReader::load(m_bytes.offset(offset), value); 166 else 167 value = m_bytes.at(offset); 168 169 return value; 170} 171 172template<Integral T> 173requires(sizeof(T) > 1) 174T Parser::read_le(T const* field) const 175{ 176 static_assert(sizeof(T) > 1); 177 return AK::convert_between_host_and_little_endian(read_host(field)); 178} 179 180template<Integral T> 181requires(sizeof(T) > 1) 182T Parser::read_be(T const* field) const 183{ 184 static_assert(sizeof(T) > 1); 185 return AK::convert_between_host_and_big_endian(read_host(field)); 186} 187 188ErrorOr<Parser> Parser::from_bytes(ReadonlyBytes bytes) 189{ 190 Parser edid(bytes); 191 TRY(edid.parse()); 192 return edid; 193} 194 195ErrorOr<Parser> Parser::from_bytes(ByteBuffer&& bytes) 196{ 197 Parser edid(move(bytes)); 198 TRY(edid.parse()); 199 return edid; 200} 201 202#ifndef KERNEL 203ErrorOr<Parser> Parser::from_display_connector_device(int display_connector_fd) 204{ 205 RawBytes edid_bytes; 206 GraphicsHeadEDID edid_info {}; 207 edid_info.bytes = &edid_bytes[0]; 208 edid_info.bytes_size = sizeof(edid_bytes); 209 if (graphics_connector_get_head_edid(display_connector_fd, &edid_info) < 0) { 210 int err = errno; 211 if (err == EOVERFLOW) { 212 // We need a bigger buffer with at least bytes_size bytes 213 auto edid_byte_buffer = TRY(ByteBuffer::create_zeroed(edid_info.bytes_size)); 214 edid_info.bytes = edid_byte_buffer.data(); 215 if (graphics_connector_get_head_edid(display_connector_fd, &edid_info) < 0) { 216 err = errno; 217 return Error::from_errno(err); 218 } 219 220 return from_bytes(move(edid_byte_buffer)); 221 } 222 223 return Error::from_errno(err); 224 } 225 226 auto edid_byte_buffer = TRY(ByteBuffer::copy((void const*)edid_bytes, sizeof(edid_bytes))); 227 return from_bytes(move(edid_byte_buffer)); 228} 229 230ErrorOr<Parser> Parser::from_display_connector_device(DeprecatedString const& display_connector_device) 231{ 232 int display_connector_fd = open(display_connector_device.characters(), O_RDWR | O_CLOEXEC); 233 if (display_connector_fd < 0) { 234 int err = errno; 235 return Error::from_errno(err); 236 } 237 ScopeGuard fd_guard([&] { 238 close(display_connector_fd); 239 }); 240 return from_display_connector_device(display_connector_fd); 241} 242#endif 243 244Parser::Parser(ReadonlyBytes bytes) 245 : m_bytes(move(bytes)) 246{ 247} 248 249Parser::Parser(ByteBuffer&& bytes) 250 : m_bytes_buffer(move(bytes)) 251 , m_bytes(m_bytes_buffer) 252{ 253} 254 255Parser::Parser(Parser const& other) 256 : m_bytes_buffer(other.m_bytes_buffer) 257 , m_revision(other.m_revision) 258{ 259 if (m_bytes_buffer.is_empty()) 260 m_bytes = other.m_bytes_buffer; // We don't own the buffer 261 else 262 m_bytes = m_bytes_buffer; // We own the buffer 263} 264 265Parser& Parser::operator=(Parser&& from) 266{ 267 m_bytes_buffer = move(from.m_bytes_buffer); 268 m_bytes = move(from.m_bytes); 269 m_revision = from.m_revision; 270 return *this; 271} 272 273Parser& Parser::operator=(Parser const& other) 274{ 275 if (this == &other) 276 return *this; 277 278 m_bytes_buffer = other.m_bytes_buffer; 279 if (m_bytes_buffer.is_empty()) 280 m_bytes = other.m_bytes_buffer; // We don't own the buffer 281 else 282 m_bytes = m_bytes_buffer; // We own the buffer 283 m_revision = other.m_revision; 284 return *this; 285} 286 287bool Parser::operator==(Parser const& other) const 288{ 289 if (this == &other) 290 return true; 291 return m_bytes == other.m_bytes; 292} 293 294Definitions::EDID const& Parser::raw_edid() const 295{ 296 return *(Definitions::EDID const*)m_bytes.data(); 297} 298 299ErrorOr<void> Parser::parse() 300{ 301 if (m_bytes.size() < sizeof(Definitions::EDID)) 302 return Error::from_string_view_or_print_error_and_return_errno("Incomplete Parser structure"sv, EINVAL); 303 304 auto const& edid = raw_edid(); 305 u64 header = read_le(&edid.header); 306 if (header != 0x00ffffffffffff00ull) 307 return Error::from_string_view_or_print_error_and_return_errno("No Parser header"sv, EINVAL); 308 309 u8 major_version = read_host(&edid.version.version); 310 m_revision = read_host(&edid.version.revision); 311 if (major_version != 1 || m_revision > 4) 312 return Error::from_string_view_or_print_error_and_return_errno("Unsupported Parser version"sv, EINVAL); 313 314#ifdef KERNEL 315 m_version = TRY(Kernel::KString::formatted("1.{}", (int)m_revision)); 316#else 317 m_version = DeprecatedString::formatted("1.{}", (int)m_revision); 318#endif 319 320 u8 checksum = 0x0; 321 for (size_t i = 0; i < sizeof(Definitions::EDID); i++) 322 checksum += m_bytes[i]; 323 324 if (checksum != 0) { 325 if (m_revision >= 4) 326 return Error::from_string_view_or_print_error_and_return_errno("Parser checksum mismatch"sv, EINVAL); 327 else 328 dbgln("EDID checksum mismatch, data may be corrupted!"); 329 } 330 331 u16 packed_id = read_be(&raw_edid().vendor.manufacturer_id); 332 if (packed_id == 0x0) 333 return {}; 334 m_legacy_manufacturer_id[0] = (char)((u16)'A' + ((packed_id >> 10) & 0x1f) - 1); 335 m_legacy_manufacturer_id[1] = (char)((u16)'A' + ((packed_id >> 5) & 0x1f) - 1); 336 m_legacy_manufacturer_id[2] = (char)((u16)'A' + (packed_id & 0x1f) - 1); 337 m_legacy_manufacturer_id[3] = '\0'; 338 m_legacy_manufacturer_id_valid = true; 339 340 return {}; 341} 342 343ErrorOr<IterationDecision> Parser::for_each_extension_block(Function<IterationDecision(unsigned, u8, u8, ReadonlyBytes)> callback) const 344{ 345 auto& edid = raw_edid(); 346 u8 raw_extension_block_count = read_host(&edid.extension_block_count); 347 if (raw_extension_block_count == 0) 348 return IterationDecision::Continue; 349 if (sizeof(Definitions::EDID) + (size_t)raw_extension_block_count * sizeof(Definitions::ExtensionBlock) > m_bytes.size()) 350 return Error::from_string_view_or_print_error_and_return_errno("Truncated EDID"sv, EINVAL); 351 352 auto validate_block_checksum = [&](Definitions::ExtensionBlock const& block) { 353 u8 checksum = 0x0; 354 auto* bytes = (u8 const*)&block; 355 for (size_t i = 0; i < sizeof(block); i++) 356 checksum += bytes[i]; 357 358 return checksum == 0; 359 }; 360 361 auto* raw_extension_blocks = (Definitions::ExtensionBlock const*)(m_bytes.data() + sizeof(Definitions::EDID)); 362 Definitions::ExtensionBlock const* current_extension_map = nullptr; 363 364 unsigned raw_index = 0; 365 if (m_revision <= 3) { 366 if (raw_extension_block_count > 1) { 367 current_extension_map = &raw_extension_blocks[0]; 368 raw_index++; 369 if (read_host(&current_extension_map->tag) != (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap) 370 return Error::from_string_view_or_print_error_and_return_errno("Did not find extension map at block 1"sv, EINVAL); 371 372 if (!validate_block_checksum(*current_extension_map)) 373 return Error::from_string_view_or_print_error_and_return_errno("Extension block map checksum mismatch"sv, EINVAL); 374 } 375 } else if (read_host(&raw_extension_blocks[0].tag) == (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap) { 376 current_extension_map = &raw_extension_blocks[0]; 377 raw_index++; 378 } 379 380 for (; raw_index < raw_extension_block_count; raw_index++) { 381 auto& raw_block = raw_extension_blocks[raw_index]; 382 u8 tag = read_host(&raw_block.tag); 383 384 if (current_extension_map && raw_index == 127) { 385 if (tag != (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap) 386 return Error::from_string_view_or_print_error_and_return_errno("Did not find extension map at block 128"sv, EINVAL); 387 388 current_extension_map = &raw_extension_blocks[127]; 389 if (!validate_block_checksum(*current_extension_map)) 390 return Error::from_string_view_or_print_error_and_return_errno("Extension block map checksum mismatch"sv, EINVAL); 391 continue; 392 } 393 394 if (tag == (u8)Definitions::ExtensionBlockTag::ExtensionBlockMap) 395 return Error::from_string_view_or_print_error_and_return_errno("Unexpected extension map encountered"sv, EINVAL); 396 397 if (!validate_block_checksum(raw_block)) 398 return Error::from_string_view_or_print_error_and_return_errno("Extension block checksum mismatch"sv, EINVAL); 399 400 size_t offset = (u8 const*)&raw_block - m_bytes.data(); 401 IterationDecision decision = callback(raw_index + 1, tag, raw_block.block.revision, m_bytes.slice(offset, sizeof(Definitions::ExtensionBlock))); 402 if (decision != IterationDecision::Continue) 403 return decision; 404 } 405 return IterationDecision::Continue; 406} 407 408StringView Parser::version() const 409{ 410#ifdef KERNEL 411 return m_version->view(); 412#else 413 return m_version; 414#endif 415} 416 417StringView Parser::legacy_manufacturer_id() const 418{ 419 return { m_legacy_manufacturer_id, strlen(m_legacy_manufacturer_id) }; 420} 421 422#ifndef KERNEL 423DeprecatedString Parser::manufacturer_name() const 424{ 425 if (!m_legacy_manufacturer_id_valid) 426 return "Unknown"; 427 auto manufacturer_id = legacy_manufacturer_id(); 428# ifdef ENABLE_PNP_IDS_DATA 429 if (auto pnp_id_data = PnpIDs::find_by_manufacturer_id(manufacturer_id); pnp_id_data.has_value()) 430 return pnp_id_data.value().manufacturer_name; 431# endif 432 return manufacturer_id; 433} 434#endif 435 436u16 Parser::product_code() const 437{ 438 return read_le(&raw_edid().vendor.product_code); 439} 440 441u32 Parser::serial_number() const 442{ 443 return read_le(&raw_edid().vendor.serial_number); 444} 445 446auto Parser::digital_display() const -> Optional<DigitalDisplay> 447{ 448 auto& edid = raw_edid(); 449 u8 video_input_definition = read_host(&edid.basic_display_parameters.video_input_definition); 450 if (!(video_input_definition & 0x80)) 451 return {}; // This is an analog display 452 453 u8 feature_support = read_host(&edid.basic_display_parameters.feature_support); 454 return DigitalDisplay(video_input_definition, feature_support, m_revision); 455} 456 457auto Parser::analog_display() const -> Optional<AnalogDisplay> 458{ 459 auto& edid = raw_edid(); 460 u8 video_input_definition = read_host(&edid.basic_display_parameters.video_input_definition); 461 if ((video_input_definition & 0x80) != 0) 462 return {}; // This is a digital display 463 464 u8 feature_support = read_host(&edid.basic_display_parameters.feature_support); 465 return AnalogDisplay(video_input_definition, feature_support, m_revision); 466} 467 468auto Parser::screen_size() const -> Optional<ScreenSize> 469{ 470 auto& edid = raw_edid(); 471 u8 horizontal_size_or_aspect_ratio = read_host(&edid.basic_display_parameters.horizontal_size_or_aspect_ratio); 472 u8 vertical_size_or_aspect_ratio = read_host(&edid.basic_display_parameters.vertical_size_or_aspect_ratio); 473 474 if (horizontal_size_or_aspect_ratio == 0 || vertical_size_or_aspect_ratio == 0) { 475 // EDID < 1.4: Unknown or undefined 476 // EDID >= 1.4: If both are 0 it is unknown or undefined 477 // If one of them is 0 then we're dealing with aspect ratios 478 return {}; 479 } 480 481 return ScreenSize(horizontal_size_or_aspect_ratio, vertical_size_or_aspect_ratio); 482} 483 484auto Parser::aspect_ratio() const -> Optional<ScreenAspectRatio> 485{ 486 if (m_revision < 4) 487 return {}; 488 489 auto& edid = raw_edid(); 490 u8 value_1 = read_host(&edid.basic_display_parameters.horizontal_size_or_aspect_ratio); 491 u8 value_2 = read_host(&edid.basic_display_parameters.vertical_size_or_aspect_ratio); 492 493 if (value_1 == 0 && value_2 == 0) 494 return {}; // Unknown or undefined 495 if (value_1 != 0 && value_2 != 0) 496 return {}; // Dimensions are in cm 497 498 if (value_1 == 0) 499 return ScreenAspectRatio(ScreenAspectRatio::Orientation::Portrait, FixedPoint<16>(100) / FixedPoint<16>((i32)value_2 + 99)); 500 501 VERIFY(value_2 == 0); 502 return ScreenAspectRatio(ScreenAspectRatio::Orientation::Landscape, FixedPoint<16>((i32)value_1 + 99) / 100); 503} 504 505Optional<FixedPoint<16>> Parser::gamma() const 506{ 507 u8 display_transfer_characteristics = read_host(&raw_edid().basic_display_parameters.display_transfer_characteristics); 508 if (display_transfer_characteristics == 0xff) { 509 if (m_revision < 4) 510 return {}; 511 512 // TODO: EDID >= 1.4 stores more gamma details in an extension block (e.g. DI-EXT) 513 return {}; 514 } 515 516 FixedPoint<16> gamma { (i32)display_transfer_characteristics + 100 }; 517 gamma /= 100; 518 return gamma; 519} 520 521u32 Parser::DetailedTiming::pixel_clock_khz() const 522{ 523 // Note: The stored value is in units of 10 kHz, which means that to get the 524 // value in kHz, we need to multiply it by 10. 525 return (u32)m_edid.read_le(&m_detailed_timings.pixel_clock) * 10; 526} 527 528u16 Parser::DetailedTiming::horizontal_addressable_pixels() const 529{ 530 u8 low = m_edid.read_host(&m_detailed_timings.horizontal_addressable_pixels_low); 531 u8 high = m_edid.read_host(&m_detailed_timings.horizontal_addressable_and_blanking_pixels_high) >> 4; 532 return ((u16)high << 8) | (u16)low; 533} 534 535u16 Parser::DetailedTiming::horizontal_blanking_pixels() const 536{ 537 u8 low = m_edid.read_host(&m_detailed_timings.horizontal_blanking_pixels_low); 538 u8 high = m_edid.read_host(&m_detailed_timings.horizontal_addressable_and_blanking_pixels_high) & 0xf; 539 return ((u16)high << 8) | (u16)low; 540} 541 542u16 Parser::DetailedTiming::vertical_addressable_lines_raw() const 543{ 544 u8 low = m_edid.read_host(&m_detailed_timings.vertical_addressable_lines_low); 545 u8 high = m_edid.read_host(&m_detailed_timings.vertical_addressable_and_blanking_lines_high) >> 4; 546 return ((u16)high << 8) | (u16)low; 547} 548 549u16 Parser::DetailedTiming::vertical_addressable_lines() const 550{ 551 auto lines = vertical_addressable_lines_raw(); 552 return is_interlaced() ? lines * 2 : lines; 553} 554 555u16 Parser::DetailedTiming::vertical_blanking_lines() const 556{ 557 u8 low = m_edid.read_host(&m_detailed_timings.vertical_blanking_lines_low); 558 u8 high = m_edid.read_host(&m_detailed_timings.vertical_addressable_and_blanking_lines_high) & 0xf; 559 return ((u16)high << 8) | (u16)low; 560} 561 562u16 Parser::DetailedTiming::horizontal_front_porch_pixels() const 563{ 564 u8 low = m_edid.read_host(&m_detailed_timings.horizontal_front_porch_pixels_low); 565 u8 high = m_edid.read_host(&m_detailed_timings.horizontal_and_vertical_front_porch_sync_pulse_width_high) >> 6; 566 return ((u16)high << 8) | (u16)low; 567} 568 569u16 Parser::DetailedTiming::horizontal_sync_pulse_width_pixels() const 570{ 571 u8 low = m_edid.read_host(&m_detailed_timings.horizontal_sync_pulse_width_pixels_low); 572 u8 high = (m_edid.read_host(&m_detailed_timings.horizontal_and_vertical_front_porch_sync_pulse_width_high) >> 4) & 3; 573 return ((u16)high << 8) | (u16)low; 574} 575 576u16 Parser::DetailedTiming::vertical_front_porch_lines() const 577{ 578 u8 low = m_edid.read_host(&m_detailed_timings.vertical_front_porch_and_sync_pulse_width_lines_low) >> 4; 579 u8 high = (m_edid.read_host(&m_detailed_timings.horizontal_and_vertical_front_porch_sync_pulse_width_high) >> 2) & 3; 580 return ((u16)high << 4) | (u16)low; 581} 582 583u16 Parser::DetailedTiming::vertical_sync_pulse_width_lines() const 584{ 585 u8 low = m_edid.read_host(&m_detailed_timings.vertical_front_porch_and_sync_pulse_width_lines_low) & 0xf; 586 u8 high = m_edid.read_host(&m_detailed_timings.horizontal_and_vertical_front_porch_sync_pulse_width_high) & 3; 587 return ((u16)high << 4) | (u16)low; 588} 589 590u16 Parser::DetailedTiming::horizontal_image_size_mm() const 591{ 592 u8 low = m_edid.read_host(&m_detailed_timings.horizontal_addressable_image_size_mm_low); 593 u8 high = m_edid.read_host(&m_detailed_timings.horizontal_vertical_addressable_image_size_mm_high) >> 4; 594 return ((u16)high << 8) | (u16)low; 595} 596 597u16 Parser::DetailedTiming::vertical_image_size_mm() const 598{ 599 u8 low = m_edid.read_host(&m_detailed_timings.vertical_addressable_image_size_mm_low); 600 u8 high = m_edid.read_host(&m_detailed_timings.horizontal_vertical_addressable_image_size_mm_high) & 0xf; 601 return ((u16)high << 8) | (u16)low; 602} 603 604u8 Parser::DetailedTiming::horizontal_right_or_left_border_pixels() const 605{ 606 return m_edid.read_host(&m_detailed_timings.right_or_left_horizontal_border_pixels); 607} 608 609u8 Parser::DetailedTiming::vertical_top_or_bottom_border_lines() const 610{ 611 return m_edid.read_host(&m_detailed_timings.top_or_bottom_vertical_border_lines); 612} 613 614bool Parser::DetailedTiming::is_interlaced() const 615{ 616 return (m_edid.read_host(&m_detailed_timings.features) & (1 << 7)) != 0; 617} 618 619FixedPoint<16, u32> Parser::DetailedTiming::refresh_rate() const 620{ 621 // Blanking = front porch + sync pulse width + back porch 622 u32 total_horizontal_pixels = (u32)horizontal_addressable_pixels() + (u32)horizontal_blanking_pixels(); 623 u32 total_vertical_lines = (u32)vertical_addressable_lines_raw() + (u32)vertical_blanking_lines(); 624 u32 total_pixels = total_horizontal_pixels * total_vertical_lines; 625 if (total_pixels == 0) 626 return {}; 627 // Use a bigger fixed point representation due to the large numbers involved and then downcast 628 // Note: We need to convert the pixel clock from kHz to Hertz to actually calculate this correctly. 629 return FixedPoint<32, u64>(pixel_clock_khz() * 1000) / total_pixels; 630} 631 632ErrorOr<IterationDecision> Parser::for_each_established_timing(Function<IterationDecision(EstablishedTiming const&)> callback) const 633{ 634 static constexpr EstablishedTiming established_timing_byte1[8] = { 635 { EstablishedTiming::Source::VESA, 800, 600, 60, 0x9 }, 636 { EstablishedTiming::Source::VESA, 800, 600, 56, 0x8 }, 637 { EstablishedTiming::Source::VESA, 640, 480, 75, 0x6 }, 638 { EstablishedTiming::Source::VESA, 640, 480, 73, 0x5 }, 639 { EstablishedTiming::Source::Apple, 640, 480, 67 }, 640 { EstablishedTiming::Source::IBM, 640, 480, 60, 0x4 }, 641 { EstablishedTiming::Source::IBM, 720, 400, 88 }, 642 { EstablishedTiming::Source::IBM, 720, 400, 70 } 643 }; 644 static constexpr EstablishedTiming established_timing_byte2[8] = { 645 { EstablishedTiming::Source::VESA, 1280, 1024, 75, 0x24 }, 646 { EstablishedTiming::Source::VESA, 1024, 768, 75, 0x12 }, 647 { EstablishedTiming::Source::VESA, 1024, 768, 70, 0x11 }, 648 { EstablishedTiming::Source::VESA, 1024, 768, 60, 0x10 }, 649 { EstablishedTiming::Source::IBM, 1024, 768, 87, 0xf }, 650 { EstablishedTiming::Source::Apple, 832, 624, 75 }, 651 { EstablishedTiming::Source::VESA, 800, 600, 75, 0xb }, 652 { EstablishedTiming::Source::VESA, 800, 600, 72, 0xa } 653 }; 654 static constexpr EstablishedTiming established_timing_byte3[1] = { 655 { EstablishedTiming::Source::Apple, 1152, 870, 75 } 656 }; 657 658 auto& established_timings = raw_edid().established_timings; 659 for (int i = 7; i >= 0; i--) { 660 if (!(established_timings.timings_1 & (1 << i))) 661 continue; 662 IterationDecision decision = callback(established_timing_byte1[i]); 663 if (decision != IterationDecision::Continue) 664 return decision; 665 } 666 for (int i = 7; i >= 0; i--) { 667 if (!(established_timings.timings_2 & (1 << i))) 668 continue; 669 IterationDecision decision = callback(established_timing_byte2[i]); 670 if (decision != IterationDecision::Continue) 671 return decision; 672 } 673 674 if ((established_timings.manufacturer_reserved & (1 << 7)) != 0) { 675 IterationDecision decision = callback(established_timing_byte3[0]); 676 if (decision != IterationDecision::Continue) 677 return decision; 678 } 679 680 u8 manufacturer_specific = established_timings.manufacturer_reserved & 0x7f; 681 if (manufacturer_specific != 0) { 682 IterationDecision decision = callback(EstablishedTiming(EstablishedTiming::Source::Manufacturer, 0, 0, manufacturer_specific)); 683 if (decision != IterationDecision::Continue) 684 return decision; 685 } 686 687 auto callback_decision = IterationDecision::Continue; 688 TRY(for_each_display_descriptor([&](u8 descriptor_tag, auto& display_descriptor) { 689 if (descriptor_tag != (u8)Definitions::DisplayDescriptorTag::EstablishedTimings3) 690 return IterationDecision::Continue; 691 692 static constexpr EstablishedTiming established_timings3_bytes[] = { 693 // Byte 1 694 { EstablishedTiming::Source::VESA, 640, 350, 85, 0x1 }, 695 { EstablishedTiming::Source::VESA, 640, 400, 85, 0x2 }, 696 { EstablishedTiming::Source::VESA, 720, 400, 85, 0x3 }, 697 { EstablishedTiming::Source::VESA, 640, 480, 85, 0x7 }, 698 { EstablishedTiming::Source::VESA, 848, 480, 60, 0xe }, 699 { EstablishedTiming::Source::VESA, 800, 600, 85, 0xc }, 700 { EstablishedTiming::Source::VESA, 1024, 768, 85, 0x13 }, 701 { EstablishedTiming::Source::VESA, 1152, 864, 75, 0x15 }, 702 // Byte 2 703 { EstablishedTiming::Source::VESA, 1280, 768, 60, 0x16 }, 704 { EstablishedTiming::Source::VESA, 1280, 768, 60, 0x17 }, 705 { EstablishedTiming::Source::VESA, 1280, 768, 75, 0x18 }, 706 { EstablishedTiming::Source::VESA, 1280, 768, 85, 0x19 }, 707 { EstablishedTiming::Source::VESA, 1280, 960, 60, 0x20 }, 708 { EstablishedTiming::Source::VESA, 1280, 960, 85, 0x21 }, 709 { EstablishedTiming::Source::VESA, 1280, 1024, 60, 0x23 }, 710 { EstablishedTiming::Source::VESA, 1280, 1024, 85, 0x25 }, 711 // Byte 3 712 { EstablishedTiming::Source::VESA, 1360, 768, 60, 0x27 }, 713 { EstablishedTiming::Source::VESA, 1440, 900, 60, 0x2e }, 714 { EstablishedTiming::Source::VESA, 1440, 900, 60, 0x2f }, 715 { EstablishedTiming::Source::VESA, 1440, 900, 75, 0x30 }, 716 { EstablishedTiming::Source::VESA, 1440, 900, 85, 0x31 }, 717 { EstablishedTiming::Source::VESA, 1400, 1050, 60, 0x29 }, 718 { EstablishedTiming::Source::VESA, 1400, 1050, 60, 0x2a }, 719 { EstablishedTiming::Source::VESA, 1400, 1050, 75, 0x2b }, 720 // Byte 4 721 { EstablishedTiming::Source::VESA, 1400, 1050, 85, 0x2c }, 722 { EstablishedTiming::Source::VESA, 1680, 1050, 60, 0x39 }, 723 { EstablishedTiming::Source::VESA, 1680, 1050, 60, 0x3a }, 724 { EstablishedTiming::Source::VESA, 1680, 1050, 75, 0x3b }, 725 { EstablishedTiming::Source::VESA, 1680, 1050, 85, 0x3c }, 726 { EstablishedTiming::Source::VESA, 1600, 1200, 60, 0x33 }, 727 { EstablishedTiming::Source::VESA, 1600, 1200, 65, 0x34 }, 728 { EstablishedTiming::Source::VESA, 1600, 1200, 70, 0x35 }, 729 // Byte 5 730 { EstablishedTiming::Source::VESA, 1600, 1200, 75, 0x36 }, 731 { EstablishedTiming::Source::VESA, 1600, 1200, 85, 0x37 }, 732 { EstablishedTiming::Source::VESA, 1792, 1344, 60, 0x3e }, 733 { EstablishedTiming::Source::VESA, 1792, 1344, 75, 0x3f }, 734 { EstablishedTiming::Source::VESA, 1856, 1392, 60, 0x41 }, 735 { EstablishedTiming::Source::VESA, 1856, 1392, 75, 0x42 }, 736 { EstablishedTiming::Source::VESA, 1920, 1200, 60, 0x44 }, 737 { EstablishedTiming::Source::VESA, 1920, 1200, 60, 0x45 }, 738 // Byte 6 739 { EstablishedTiming::Source::VESA, 1920, 1200, 75, 0x46 }, 740 { EstablishedTiming::Source::VESA, 1920, 1200, 85, 0x47 }, 741 { EstablishedTiming::Source::VESA, 1920, 1440, 60, 0x49 }, 742 { EstablishedTiming::Source::VESA, 1920, 1440, 75, 0x4a } 743 // Reserved 744 }; 745 746 size_t byte_index = 0; 747 for (u8 dmt_bits : display_descriptor.established_timings3.dmt_bits) { 748 for (int i = 7; i >= 0; i--) { 749 if ((dmt_bits & (1 << i)) == 0) 750 continue; 751 752 size_t table_index = byte_index * 8 + (size_t)(7 - i); 753 if (table_index >= (sizeof(established_timings3_bytes) + 7) / sizeof(established_timings3_bytes[0])) 754 break; // Sometimes reserved bits are set 755 756 callback_decision = callback(established_timings3_bytes[table_index]); 757 if (callback_decision != IterationDecision::Continue) 758 return IterationDecision::Break; 759 } 760 byte_index++; 761 } 762 return IterationDecision::Break; // Only process one descriptor 763 })); 764 return callback_decision; 765} 766 767ErrorOr<IterationDecision> Parser::for_each_standard_timing(Function<IterationDecision(StandardTiming const&)> callback) const 768{ 769 for (size_t index = 0; index < 8; index++) { 770 auto& standard_timings = raw_edid().standard_timings[index]; 771 if (standard_timings.horizontal_8_pixels == 0x1 && standard_timings.ratio_and_refresh_rate == 0x1) 772 continue; // Skip unused records 773 u16 width = 8 * ((u16)read_host(&standard_timings.horizontal_8_pixels) + 31); 774 u8 aspect_ratio_and_refresh_rate = read_host(&standard_timings.ratio_and_refresh_rate); 775 u8 refresh_rate = (aspect_ratio_and_refresh_rate & 0x3f) + 60; 776 u16 height; 777 StandardTiming::AspectRatio aspect_ratio; 778 switch ((aspect_ratio_and_refresh_rate >> 6) & 3) { 779 case 0: 780 height = (width * 10) / 16; 781 aspect_ratio = StandardTiming::AspectRatio::AR_16_10; 782 break; 783 case 1: 784 height = (width * 3) / 4; 785 aspect_ratio = StandardTiming::AspectRatio::AR_4_3; 786 break; 787 case 2: 788 height = (width * 4) / 5; 789 aspect_ratio = StandardTiming::AspectRatio::AR_5_4; 790 break; 791 case 3: 792 height = (width * 9) / 16; 793 aspect_ratio = StandardTiming::AspectRatio::AR_16_9; 794 break; 795 default: 796 VERIFY_NOT_REACHED(); 797 } 798 799 auto* dmt = DMT::find_timing_by_std_id(standard_timings.horizontal_8_pixels, standard_timings.ratio_and_refresh_rate); 800 IterationDecision decision = callback(StandardTiming(width, height, refresh_rate, aspect_ratio, dmt ? dmt->dmt_id : 0)); 801 if (decision != IterationDecision::Continue) 802 return decision; 803 } 804 805 return IterationDecision::Continue; 806} 807 808u16 Parser::CoordinatedVideoTiming::horizontal_addressable_pixels() const 809{ 810 u32 aspect_h, aspect_v; 811 switch (aspect_ratio()) { 812 case AspectRatio::AR_4_3: 813 aspect_h = 4; 814 aspect_v = 3; 815 break; 816 case AspectRatio::AR_16_9: 817 aspect_h = 16; 818 aspect_v = 9; 819 break; 820 case AspectRatio::AR_16_10: 821 aspect_h = 16; 822 aspect_v = 10; 823 break; 824 case AspectRatio::AR_15_9: 825 aspect_h = 15; 826 aspect_v = 9; 827 break; 828 } 829 // Round down to nearest cell as per 3.10.3.8 830 return (u16)(8 * ((((u32)vertical_addressable_lines() * aspect_h) / aspect_v) / 8)); 831} 832 833u16 Parser::CoordinatedVideoTiming::vertical_addressable_lines() const 834{ 835 return ((u16)(m_cvt.bytes[1] >> 4) << 8) | (u16)m_cvt.bytes[0]; 836} 837 838auto Parser::CoordinatedVideoTiming::aspect_ratio() const -> AspectRatio 839{ 840 return (AspectRatio)((m_cvt.bytes[2] >> 2) & 0x3); 841} 842 843u16 Parser::CoordinatedVideoTiming::preferred_refresh_rate() 844{ 845 switch ((m_cvt.bytes[2] >> 5) & 3) { 846 case 0: 847 return 50; 848 case 1: 849 return 60; 850 case 2: 851 return 75; 852 case 3: 853 return 85; 854 default: 855 VERIFY_NOT_REACHED(); 856 } 857} 858 859ErrorOr<IterationDecision> Parser::for_each_coordinated_video_timing(Function<IterationDecision(CoordinatedVideoTiming const&)> callback) const 860{ 861 return for_each_display_descriptor([&](u8 descriptor_tag, Definitions::DisplayDescriptor const& display_descriptor) { 862 if (descriptor_tag != (u8)Definitions::DisplayDescriptorTag::CVTTimingCodes) 863 return IterationDecision::Continue; 864 u8 version = read_host(&display_descriptor.coordinated_video_timings.version); 865 if (version != 1) { 866 dbgln("Unsupported CVT display descriptor version: {}", version); 867 return IterationDecision::Continue; 868 } 869 870 for (size_t i = 0; i < 4; i++) { 871 const DMT::CVT cvt { 872 { 873 read_host(&display_descriptor.coordinated_video_timings.cvt[i][0]), 874 read_host(&display_descriptor.coordinated_video_timings.cvt[i][1]), 875 read_host(&display_descriptor.coordinated_video_timings.cvt[i][2]), 876 } 877 }; 878 if (cvt.bytes[0] == 0 && cvt.bytes[1] == 0 && cvt.bytes[2] == 0) 879 continue; 880 881 IterationDecision decision = callback(CoordinatedVideoTiming(cvt)); 882 if (decision != IterationDecision::Continue) 883 return decision; 884 } 885 return IterationDecision::Continue; 886 }); 887} 888 889ErrorOr<IterationDecision> Parser::for_each_detailed_timing(Function<IterationDecision(DetailedTiming const&, unsigned)> callback) const 890{ 891 auto& edid = raw_edid(); 892 for (size_t raw_index = 0; raw_index < 4; raw_index++) { 893 if (raw_index == 0 || read_le(&edid.detailed_timing_or_display_descriptors[raw_index].detailed_timing.pixel_clock) != 0) { 894 IterationDecision decision = callback(DetailedTiming(*this, &edid.detailed_timing_or_display_descriptors[raw_index].detailed_timing), 0); 895 if (decision != IterationDecision::Continue) 896 return decision; 897 } 898 } 899 900 Optional<Error> extension_error; 901 auto result = TRY(for_each_extension_block([&](u8 block_id, u8 tag, u8, ReadonlyBytes bytes) { 902 if (tag != (u8)Definitions::ExtensionBlockTag::CEA_861) 903 return IterationDecision::Continue; 904 905 CEA861ExtensionBlock cea861(*this, (Definitions::ExtensionBlock const*)bytes.data()); 906 auto result = cea861.for_each_dtd([&](auto& dtd) { 907 return callback(dtd, block_id); 908 }); 909 if (result.is_error()) { 910 dbgln("Failed to iterate DTDs in CEA861 extension block: {}", result.error()); 911 extension_error = result.release_error(); 912 return IterationDecision::Break; 913 } 914 915 return result.value(); 916 })); 917 if (extension_error.has_value()) 918 return extension_error.release_value(); 919 return result; 920} 921 922auto Parser::detailed_timing(size_t index) const -> Optional<DetailedTiming> 923{ 924 Optional<DetailedTiming> found_dtd; 925 auto result = for_each_detailed_timing([&](DetailedTiming const& dtd, unsigned) { 926 if (index == 0) { 927 found_dtd = dtd; 928 return IterationDecision::Break; 929 } 930 index--; 931 return IterationDecision::Continue; 932 }); 933 if (result.is_error()) { 934 dbgln("Error getting Parser detailed timing #{}: {}", index, result.error()); 935 return {}; 936 } 937 return found_dtd; 938} 939 940ErrorOr<IterationDecision> Parser::for_each_short_video_descriptor(Function<IterationDecision(unsigned, bool, VIC::Details const&)> callback) const 941{ 942 Optional<Error> extension_error; 943 auto result = for_each_extension_block([&](u8 block_id, u8 tag, u8, ReadonlyBytes bytes) { 944 if (tag != (u8)Definitions::ExtensionBlockTag::CEA_861) 945 return IterationDecision::Continue; 946 947 CEA861ExtensionBlock cea861(*this, (Definitions::ExtensionBlock const*)bytes.data()); 948 auto result = cea861.for_each_short_video_descriptor([&](bool is_native, VIC::Details const& vic) { 949 return callback(block_id, is_native, vic); 950 }); 951 if (result.is_error()) { 952 extension_error = result.release_error(); 953 return IterationDecision::Break; 954 } 955 return result.value(); 956 }); 957 if (result.is_error()) { 958 dbgln("Failed to iterate Parser extension blocks: {}", result.error()); 959 return IterationDecision::Break; 960 } 961 return result.value(); 962} 963 964ErrorOr<IterationDecision> Parser::for_each_display_descriptor(Function<IterationDecision(u8, Definitions::DisplayDescriptor const&)> callback) const 965{ 966 auto& edid = raw_edid(); 967 for (size_t raw_index = 1; raw_index < 4; raw_index++) { 968 auto& display_descriptor = edid.detailed_timing_or_display_descriptors[raw_index].display_descriptor; 969 if (read_le(&display_descriptor.zero) != 0 || read_host(&display_descriptor.reserved1) != 0) 970 continue; 971 972 u8 tag = read_host(&display_descriptor.tag); 973 IterationDecision decision = callback(tag, display_descriptor); 974 if (decision != IterationDecision::Continue) 975 return decision; 976 } 977 978 Optional<Error> extension_error; 979 auto result = TRY(for_each_extension_block([&](u8, u8 tag, u8, ReadonlyBytes bytes) { 980 if (tag != (u8)Definitions::ExtensionBlockTag::CEA_861) 981 return IterationDecision::Continue; 982 983 CEA861ExtensionBlock cea861(*this, (Definitions::ExtensionBlock const*)bytes.data()); 984 auto result = cea861.for_each_display_descriptor([&](u8 tag, auto& display_descriptor) { 985 return callback(tag, display_descriptor); 986 }); 987 if (result.is_error()) { 988 dbgln("Failed to iterate display descriptors in CEA861 extension block: {}", result.error()); 989 extension_error = result.release_error(); 990 return IterationDecision::Break; 991 } 992 993 return result.value(); 994 })); 995 if (extension_error.has_value()) 996 return extension_error.release_value(); 997 return result; 998} 999 1000#ifndef KERNEL 1001DeprecatedString Parser::display_product_name() const 1002{ 1003 DeprecatedString product_name; 1004 auto result = for_each_display_descriptor([&](u8 descriptor_tag, Definitions::DisplayDescriptor const& display_descriptor) { 1005 if (descriptor_tag != (u8)Definitions::DisplayDescriptorTag::DisplayProductName) 1006 return IterationDecision::Continue; 1007 1008 StringBuilder str; 1009 for (u8 byte : display_descriptor.display_product_name.ascii_name) { 1010 if (byte == 0xa) 1011 break; 1012 str.append((char)byte); 1013 } 1014 product_name = str.to_deprecated_string(); 1015 return IterationDecision::Break; 1016 }); 1017 if (result.is_error()) { 1018 dbgln("Failed to locate product name display descriptor: {}", result.error()); 1019 return {}; 1020 } 1021 return product_name; 1022} 1023 1024DeprecatedString Parser::display_product_serial_number() const 1025{ 1026 DeprecatedString product_name; 1027 auto result = for_each_display_descriptor([&](u8 descriptor_tag, Definitions::DisplayDescriptor const& display_descriptor) { 1028 if (descriptor_tag != (u8)Definitions::DisplayDescriptorTag::DisplayProductSerialNumber) 1029 return IterationDecision::Continue; 1030 1031 StringBuilder str; 1032 for (u8 byte : display_descriptor.display_product_serial_number.ascii_str) { 1033 if (byte == 0xa) 1034 break; 1035 str.append((char)byte); 1036 } 1037 product_name = str.to_deprecated_string(); 1038 return IterationDecision::Break; 1039 }); 1040 if (result.is_error()) { 1041 dbgln("Failed to locate product name display descriptor: {}", result.error()); 1042 return {}; 1043 } 1044 return product_name; 1045} 1046#endif 1047 1048auto Parser::supported_resolutions() const -> ErrorOr<Vector<SupportedResolution>> 1049{ 1050 Vector<SupportedResolution> resolutions; 1051 1052 auto add_resolution = [&](unsigned width, unsigned height, FixedPoint<16, u32> refresh_rate, bool preferred = false) { 1053 auto it = resolutions.find_if([&](auto& info) { 1054 return info.width == width && info.height == height; 1055 }); 1056 if (it == resolutions.end()) { 1057 resolutions.append({ width, height, { { refresh_rate, preferred } } }); 1058 } else { 1059 auto& info = *it; 1060 SupportedResolution::RefreshRate* found_refresh_rate = nullptr; 1061 for (auto& supported_refresh_rate : info.refresh_rates) { 1062 if (supported_refresh_rate.rate == refresh_rate) { 1063 found_refresh_rate = &supported_refresh_rate; 1064 break; 1065 } 1066 } 1067 if (found_refresh_rate) 1068 found_refresh_rate->preferred |= preferred; 1069 else 1070 info.refresh_rates.append({ refresh_rate, preferred }); 1071 } 1072 }; 1073 1074 TRY(for_each_established_timing([&](auto& established_timing) { 1075 if (established_timing.source() != EstablishedTiming::Source::Manufacturer) 1076 add_resolution(established_timing.width(), established_timing.height(), established_timing.refresh_rate()); 1077 return IterationDecision::Continue; 1078 })); 1079 1080 TRY(for_each_standard_timing([&](auto& standard_timing) { 1081 add_resolution(standard_timing.width(), standard_timing.height(), standard_timing.refresh_rate()); 1082 return IterationDecision::Continue; 1083 })); 1084 1085 size_t detailed_timing_index = 0; 1086 TRY(for_each_detailed_timing([&](auto& detailed_timing, auto) { 1087 bool is_preferred = detailed_timing_index++ == 0; 1088 add_resolution(detailed_timing.horizontal_addressable_pixels(), detailed_timing.vertical_addressable_lines(), detailed_timing.refresh_rate(), is_preferred); 1089 return IterationDecision::Continue; 1090 })); 1091 1092 TRY(for_each_short_video_descriptor([&](unsigned, bool, VIC::Details const& vic_details) { 1093 add_resolution(vic_details.horizontal_pixels, vic_details.vertical_lines, vic_details.refresh_rate_hz()); 1094 return IterationDecision::Continue; 1095 })); 1096 1097 TRY(for_each_coordinated_video_timing([&](auto& coordinated_video_timing) { 1098 if (auto* dmt = DMT::find_timing_by_cvt(coordinated_video_timing.cvt_code())) { 1099 add_resolution(dmt->horizontal_pixels, dmt->vertical_lines, dmt->vertical_frequency_hz()); 1100 } else { 1101 // TODO: We couldn't find this cvt code, try to decode it 1102 auto cvt = coordinated_video_timing.cvt_code(); 1103 dbgln("TODO: Decode CVT code: {:02x},{:02x},{:02x}", cvt.bytes[0], cvt.bytes[1], cvt.bytes[2]); 1104 } 1105 return IterationDecision::Continue; 1106 })); 1107 1108 quick_sort(resolutions, [&](auto& info1, auto& info2) { 1109 if (info1.width < info2.width) 1110 return true; 1111 if (info1.width == info2.width && info1.height < info2.height) 1112 return true; 1113 return false; 1114 }); 1115 for (auto& res : resolutions) { 1116 if (res.refresh_rates.size() > 1) 1117 quick_sort(res.refresh_rates); 1118 } 1119 return resolutions; 1120} 1121 1122}