diff options
author | dnicoara@chromium.org <dnicoara@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-25 20:12:14 +0000 |
---|---|---|
committer | dnicoara@chromium.org <dnicoara@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-25 20:12:14 +0000 |
commit | f9b77696792c95cc9b2ffb04154c9eb4bb0ad479 (patch) | |
tree | 38019679a2f71ec7f1f97f59e80f9c64cbad3ea4 /ui | |
parent | 2c7f2031a7a062c31a76de1f1bc54b5c06961e3d (diff) | |
download | chromium_src-f9b77696792c95cc9b2ffb04154c9eb4bb0ad479.zip chromium_src-f9b77696792c95cc9b2ffb04154c9eb4bb0ad479.tar.gz chromium_src-f9b77696792c95cc9b2ffb04154c9eb4bb0ad479.tar.bz2 |
Refactor EDID parsing utilities and consolidate them under ui/display/chromeos
This refactoring splits EDID parsing to generic parsing and Xrandr helpers. I'm also consolidating EDID parsing under ui/display/chromeos since this is the only place it is used.
BUG=333413
Review URL: https://codereview.chromium.org/196413010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@259316 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r-- | ui/display/chromeos/x11/display_snapshot_x11.cc | 2 | ||||
-rw-r--r-- | ui/display/chromeos/x11/display_util.cc | 161 | ||||
-rw-r--r-- | ui/display/chromeos/x11/display_util.h | 37 | ||||
-rw-r--r-- | ui/display/chromeos/x11/display_util_x11.cc | 41 | ||||
-rw-r--r-- | ui/display/chromeos/x11/display_util_x11.h | 24 | ||||
-rw-r--r-- | ui/display/chromeos/x11/display_util_x11_unittest.cc | 42 | ||||
-rw-r--r-- | ui/display/chromeos/x11/native_display_delegate_x11.cc | 7 | ||||
-rw-r--r-- | ui/display/display.gyp | 8 | ||||
-rw-r--r-- | ui/display/display_unittests.gypi | 5 | ||||
-rw-r--r-- | ui/display/edid_parser.cc | 208 | ||||
-rw-r--r-- | ui/display/edid_parser.h | 41 | ||||
-rw-r--r-- | ui/display/edid_parser_unittest.cc (renamed from ui/display/chromeos/x11/display_util_unittest.cc) | 146 | ||||
-rw-r--r-- | ui/display/x11/edid_parser_x11.cc | 121 | ||||
-rw-r--r-- | ui/display/x11/edid_parser_x11.h | 48 | ||||
-rw-r--r-- | ui/views/DEPS | 1 | ||||
-rw-r--r-- | ui/views/views.gyp | 5 | ||||
-rw-r--r-- | ui/views/widget/desktop_aura/desktop_screen_x11.cc | 4 |
17 files changed, 657 insertions, 244 deletions
diff --git a/ui/display/chromeos/x11/display_snapshot_x11.cc b/ui/display/chromeos/x11/display_snapshot_x11.cc index 366a1f3..9228a07 100644 --- a/ui/display/chromeos/x11/display_snapshot_x11.cc +++ b/ui/display/chromeos/x11/display_snapshot_x11.cc @@ -6,7 +6,7 @@ #include "base/strings/stringprintf.h" #include "ui/display/chromeos/x11/display_mode_x11.h" -#include "ui/display/chromeos/x11/display_util.h" +#include "ui/display/x11/edid_parser_x11.h" namespace ui { diff --git a/ui/display/chromeos/x11/display_util.cc b/ui/display/chromeos/x11/display_util.cc deleted file mode 100644 index 107d8be..0000000 --- a/ui/display/chromeos/x11/display_util.cc +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "ui/display/chromeos/x11/display_util.h" - -#include <X11/extensions/Xrandr.h> -#include <X11/Xatom.h> -#include <X11/Xlib.h> - -#include "base/macros.h" -#include "base/strings/string_util.h" -#include "base/x11/edid_parser_x11.h" - -namespace ui { - -namespace { - -struct OutputTypeMapping { - // Prefix of output name. - std::string name; - OutputType type; -}; - -const OutputTypeMapping kOutputTypeMapping[] = { - {"LVDS", OUTPUT_TYPE_INTERNAL}, - {"eDP", OUTPUT_TYPE_INTERNAL}, - {"DSI", OUTPUT_TYPE_INTERNAL}, - {"VGA", OUTPUT_TYPE_VGA}, - {"HDMI", OUTPUT_TYPE_HDMI}, - {"DVI", OUTPUT_TYPE_DVI}, - {"DP", OUTPUT_TYPE_DISPLAYPORT} -}; - -// Gets some useful data from the specified output device, such like -// manufacturer's ID, product code, and human readable name. Returns false if it -// fails to get those data and doesn't touch manufacturer ID/product code/name. -// NULL can be passed for unwanted output parameters. -bool GetOutputDeviceData(XID output, - uint16* manufacturer_id, - std::string* human_readable_name) { - unsigned long nitems = 0; - unsigned char* prop = NULL; - if (!base::GetEDIDProperty(output, &nitems, &prop)) - return false; - - bool result = base::ParseOutputDeviceData( - prop, nitems, manufacturer_id, human_readable_name); - XFree(prop); - return result; -} - -} // namespace - -OutputType GetOutputTypeFromName(const std::string& name) { - for (unsigned int i = 0; i < arraysize(kOutputTypeMapping); ++i) { - if (name.find(kOutputTypeMapping[i].name) == 0) { - return kOutputTypeMapping[i].type; - } - } - - return OUTPUT_TYPE_UNKNOWN; -} - -std::string GetDisplayName(RROutput output) { - std::string display_name; - GetOutputDeviceData(output, NULL, &display_name); - return display_name; -} - -bool GetOutputOverscanFlag(RROutput output, bool* flag) { - unsigned long nitems = 0; - unsigned char* prop = NULL; - if (!base::GetEDIDProperty(output, &nitems, &prop)) - return false; - - bool found = ParseOutputOverscanFlag(prop, nitems, flag); - XFree(prop); - return found; -} - -bool ParseOutputOverscanFlag(const unsigned char* prop, - unsigned long nitems, - bool* flag) { - // See http://en.wikipedia.org/wiki/Extended_display_identification_data - // for the extension format of EDID. Also see EIA/CEA-861 spec for - // the format of the extensions and how video capability is encoded. - // - byte 0: tag. should be 02h. - // - byte 1: revision. only cares revision 3 (03h). - // - byte 4-: data block. - const unsigned int kExtensionBase = 128; - const unsigned int kExtensionSize = 128; - const unsigned int kNumExtensionsOffset = 126; - const unsigned int kDataBlockOffset = 4; - const unsigned char kCEAExtensionTag = '\x02'; - const unsigned char kExpectedExtensionRevision = '\x03'; - const unsigned char kExtendedTag = 7; - const unsigned char kExtendedVideoCapabilityTag = 0; - const unsigned int kPTOverscan = 4; - const unsigned int kITOverscan = 2; - const unsigned int kCEOverscan = 0; - - if (nitems <= kNumExtensionsOffset) - return false; - - unsigned char num_extensions = prop[kNumExtensionsOffset]; - - for (size_t i = 0; i < num_extensions; ++i) { - // Skip parsing the whole extension if size is not enough. - if (nitems < kExtensionBase + (i + 1) * kExtensionSize) - break; - - const unsigned char* extension = prop + kExtensionBase + i * kExtensionSize; - unsigned char tag = extension[0]; - unsigned char revision = extension[1]; - if (tag != kCEAExtensionTag || revision != kExpectedExtensionRevision) - continue; - - unsigned char timing_descriptors_start = - std::min(extension[2], static_cast<unsigned char>(kExtensionSize)); - const unsigned char* data_block = extension + kDataBlockOffset; - while (data_block < extension + timing_descriptors_start) { - // A data block is encoded as: - // - byte 1 high 3 bits: tag. '07' for extended tags. - // - byte 1 remaining bits: the length of data block. - // - byte 2: the extended tag. '0' for video capability. - // - byte 3: the capability. - unsigned char tag = data_block[0] >> 5; - unsigned char payload_length = data_block[0] & 0x1f; - if (static_cast<unsigned long>(data_block + payload_length - prop) > - nitems) - break; - - if (tag != kExtendedTag || payload_length < 2) { - data_block += payload_length + 1; - continue; - } - - unsigned char extended_tag_code = data_block[1]; - if (extended_tag_code != kExtendedVideoCapabilityTag) { - data_block += payload_length + 1; - continue; - } - - // The difference between preferred, IT, and CE video formats - // doesn't matter. Sets |flag| to true if any of these flags are true. - if ((data_block[2] & (1 << kPTOverscan)) || - (data_block[2] & (1 << kITOverscan)) || - (data_block[2] & (1 << kCEOverscan))) { - *flag = true; - } else { - *flag = false; - } - return true; - } - } - - return false; -} - -} // namespace ui diff --git a/ui/display/chromeos/x11/display_util.h b/ui/display/chromeos/x11/display_util.h deleted file mode 100644 index 33d9545..0000000 --- a/ui/display/chromeos/x11/display_util.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef UI_DISPLAY_CHROMEOS_DISPLAY_UTIL_H_ -#define UI_DISPLAY_CHROMEOS_DISPLAY_UTIL_H_ - -#include <string> - -#include "ui/display/display_constants.h" -#include "ui/display/display_export.h" - -typedef unsigned long RROutput; - -namespace ui { - -// Returns the OutputType by matching known type prefixes to |name|. Returns -// OUTPUT_TYPE_UNKNOWN if no valid match. -DISPLAY_EXPORT OutputType GetOutputTypeFromName(const std::string& name); - -// Generate the human readable string from EDID obtained from |output|. -DISPLAY_EXPORT std::string GetDisplayName(RROutput output); - -// Gets the overscan flag from |output| and stores to |flag|. Returns true if -// the flag is found. Otherwise returns false and doesn't touch |flag|. The -// output will produce overscan if |flag| is set to true, but the output may -// still produce overscan even though it returns true and |flag| is set to -// false. -DISPLAY_EXPORT bool GetOutputOverscanFlag(RROutput output, bool* flag); - -DISPLAY_EXPORT bool ParseOutputOverscanFlag(const unsigned char* prop, - unsigned long nitems, - bool* flag); - -} // namespace ui - -#endif // UI_DISPLAY_CHROMEOS_DISPLAY_UTIL_H_ diff --git a/ui/display/chromeos/x11/display_util_x11.cc b/ui/display/chromeos/x11/display_util_x11.cc new file mode 100644 index 0000000..f5a4f27 --- /dev/null +++ b/ui/display/chromeos/x11/display_util_x11.cc @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/display/chromeos/x11/display_util_x11.h" + +#include "base/macros.h" + +namespace ui { + +namespace { + +struct OutputTypeMapping { + // Prefix of output name. + const char* name; + OutputType type; +}; + +const OutputTypeMapping kOutputTypeMapping[] = { + {"LVDS", OUTPUT_TYPE_INTERNAL}, + {"eDP", OUTPUT_TYPE_INTERNAL}, + {"DSI", OUTPUT_TYPE_INTERNAL}, + {"VGA", OUTPUT_TYPE_VGA}, + {"HDMI", OUTPUT_TYPE_HDMI}, + {"DVI", OUTPUT_TYPE_DVI}, + {"DP", OUTPUT_TYPE_DISPLAYPORT} +}; + +} // namespace + +OutputType GetOutputTypeFromName(const std::string& name) { + for (unsigned int i = 0; i < arraysize(kOutputTypeMapping); ++i) { + if (name.find(kOutputTypeMapping[i].name) == 0) { + return kOutputTypeMapping[i].type; + } + } + + return OUTPUT_TYPE_UNKNOWN; +} + +} // namespace ui diff --git a/ui/display/chromeos/x11/display_util_x11.h b/ui/display/chromeos/x11/display_util_x11.h new file mode 100644 index 0000000..c8a7837 --- /dev/null +++ b/ui/display/chromeos/x11/display_util_x11.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_DISPLAY_CHROMEOS_DISPLAY_UTIL_X11_H_ +#define UI_DISPLAY_CHROMEOS_DISPLAY_UTIL_X11_H_ + +#include <string> + +#include "ui/display/display_constants.h" +#include "ui/display/display_export.h" + +typedef unsigned long XID; +typedef XID RROutput; + +namespace ui { + +// Returns the OutputType by matching known type prefixes to |name|. Returns +// OUTPUT_TYPE_UNKNOWN if no valid match. +DISPLAY_EXPORT OutputType GetOutputTypeFromName(const std::string& name); + +} // namespace ui + +#endif // UI_DISPLAY_CHROMEOS_DISPLAY_UTIL_X11_H_ diff --git a/ui/display/chromeos/x11/display_util_x11_unittest.cc b/ui/display/chromeos/x11/display_util_x11_unittest.cc new file mode 100644 index 0000000..e178f7f --- /dev/null +++ b/ui/display/chromeos/x11/display_util_x11_unittest.cc @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/display/chromeos/x11/display_util_x11.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ui { + +TEST(DisplayUtilX11Test, GetOutputTypeFromName) { + EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("LVDS")); + EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("eDP")); + EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("DSI")); + EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("LVDSxx")); + EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("eDPzz")); + EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("DSIyy")); + + EXPECT_EQ(OUTPUT_TYPE_VGA, GetOutputTypeFromName("VGA")); + EXPECT_EQ(OUTPUT_TYPE_VGA, GetOutputTypeFromName("VGAxx")); + EXPECT_EQ(OUTPUT_TYPE_HDMI, GetOutputTypeFromName("HDMI")); + EXPECT_EQ(OUTPUT_TYPE_HDMI, GetOutputTypeFromName("HDMIyy")); + EXPECT_EQ(OUTPUT_TYPE_DVI, GetOutputTypeFromName("DVI")); + EXPECT_EQ(OUTPUT_TYPE_DVI, GetOutputTypeFromName("DVIzz")); + EXPECT_EQ(OUTPUT_TYPE_DISPLAYPORT, GetOutputTypeFromName("DP")); + EXPECT_EQ(OUTPUT_TYPE_DISPLAYPORT, GetOutputTypeFromName("DPww")); + + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("xyz")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("abcLVDS")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("cdeeDP")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("abcDSI")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("LVD")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("eD")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("DS")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("VG")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("HDM")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("DV")); + EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("D")); +} + +} // namespace ui diff --git a/ui/display/chromeos/x11/native_display_delegate_x11.cc b/ui/display/chromeos/x11/native_display_delegate_x11.cc index b56146e..e4bfbfc 100644 --- a/ui/display/chromeos/x11/native_display_delegate_x11.cc +++ b/ui/display/chromeos/x11/native_display_delegate_x11.cc @@ -16,12 +16,12 @@ #include "base/message_loop/message_loop.h" #include "base/message_loop/message_pump_x11.h" #include "base/stl_util.h" -#include "base/x11/edid_parser_x11.h" #include "ui/display/chromeos/native_display_observer.h" #include "ui/display/chromeos/x11/display_mode_x11.h" #include "ui/display/chromeos/x11/display_snapshot_x11.h" -#include "ui/display/chromeos/x11/display_util.h" +#include "ui/display/chromeos/x11/display_util_x11.h" #include "ui/display/chromeos/x11/native_display_event_dispatcher_x11.h" +#include "ui/display/x11/edid_parser_x11.h" #include "ui/gfx/x/x11_error_tracker.h" namespace ui { @@ -343,7 +343,8 @@ DisplaySnapshotX11* NativeDisplayDelegateX11::InitDisplaySnapshot( RRCrtc* last_used_crtc, int index) { int64_t display_id = 0; - bool has_display_id = base::GetDisplayId(id, index, &display_id); + bool has_display_id = GetDisplayId( + id, static_cast<uint8>(index), &display_id); OutputType type = GetOutputTypeFromName(info->name); if (type == OUTPUT_TYPE_UNKNOWN) diff --git a/ui/display/display.gyp b/ui/display/display.gyp index 5f766ac..d11fa4f 100644 --- a/ui/display/display.gyp +++ b/ui/display/display.gyp @@ -27,12 +27,12 @@ 'chromeos/native_display_observer.h', 'chromeos/output_configurator.cc', 'chromeos/output_configurator.h', - 'chromeos/x11/display_util.cc', - 'chromeos/x11/display_util.h', 'chromeos/x11/display_mode_x11.cc', 'chromeos/x11/display_mode_x11.h', 'chromeos/x11/display_snapshot_x11.cc', 'chromeos/x11/display_snapshot_x11.h', + 'chromeos/x11/display_util_x11.cc', + 'chromeos/x11/display_util_x11.h', 'chromeos/x11/native_display_delegate_x11.cc', 'chromeos/x11/native_display_delegate_x11.h', 'chromeos/x11/native_display_event_dispatcher_x11.cc', @@ -41,6 +41,10 @@ 'chromeos/x11/touchscreen_delegate_x11.h', 'display_constants.h', 'display_export.h', + 'edid_parser.cc', + 'edid_parser.h', + 'x11/edid_parser_x11.cc', + 'x11/edid_parser_x11.h', ], 'conditions': [ ['use_x11 == 1', { diff --git a/ui/display/display_unittests.gypi b/ui/display/display_unittests.gypi index 6597bb9..591bde5 100644 --- a/ui/display/display_unittests.gypi +++ b/ui/display/display_unittests.gypi @@ -8,11 +8,13 @@ 'dependencies': [ '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', + '../ui/display/display.gyp:display', '../ui/gfx/gfx.gyp:gfx_geometry', ], 'sources': [ + 'edid_parser_unittest.cc', 'chromeos/output_configurator_unittest.cc', - 'chromeos/x11/display_util_unittest.cc', + 'chromeos/x11/display_util_x11_unittest.cc', 'chromeos/x11/native_display_event_dispatcher_x11_unittest.cc', ], 'conditions': [ @@ -21,7 +23,6 @@ # not like empty libraries. ['chromeos == 1', { 'dependencies': [ - '../ui/display/display.gyp:display', '../ui/display/display.gyp:display_test_util', ], }], diff --git a/ui/display/edid_parser.cc b/ui/display/edid_parser.cc new file mode 100644 index 0000000..0660bdb --- /dev/null +++ b/ui/display/edid_parser.cc @@ -0,0 +1,208 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/display/edid_parser.h" + +#include <algorithm> + +#include "base/hash.h" +#include "base/strings/string_util.h" +#include "base/sys_byteorder.h" + +namespace ui { + +namespace { + +// Returns 64-bit persistent ID for the specified manufacturer's ID and +// product_code_hash, and the index of the output it is connected to. +// |output_index| is used to distinguish the displays of the same type. For +// example, swapping two identical display between two outputs will not be +// treated as swap. The 'serial number' field in EDID isn't used here because +// it is not guaranteed to have unique number and it may have the same fixed +// value (like 0). +int64 GetID(uint16 manufacturer_id, + uint32 product_code_hash, + uint8 output_index) { + return ((static_cast<int64>(manufacturer_id) << 40) | + (static_cast<int64>(product_code_hash) << 8) | output_index); +} + +} // namespace + +bool GetDisplayIdFromEDID(const unsigned char* prop, + unsigned long nitems, + uint8 output_index, + int64* display_id_out) { + uint16 manufacturer_id = 0; + std::string product_name; + + // ParseOutputDeviceData fails if it doesn't have product_name. + ParseOutputDeviceData(prop, nitems, &manufacturer_id, &product_name); + + // Generates product specific value from product_name instead of product code. + // See crbug.com/240341 + uint32 product_code_hash = product_name.empty() ? + 0 : base::Hash(product_name); + if (manufacturer_id != 0) { + // An ID based on display's index will be assigned later if this call + // fails. + *display_id_out = GetID( + manufacturer_id, product_code_hash, output_index); + return true; + } + return false; +} + +bool ParseOutputDeviceData(const unsigned char* prop, + unsigned long nitems, + uint16* manufacturer_id, + std::string* human_readable_name) { + // See http://en.wikipedia.org/wiki/Extended_display_identification_data + // for the details of EDID data format. We use the following data: + // bytes 8-9: manufacturer EISA ID, in big-endian + // bytes 54-125: four descriptors (18-bytes each) which may contain + // the display name. + const unsigned int kManufacturerOffset = 8; + const unsigned int kManufacturerLength = 2; + const unsigned int kDescriptorOffset = 54; + const unsigned int kNumDescriptors = 4; + const unsigned int kDescriptorLength = 18; + // The specifier types. + const unsigned char kMonitorNameDescriptor = 0xfc; + + if (manufacturer_id) { + if (nitems < kManufacturerOffset + kManufacturerLength) { + LOG(ERROR) << "too short EDID data: manifacturer id"; + return false; + } + + *manufacturer_id = + *reinterpret_cast<const uint16*>(prop + kManufacturerOffset); +#if defined(ARCH_CPU_LITTLE_ENDIAN) + *manufacturer_id = base::ByteSwap(*manufacturer_id); +#endif + } + + if (!human_readable_name) + return true; + + human_readable_name->clear(); + for (unsigned int i = 0; i < kNumDescriptors; ++i) { + if (nitems < kDescriptorOffset + (i + 1) * kDescriptorLength) + break; + + const unsigned char* desc_buf = + prop + kDescriptorOffset + i * kDescriptorLength; + // If the descriptor contains the display name, it has the following + // structure: + // bytes 0-2, 4: \0 + // byte 3: descriptor type, defined above. + // bytes 5-17: text data, ending with \r, padding with spaces + // we should check bytes 0-2 and 4, since it may have other values in + // case that the descriptor contains other type of data. + if (desc_buf[0] == 0 && desc_buf[1] == 0 && desc_buf[2] == 0 && + desc_buf[4] == 0) { + if (desc_buf[3] == kMonitorNameDescriptor) { + std::string found_name( + reinterpret_cast<const char*>(desc_buf + 5), kDescriptorLength - 5); + base::TrimWhitespaceASCII( + found_name, base::TRIM_TRAILING, human_readable_name); + break; + } + } + } + + // Verify if the |human_readable_name| consists of printable characters only. + for (size_t i = 0; i < human_readable_name->size(); ++i) { + char c = (*human_readable_name)[i]; + if (!isascii(c) || !isprint(c)) { + human_readable_name->clear(); + LOG(ERROR) << "invalid EDID: human unreadable char in name"; + return false; + } + } + + return true; +} + +bool ParseOutputOverscanFlag(const unsigned char* prop, + unsigned long nitems, + bool* flag) { + // See http://en.wikipedia.org/wiki/Extended_display_identification_data + // for the extension format of EDID. Also see EIA/CEA-861 spec for + // the format of the extensions and how video capability is encoded. + // - byte 0: tag. should be 02h. + // - byte 1: revision. only cares revision 3 (03h). + // - byte 4-: data block. + const unsigned int kExtensionBase = 128; + const unsigned int kExtensionSize = 128; + const unsigned int kNumExtensionsOffset = 126; + const unsigned int kDataBlockOffset = 4; + const unsigned char kCEAExtensionTag = '\x02'; + const unsigned char kExpectedExtensionRevision = '\x03'; + const unsigned char kExtendedTag = 7; + const unsigned char kExtendedVideoCapabilityTag = 0; + const unsigned int kPTOverscan = 4; + const unsigned int kITOverscan = 2; + const unsigned int kCEOverscan = 0; + + if (nitems <= kNumExtensionsOffset) + return false; + + unsigned char num_extensions = prop[kNumExtensionsOffset]; + + for (size_t i = 0; i < num_extensions; ++i) { + // Skip parsing the whole extension if size is not enough. + if (nitems < kExtensionBase + (i + 1) * kExtensionSize) + break; + + const unsigned char* extension = prop + kExtensionBase + i * kExtensionSize; + unsigned char tag = extension[0]; + unsigned char revision = extension[1]; + if (tag != kCEAExtensionTag || revision != kExpectedExtensionRevision) + continue; + + unsigned char timing_descriptors_start = + std::min(extension[2], static_cast<unsigned char>(kExtensionSize)); + const unsigned char* data_block = extension + kDataBlockOffset; + while (data_block < extension + timing_descriptors_start) { + // A data block is encoded as: + // - byte 1 high 3 bits: tag. '07' for extended tags. + // - byte 1 remaining bits: the length of data block. + // - byte 2: the extended tag. '0' for video capability. + // - byte 3: the capability. + unsigned char tag = data_block[0] >> 5; + unsigned char payload_length = data_block[0] & 0x1f; + if (static_cast<unsigned long>(data_block + payload_length - prop) > + nitems) + break; + + if (tag != kExtendedTag || payload_length < 2) { + data_block += payload_length + 1; + continue; + } + + unsigned char extended_tag_code = data_block[1]; + if (extended_tag_code != kExtendedVideoCapabilityTag) { + data_block += payload_length + 1; + continue; + } + + // The difference between preferred, IT, and CE video formats + // doesn't matter. Sets |flag| to true if any of these flags are true. + if ((data_block[2] & (1 << kPTOverscan)) || + (data_block[2] & (1 << kITOverscan)) || + (data_block[2] & (1 << kCEOverscan))) { + *flag = true; + } else { + *flag = false; + } + return true; + } + } + + return false; +} + +} // namespace ui diff --git a/ui/display/edid_parser.h b/ui/display/edid_parser.h new file mode 100644 index 0000000..38361cd --- /dev/null +++ b/ui/display/edid_parser.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_DISPLAY_EDID_PARSER_H_ +#define UI_DISPLAY_EDID_PARSER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "ui/display/display_export.h" + +// EDID (Extended Display Identification Data) is a format for monitor +// metadata. This provides a parser for the data. + +namespace ui { + +// Generates the display id for the pair of |prop| with |nitems| length and +// |index|, and store in |display_id_out|. Returns true if the display id is +// successfully generated, or false otherwise. +DISPLAY_EXPORT bool GetDisplayIdFromEDID(const unsigned char* prop, + unsigned long nitems, + uint8 index, + int64* display_id_out); + +// Parses |prop| as EDID data and stores extracted data into |manufacturer_id| +// and |human_readable_name| and returns true. NULL can be passed for unwanted +// output parameters. Some devices (especially internal displays) may not have +// the field for |human_readable_name|, and it will return true in that case. +DISPLAY_EXPORT bool ParseOutputDeviceData(const unsigned char* prop, + unsigned long nitems, + uint16* manufacturer_id, + std::string* human_readable_name); + +DISPLAY_EXPORT bool ParseOutputOverscanFlag(const unsigned char* prop, + unsigned long nitems, + bool* flag); + +} // namespace ui + +#endif // UI_DISPLAY_EDID_PARSER_H_ diff --git a/ui/display/chromeos/x11/display_util_unittest.cc b/ui/display/edid_parser_unittest.cc index 03a6af5..8026df6 100644 --- a/ui/display/chromeos/x11/display_util_unittest.cc +++ b/ui/display/edid_parser_unittest.cc @@ -2,13 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/display/chromeos/x11/display_util.h" +#include "ui/display/edid_parser.h" #include "base/memory/scoped_ptr.h" #include "testing/gtest/include/gtest/gtest.h" -#include <X11/extensions/Xrandr.h> - namespace ui { namespace { @@ -57,7 +55,7 @@ const unsigned char kOverscanDisplay[] = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6"; // The EDID info misdetecting overscan once. see crbug.com/226318 -const unsigned char kMisdetecedDisplay[] = +const unsigned char kMisdetectedDisplay[] = "\x00\xff\xff\xff\xff\xff\xff\x00\x10\xac\x64\x40\x4c\x30\x30\x32" "\x0c\x15\x01\x03\x80\x40\x28\x78\xea\x8d\x85\xad\x4f\x35\xb1\x25" "\x0e\x50\x54\xa5\x4b\x00\x71\x4f\x81\x00\x81\x80\xd1\x00\xa9\x40" @@ -75,9 +73,29 @@ const unsigned char kMisdetecedDisplay[] = "\x0a\xd0\x8a\x20\xe0\x2d\x10\x10\x3e\x96\x00\x81\x91\x21\x00\x00" "\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94"; +const unsigned char kLP2565A[] = + "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x22\xF0\x76\x26\x01\x01\x01\x01" + "\x02\x12\x01\x03\x80\x34\x21\x78\xEE\xEF\x95\xA3\x54\x4C\x9B\x26" + "\x0F\x50\x54\xA5\x6B\x80\x81\x40\x81\x80\x81\x99\x71\x00\xA9\x00" + "\xA9\x40\xB3\x00\xD1\x00\x28\x3C\x80\xA0\x70\xB0\x23\x40\x30\x20" + "\x36\x00\x07\x44\x21\x00\x00\x1A\x00\x00\x00\xFD\x00\x30\x55\x1E" + "\x5E\x11\x00\x0A\x20\x20\x20\x20\x20\x20\x00\x00\x00\xFC\x00\x48" + "\x50\x20\x4C\x50\x32\x34\x36\x35\x0A\x20\x20\x20\x00\x00\x00\xFF" + "\x00\x43\x4E\x4B\x38\x30\x32\x30\x34\x48\x4D\x0A\x20\x20\x00\xA4"; + +const unsigned char kLP2565B[] = + "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x22\xF0\x75\x26\x01\x01\x01\x01" + "\x02\x12\x01\x03\x6E\x34\x21\x78\xEE\xEF\x95\xA3\x54\x4C\x9B\x26" + "\x0F\x50\x54\xA5\x6B\x80\x81\x40\x71\x00\xA9\x00\xA9\x40\xA9\x4F" + "\xB3\x00\xD1\xC0\xD1\x00\x28\x3C\x80\xA0\x70\xB0\x23\x40\x30\x20" + "\x36\x00\x07\x44\x21\x00\x00\x1A\x00\x00\x00\xFD\x00\x30\x55\x1E" + "\x5E\x15\x00\x0A\x20\x20\x20\x20\x20\x20\x00\x00\x00\xFC\x00\x48" + "\x50\x20\x4C\x50\x32\x34\x36\x35\x0A\x20\x20\x20\x00\x00\x00\xFF" + "\x00\x43\x4E\x4B\x38\x30\x32\x30\x34\x48\x4D\x0A\x20\x20\x00\x45"; + } // namespace -TEST(DisplayUtilTest, ParseOverscanFlag) { +TEST(EDIDParserTest, ParseOverscanFlag) { bool flag = false; EXPECT_FALSE( ParseOutputOverscanFlag(kNormalDisplay, charsize(kNormalDisplay), &flag)); @@ -93,7 +111,7 @@ TEST(DisplayUtilTest, ParseOverscanFlag) { flag = false; EXPECT_FALSE(ParseOutputOverscanFlag( - kMisdetecedDisplay, charsize(kMisdetecedDisplay), &flag)); + kMisdetectedDisplay, charsize(kMisdetectedDisplay), &flag)); flag = false; // Copy |kOverscanDisplay| and set flags to false in it. The overscan flags @@ -109,7 +127,7 @@ TEST(DisplayUtilTest, ParseOverscanFlag) { EXPECT_FALSE(flag); } -TEST(DisplayUtilTest, ParseBrokenOverscanData) { +TEST(EDIDParserTest, ParseBrokenOverscanData) { // Do not fill valid data here because it anyway fails to parse the data. scoped_ptr<unsigned char[]> data(new unsigned char[126]); bool flag = false; @@ -124,34 +142,90 @@ TEST(DisplayUtilTest, ParseBrokenOverscanData) { EXPECT_FALSE(ParseOutputOverscanFlag(data.get(), 150, &flag)); } -TEST(DisplayUtilTest, GetOutputTypeFromName) { - EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("LVDS")); - EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("eDP")); - EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("DSI")); - EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("LVDSxx")); - EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("eDPzz")); - EXPECT_EQ(OUTPUT_TYPE_INTERNAL, GetOutputTypeFromName("DSIyy")); - - EXPECT_EQ(OUTPUT_TYPE_VGA, GetOutputTypeFromName("VGA")); - EXPECT_EQ(OUTPUT_TYPE_VGA, GetOutputTypeFromName("VGAxx")); - EXPECT_EQ(OUTPUT_TYPE_HDMI, GetOutputTypeFromName("HDMI")); - EXPECT_EQ(OUTPUT_TYPE_HDMI, GetOutputTypeFromName("HDMIyy")); - EXPECT_EQ(OUTPUT_TYPE_DVI, GetOutputTypeFromName("DVI")); - EXPECT_EQ(OUTPUT_TYPE_DVI, GetOutputTypeFromName("DVIzz")); - EXPECT_EQ(OUTPUT_TYPE_DISPLAYPORT, GetOutputTypeFromName("DP")); - EXPECT_EQ(OUTPUT_TYPE_DISPLAYPORT, GetOutputTypeFromName("DPww")); - - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("xyz")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("abcLVDS")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("cdeeDP")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("abcDSI")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("LVD")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("eD")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("DS")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("VG")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("HDM")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("DV")); - EXPECT_EQ(OUTPUT_TYPE_UNKNOWN, GetOutputTypeFromName("D")); +TEST(EDIDParserTest, ParseEDID) { + uint16 manufacturer_id = 0; + std::string human_readable_name; + EXPECT_TRUE(ParseOutputDeviceData( + kNormalDisplay, charsize(kNormalDisplay), + &manufacturer_id, &human_readable_name)); + EXPECT_EQ(0x22f0u, manufacturer_id); + EXPECT_EQ("HP ZR30w", human_readable_name); + + manufacturer_id = 0; + human_readable_name.clear(); + EXPECT_TRUE(ParseOutputDeviceData( + kInternalDisplay, charsize(kInternalDisplay), + &manufacturer_id, NULL)); + EXPECT_EQ(0x4ca3u, manufacturer_id); + EXPECT_EQ("", human_readable_name); + + // Internal display doesn't have name. + EXPECT_TRUE(ParseOutputDeviceData( + kInternalDisplay, charsize(kInternalDisplay), + NULL, &human_readable_name)); + EXPECT_TRUE(human_readable_name.empty()); + + manufacturer_id = 0; + human_readable_name.clear(); + EXPECT_TRUE(ParseOutputDeviceData( + kOverscanDisplay, charsize(kOverscanDisplay), + &manufacturer_id, &human_readable_name)); + EXPECT_EQ(0x4c2du, manufacturer_id); + EXPECT_EQ("SAMSUNG", human_readable_name); +} + +TEST(EDIDParserTest, ParseBrokenEDID) { + uint16 manufacturer_id = 0; + std::string human_readable_name; + + // length == 0 + EXPECT_FALSE(ParseOutputDeviceData( + kNormalDisplay, 0, + &manufacturer_id, &human_readable_name)); + + // name is broken. Copying kNormalDisplay and substitute its name data by + // some control code. + std::string display_data( + reinterpret_cast<const char*>(kNormalDisplay), charsize(kNormalDisplay)); + + // display's name data is embedded in byte 95-107 in this specific example. + // Fix here too when the contents of kNormalDisplay is altered. + display_data[97] = '\x1b'; + EXPECT_FALSE(ParseOutputDeviceData( + reinterpret_cast<const unsigned char*>(display_data.data()), + display_data.size(), + &manufacturer_id, &human_readable_name)); + + // If |human_readable_name| isn't specified, it skips parsing the name. + manufacturer_id = 0; + EXPECT_TRUE(ParseOutputDeviceData( + reinterpret_cast<const unsigned char*>(display_data.data()), + display_data.size(), + &manufacturer_id, NULL)); + EXPECT_EQ(0x22f0u, manufacturer_id); +} + +TEST(EDIDParserTest, GetDisplayId) { + // EDID of kLP2565A and B are slightly different but actually the same device. + int64 id1 = -1; + int64 id2 = -1; + EXPECT_TRUE(GetDisplayIdFromEDID(kLP2565A, charsize(kLP2565A), 0, &id1)); + EXPECT_TRUE(GetDisplayIdFromEDID(kLP2565B, charsize(kLP2565B), 0, &id2)); + EXPECT_EQ(id1, id2); + EXPECT_NE(-1, id1); +} + +TEST(EDIDParserTest, GetDisplayIdFromInternal) { + int64 id = -1; + EXPECT_TRUE(GetDisplayIdFromEDID( + kInternalDisplay, charsize(kInternalDisplay), 0, &id)); + EXPECT_NE(-1, id); +} + +TEST(EDIDParserTest, GetDisplayIdFailure) { + int64 id = -1; + EXPECT_FALSE(GetDisplayIdFromEDID(NULL, 0, 0, &id)); + EXPECT_EQ(-1, id); } -} // namespace ui +} // namespace ui diff --git a/ui/display/x11/edid_parser_x11.cc b/ui/display/x11/edid_parser_x11.cc new file mode 100644 index 0000000..146c5a7 --- /dev/null +++ b/ui/display/x11/edid_parser_x11.cc @@ -0,0 +1,121 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/display/x11/edid_parser_x11.h" + +#include <X11/extensions/Xrandr.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> + +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "ui/display/edid_parser.h" + +namespace ui { + +namespace { + +// Gets some useful data from the specified output device, such like +// manufacturer's ID, product code, and human readable name. Returns false if it +// fails to get those data and doesn't touch manufacturer ID/product code/name. +// NULL can be passed for unwanted output parameters. +bool GetOutputDeviceData(XID output, + uint16* manufacturer_id, + std::string* human_readable_name) { + unsigned long nitems = 0; + unsigned char* prop = NULL; + if (!GetEDIDProperty(output, &nitems, &prop)) + return false; + + bool result = ParseOutputDeviceData( + prop, nitems, manufacturer_id, human_readable_name); + XFree(prop); + return result; +} + +bool IsRandRAvailable() { + int randr_version_major = 0; + int randr_version_minor = 0; + static bool is_randr_available = XRRQueryVersion( + base::MessagePumpX11::GetDefaultXDisplay(), + &randr_version_major, &randr_version_minor); + return is_randr_available; +} + +} // namespace + +bool GetEDIDProperty(XID output, unsigned long* nitems, unsigned char** prop) { + if (!IsRandRAvailable()) + return false; + + Display* display = base::MessagePumpX11::GetDefaultXDisplay(); + + static Atom edid_property = XInternAtom( + base::MessagePumpX11::GetDefaultXDisplay(), + RR_PROPERTY_RANDR_EDID, false); + + bool has_edid_property = false; + int num_properties = 0; + Atom* properties = XRRListOutputProperties(display, output, &num_properties); + for (int i = 0; i < num_properties; ++i) { + if (properties[i] == edid_property) { + has_edid_property = true; + break; + } + } + XFree(properties); + if (!has_edid_property) + return false; + + Atom actual_type; + int actual_format; + unsigned long bytes_after; + XRRGetOutputProperty(display, + output, + edid_property, + 0, // offset + 128, // length + false, // _delete + false, // pending + AnyPropertyType, // req_type + &actual_type, + &actual_format, + nitems, + &bytes_after, + prop); + DCHECK_EQ(XA_INTEGER, actual_type); + DCHECK_EQ(8, actual_format); + return true; +} + +bool GetDisplayId(XID output_id, uint8 output_index, int64* display_id_out) { + unsigned long nitems = 0; + unsigned char* prop = NULL; + if (!GetEDIDProperty(output_id, &nitems, &prop)) + return false; + + bool result = + GetDisplayIdFromEDID(prop, nitems, output_index, display_id_out); + XFree(prop); + return result; +} + +std::string GetDisplayName(RROutput output) { + std::string display_name; + GetOutputDeviceData(output, NULL, &display_name); + return display_name; +} + +bool GetOutputOverscanFlag(RROutput output, bool* flag) { + unsigned long nitems = 0; + unsigned char* prop = NULL; + if (!GetEDIDProperty(output, &nitems, &prop)) + return false; + + bool found = ParseOutputOverscanFlag(prop, nitems, flag); + XFree(prop); + return found; +} + +} // namespace ui diff --git a/ui/display/x11/edid_parser_x11.h b/ui/display/x11/edid_parser_x11.h new file mode 100644 index 0000000..730baae --- /dev/null +++ b/ui/display/x11/edid_parser_x11.h @@ -0,0 +1,48 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_DISPLAY_X11_EDID_PARSER_X11_H_ +#define UI_DISPLAY_X11_EDID_PARSER_X11_H_ + +#include <string> + +#include "base/basictypes.h" +#include "ui/display/display_constants.h" +#include "ui/display/display_export.h" + +typedef unsigned long XID; +typedef XID RROutput; + +// Xrandr utility functions to help get EDID information. + +namespace ui { + +// Get the EDID data from the |output| and stores to |prop|. |nitem| will store +// the number of characters |prop| will have. It doesn't take the ownership of +// |prop|, so caller must release it by XFree(). +// Returns true if EDID property is successfully obtained. Otherwise returns +// false and does not touch |prop| and |nitems|. +DISPLAY_EXPORT bool GetEDIDProperty(XID output, + unsigned long* nitems, + unsigned char** prop); + +// Gets the EDID data from |output| and generates the display id through +// |GetDisplayIdFromEDID|. +DISPLAY_EXPORT bool GetDisplayId(XID output, + uint8 index, + int64* display_id_out); + +// Generate the human readable string from EDID obtained from |output|. +DISPLAY_EXPORT std::string GetDisplayName(RROutput output); + +// Gets the overscan flag from |output| and stores to |flag|. Returns true if +// the flag is found. Otherwise returns false and doesn't touch |flag|. The +// output will produce overscan if |flag| is set to true, but the output may +// still produce overscan even though it returns true and |flag| is set to +// false. +DISPLAY_EXPORT bool GetOutputOverscanFlag(RROutput output, bool* flag); + +} // namespace ui + +#endif // UI_DISPLAY_X11_EDID_PARSER_X11_H_ diff --git a/ui/views/DEPS b/ui/views/DEPS index 065ceac..b29fbd8 100644 --- a/ui/views/DEPS +++ b/ui/views/DEPS @@ -8,6 +8,7 @@ include_rules = [ "+ui/aura", "+ui/base", "+ui/compositor", + "+ui/display", "+ui/events", "+ui/gfx", "+ui/gl/gl_surface.h", # To initialize GL for tests. diff --git a/ui/views/views.gyp b/ui/views/views.gyp index a099de7..7295a4a 100644 --- a/ui/views/views.gyp +++ b/ui/views/views.gyp @@ -433,6 +433,11 @@ 'bubble/tray_bubble_view.h', ], }], + ['chromeos==0 and use_x11==1', { + 'dependencies': [ + '../display/display.gyp:display', + ], + }], ['OS=="linux" and chromeos==0', { 'dependencies': [ '../shell_dialogs/shell_dialogs.gyp:shell_dialogs', diff --git a/ui/views/widget/desktop_aura/desktop_screen_x11.cc b/ui/views/widget/desktop_aura/desktop_screen_x11.cc index f5a6a7f..970d86c 100644 --- a/ui/views/widget/desktop_aura/desktop_screen_x11.cc +++ b/ui/views/widget/desktop_aura/desktop_screen_x11.cc @@ -12,12 +12,12 @@ #include "base/debug/trace_event.h" #include "base/logging.h" -#include "base/x11/edid_parser_x11.h" #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/aura/window_tree_host.h" #include "ui/base/layout.h" #include "ui/base/x/x11_util.h" +#include "ui/display/x11/edid_parser_x11.h" #include "ui/gfx/display.h" #include "ui/gfx/display_observer.h" #include "ui/gfx/native_widget_types.h" @@ -376,7 +376,7 @@ std::vector<gfx::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() { output_info->crtc); int64 display_id = -1; - if (!base::GetDisplayId(output_id, i, &display_id)) { + if (!ui::GetDisplayId(output_id, static_cast<uint8>(i), &display_id)) { // It isn't ideal, but if we can't parse the EDID data, fallback on the // display number. display_id = i; |