diff options
Diffstat (limited to 'chromeos/display')
-rw-r--r-- | chromeos/display/output_configurator.cc | 22 | ||||
-rw-r--r-- | chromeos/display/output_configurator.h | 15 | ||||
-rw-r--r-- | chromeos/display/output_configurator_unittest.cc | 36 | ||||
-rw-r--r-- | chromeos/display/output_util.cc | 319 | ||||
-rw-r--r-- | chromeos/display/output_util.h | 56 | ||||
-rw-r--r-- | chromeos/display/output_util_unittest.cc | 208 | ||||
-rw-r--r-- | chromeos/display/real_output_configurator_delegate.cc | 7 |
7 files changed, 626 insertions, 37 deletions
diff --git a/chromeos/display/output_configurator.cc b/chromeos/display/output_configurator.cc index cca82e7..fab354e 100644 --- a/chromeos/display/output_configurator.cc +++ b/chromeos/display/output_configurator.cc @@ -12,16 +12,13 @@ #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/time.h" +#include "chromeos/display/output_util.h" #include "chromeos/display/real_output_configurator_delegate.h" namespace chromeos { namespace { -// Prefixes for the built-in displays. -const char kInternal_LVDS[] = "LVDS"; -const char kInternal_eDP[] = "eDP"; - // The delay to perform configuration after RRNotify. See the comment // in |Dispatch()|. const int64 kConfigureDelayMs = 500; @@ -93,7 +90,8 @@ OutputConfigurator::OutputSnapshot::OutputSnapshot() is_internal(false), is_aspect_preserving_scaling(false), touch_device_id(0), - index(0) {} + display_id(0), + has_display_id(false) {} OutputConfigurator::CoordinateTransformation::CoordinateTransformation() : x_scale(1.0), @@ -142,11 +140,6 @@ bool OutputConfigurator::TestApi::SendOutputChangeEvents(bool connected) { return true; } -// static -bool OutputConfigurator::IsInternalOutputName(const std::string& name) { - return name.find(kInternal_LVDS) == 0 || name.find(kInternal_eDP) == 0; -} - OutputConfigurator::OutputConfigurator() : state_controller_(NULL), configure_display_(base::chromeos::IsRunningOnChromeOS()), @@ -512,7 +505,14 @@ OutputState OutputConfigurator::GetOutputState( } else { // With either both outputs on or both outputs off, use one of the // dual modes. - return state_controller_->GetStateForOutputs(outputs); + std::vector<int64> display_ids; + for (size_t i = 0; i < outputs.size(); ++i) { + // If display id isn't available, switch to extended mode. + if (!outputs[i].has_display_id) + return STATE_DUAL_EXTENDED; + display_ids.push_back(outputs[i].display_id); + } + return state_controller_->GetStateForDisplayIds(display_ids); } } default: diff --git a/chromeos/display/output_configurator.h b/chromeos/display/output_configurator.h index 43060ac..84de2d0 100644 --- a/chromeos/display/output_configurator.h +++ b/chromeos/display/output_configurator.h @@ -6,6 +6,7 @@ #define CHROMEOS_DISPLAY_OUTPUT_CONFIGURATOR_H_ #include <map> +#include <string> #include <vector> #include "base/basictypes.h" @@ -62,9 +63,10 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { // XInput device ID or 0 if this output isn't a touchscreen. int touch_device_id; - // TODO(oshima): Move xrandr related functions to here - // from ui/base/x and replace this with display id. - int index; + // Display id for this output. + int64 display_id; + + bool has_display_id; }; struct CoordinateTransformation { @@ -108,8 +110,8 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { virtual ~StateController() {} // Called when displays are detected. - virtual OutputState GetStateForOutputs( - const std::vector<OutputSnapshot>& outputs) const = 0; + virtual OutputState GetStateForDisplayIds( + const std::vector<int64>& display_ids) const = 0; }; // Interface for classes that perform actions on behalf of OutputController. @@ -216,9 +218,6 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { // See crbug.com/130188 for initial discussion. static const int kVerticalGap = 60; - // Returns true if an output named |name| is an internal display. - static bool IsInternalOutputName(const std::string& name); - OutputConfigurator(); virtual ~OutputConfigurator(); diff --git a/chromeos/display/output_configurator_unittest.cc b/chromeos/display/output_configurator_unittest.cc index 3d4fbfc..8f69ce7 100644 --- a/chromeos/display/output_configurator_unittest.cc +++ b/chromeos/display/output_configurator_unittest.cc @@ -208,8 +208,8 @@ class TestStateController : public OutputConfigurator::StateController { void set_state(OutputState state) { state_ = state; } // OutputConfigurator::StateController overrides: - virtual OutputState GetStateForOutputs( - const OutputSnapshotList& outputs) const OVERRIDE { return state_; } + virtual OutputState GetStateForDisplayIds( + const std::vector<int64>& outputs) const OVERRIDE { return state_; } private: OutputState state_; @@ -240,6 +240,7 @@ class OutputConfiguratorTest : public testing::Test { o->is_internal = true; o->is_aspect_preserving_scaling = true; o->touch_device_id = 0; + o->has_display_id = true; o = &outputs_[1]; o->output = 2; @@ -252,6 +253,7 @@ class OutputConfiguratorTest : public testing::Test { o->is_internal = false; o->is_aspect_preserving_scaling = true; o->touch_device_id = 0; + o->has_display_id = true; UpdateOutputs(2); delegate_->AddMode(kSmallModeId, kSmallModeWidth, kSmallModeHeight, false); @@ -309,19 +311,6 @@ class OutputConfiguratorTest : public testing::Test { } // namespace -TEST_F(OutputConfiguratorTest, IsInternalOutputName) { - EXPECT_TRUE(OutputConfigurator::IsInternalOutputName("LVDS")); - EXPECT_TRUE(OutputConfigurator::IsInternalOutputName("eDP")); - EXPECT_TRUE(OutputConfigurator::IsInternalOutputName("LVDSxx")); - EXPECT_TRUE(OutputConfigurator::IsInternalOutputName("eDPzz")); - - EXPECT_FALSE(OutputConfigurator::IsInternalOutputName("xyz")); - EXPECT_FALSE(OutputConfigurator::IsInternalOutputName("abcLVDS")); - EXPECT_FALSE(OutputConfigurator::IsInternalOutputName("cdeeDP")); - EXPECT_FALSE(OutputConfigurator::IsInternalOutputName("LVD")); - EXPECT_FALSE(OutputConfigurator::IsInternalOutputName("eD")); -} - TEST_F(OutputConfiguratorTest, ConnectSecondOutput) { InitWithSingleOutput(); @@ -594,4 +583,21 @@ TEST_F(OutputConfiguratorTest, InvalidOutputStates) { EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED)); } +TEST_F(OutputConfiguratorTest, GetOutputStateForDisplays) { + outputs_[0].has_display_id = false; + UpdateOutputs(2); + + configurator_.Init(false, 0); + configurator_.Start(); + + state_controller_.set_state(STATE_DUAL_MIRROR); + test_api_.SendOutputChangeEvents(true); + EXPECT_EQ(STATE_DUAL_EXTENDED, configurator_.output_state()); + + outputs_[0].has_display_id = true; + UpdateOutputs(2); + test_api_.SendOutputChangeEvents(true); + EXPECT_EQ(STATE_DUAL_MIRROR, configurator_.output_state()); +} + } // namespace chromeos diff --git a/chromeos/display/output_util.cc b/chromeos/display/output_util.cc new file mode 100644 index 0000000..aa235b0 --- /dev/null +++ b/chromeos/display/output_util.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2013 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 "chromeos/display/output_util.h" + +#include <X11/Xlib.h> +#include <X11/extensions/Xrandr.h> +#include <X11/Xatom.h> + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/sys_byteorder.h" + +namespace chromeos { +namespace { + +// Prefixes for the built-in displays. +const char kInternal_LVDS[] = "LVDS"; +const char kInternal_eDP[] = "eDP"; + +// Returns 64-bit persistent ID for the specified manufacturer's ID and +// product_code, 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, + uint16 product_code, + uint8 output_index) { + return ((static_cast<int64>(manufacturer_id) << 24) | + (static_cast<int64>(product_code) << 8) | output_index); +} + +bool IsRandRAvailable() { + int randr_version_major = 0; + int randr_version_minor = 0; + static bool is_randr_available = XRRQueryVersion( + base::MessagePumpAuraX11::GetDefaultXDisplay(), + &randr_version_major, &randr_version_minor); + return is_randr_available; +} + +// 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|. +bool GetEDIDProperty(XID output, unsigned long* nitems, unsigned char** prop) { + if (!IsRandRAvailable()) + return false; + + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + + static Atom edid_property = XInternAtom( + base::MessagePumpAuraX11::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; +} + +// 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, + uint16* product_code, + 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, product_code, human_readable_name); + XFree(prop); + return result; +} + +} // namespace + +std::string GetDisplayName(XID output_id) { + std::string display_name; + GetOutputDeviceData(output_id, NULL, NULL, &display_name); + return display_name; +} + +bool GetDisplayId(XID output_id, size_t output_index, int64* display_id_out) { + uint16 manufacturer_id = 0; + uint16 product_code = 0; + if (GetOutputDeviceData( + output_id, &manufacturer_id, &product_code, NULL) && + 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, output_index); + return true; + } + return false; +} + +bool ParseOutputDeviceData(const unsigned char* prop, + unsigned long nitems, + uint16* manufacturer_id, + uint16* product_code, + 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 10-11: represents product code, in little-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 kProductCodeOffset = 10; + const unsigned int kProductCodeLength = 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 (product_code) { + if (nitems < kProductCodeOffset + kProductCodeLength) { + LOG(ERROR) << "too short EDID data: product code"; + return false; + } + + *product_code = base::ByteSwapToLE16( + *reinterpret_cast<const uint16*>(prop + kProductCodeOffset)); + } + + 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); + TrimWhitespaceASCII(found_name, TRIM_TRAILING, human_readable_name); + break; + } + } + } + + if (human_readable_name->empty()) { + LOG(ERROR) << "invalid EDID: empty readable name"; + return false; + } + + // 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 GetOutputOverscanFlag(XID 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; +} + +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; +} + +bool IsInternalOutputName(const std::string& name) { + return name.find(kInternal_LVDS) == 0 || name.find(kInternal_eDP) == 0; +} + +} // namespace chromeos diff --git a/chromeos/display/output_util.h b/chromeos/display/output_util.h new file mode 100644 index 0000000..5ecda9b --- /dev/null +++ b/chromeos/display/output_util.h @@ -0,0 +1,56 @@ +// Copyright (c) 2013 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 CHROMEOS_DISPLAY_OUTPUT_UTIL_H_ +#define CHROMEOS_DISPLAY_OUTPUT_UTIL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "chromeos/chromeos_export.h" + +// Forward declarations for Xlib and Xrandr. +// This is so unused X definitions don't pollute the namespace. +typedef unsigned long XID; + +namespace chromeos { + +// Generates the display id for the pair of |output| and |index| and store +// in |display_id_out|. Returns true if the display id is successfully +// generated, or false otherwise. +CHROMEOS_EXPORT bool GetDisplayId(XID output, size_t index, + int64* display_id_out); + +// Generates the human readable string from EDID obtained for |output|. +CHROMEOS_EXPORT std::string GetDisplayName(XID 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. +CHROMEOS_EXPORT bool GetOutputOverscanFlag(XID output, bool* flag); + +// Parses |prop| as EDID data and stores extracted data into |manufacturer_id|, +// |product_code|, and |human_readable_name| and returns true. NULL can be +// passed for unwanted output parameters. This is exported for +// x11_util_unittest.cc. +CHROMEOS_EXPORT bool ParseOutputDeviceData(const unsigned char* prop, + unsigned long nitems, + uint16* manufacturer_id, + uint16* product_code, + std::string* human_readable_name); + +// Parses |prop| as EDID data and stores the overscan flag to |flag|. Returns +// true if the flag is found. This is exported for x11_util_unittest.cc. +CHROMEOS_EXPORT bool ParseOutputOverscanFlag(const unsigned char* prop, + unsigned long nitems, + bool* flag); + +// Returns true if an output named |name| is an internal display. +CHROMEOS_EXPORT bool IsInternalOutputName(const std::string& name); + +} // namespace chromeos + +#endif // CHROMEOS_DISPLAY_OUTPUT_UTIL_H_ diff --git a/chromeos/display/output_util_unittest.cc b/chromeos/display/output_util_unittest.cc new file mode 100644 index 0000000..cd724ae --- /dev/null +++ b/chromeos/display/output_util_unittest.cc @@ -0,0 +1,208 @@ +// Copyright (c) 2013 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 "chromeos/display/output_util.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromeos { + +namespace { + +// Returns the number of characters in the string literal but doesn't count its +// terminator NULL byte. +#define charsize(str) (arraysize(str) - 1) + +// Sample EDID data extracted from real devices. +const unsigned char kNormalDisplay[] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x22\xf0\x6c\x28\x01\x01\x01\x01" + "\x02\x16\x01\x04\xb5\x40\x28\x78\xe2\x8d\x85\xad\x4f\x35\xb1\x25" + "\x0e\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\xe2\x68\x00\xa0\xa0\x40\x2e\x60\x30\x20" + "\x36\x00\x81\x90\x21\x00\x00\x1a\xbc\x1b\x00\xa0\x50\x20\x17\x30" + "\x30\x20\x36\x00\x81\x90\x21\x00\x00\x1a\x00\x00\x00\xfc\x00\x48" + "\x50\x20\x5a\x52\x33\x30\x77\x0a\x20\x20\x20\x20\x00\x00\x00\xff" + "\x00\x43\x4e\x34\x32\x30\x32\x31\x33\x37\x51\x0a\x20\x20\x00\x71"; + +const unsigned char kInternalDisplay[] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00" + "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27" + "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30" + "\x13\x00\x05\xa3\x10\x00\x00\x19\x00\x00\x00\x0f\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x23\x87\x02\x64\x00\x00\x00\x00\xfe\x00\x53" + "\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x00\x00\x00\xfe" + "\x00\x31\x32\x31\x41\x54\x31\x31\x2d\x38\x30\x31\x0a\x20\x00\x45"; + +const unsigned char kOverscanDisplay[] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00" + "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26" + "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00" + "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c" + "\x45\x00\xa0\x5a\x00\x00\x00\x1e\x66\x21\x56\xaa\x51\x00\x1e\x30" + "\x46\x8f\x33\x00\xa0\x5a\x00\x00\x00\x1e\x00\x00\x00\xfd\x00\x18" + "\x4b\x0f\x51\x17\x00\x0a\x20\x20\x20\x20\x20\x20\x00\x00\x00\xfc" + "\x00\x53\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x01\x1d" + "\x02\x03\x1f\xf1\x47\x90\x04\x05\x03\x20\x22\x07\x23\x09\x07\x07" + "\x83\x01\x00\x00\xe2\x00\x0f\x67\x03\x0c\x00\x20\x00\xb8\x2d\x01" + "\x1d\x80\x18\x71\x1c\x16\x20\x58\x2c\x25\x00\xa0\x5a\x00\x00\x00" + "\x9e\x01\x1d\x00\x72\x51\xd0\x1e\x20\x6e\x28\x55\x00\xa0\x5a\x00" + "\x00\x00\x1e\x8c\x0a\xd0\x8a\x20\xe0\x2d\x10\x10\x3e\x96\x00\xa0" + "\x5a\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\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[] = + "\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" + "\x01\x01\x01\x01\x01\x01\x28\x3c\x80\xa0\x70\xb0\x23\x40\x30\x20" + "\x36\x00\x81\x91\x21\x00\x00\x1a\x00\x00\x00\xff\x00\x50\x48\x35" + "\x4e\x59\x31\x33\x4e\x32\x30\x30\x4c\x0a\x00\x00\x00\xfc\x00\x44" + "\x45\x4c\x4c\x20\x55\x33\x30\x31\x31\x0a\x20\x20\x00\x00\x00\xfd" + "\x00\x31\x56\x1d\x5e\x12\x00\x0a\x20\x20\x20\x20\x20\x20\x01\x38" + "\x02\x03\x29\xf1\x50\x90\x05\x04\x03\x02\x07\x16\x01\x06\x11\x12" + "\x15\x13\x14\x1f\x20\x23\x0d\x7f\x07\x83\x0f\x00\x00\x67\x03\x0c" + "\x00\x10\x00\x38\x2d\xe3\x05\x03\x01\x02\x3a\x80\x18\x71\x38\x2d" + "\x40\x58\x2c\x45\x00\x81\x91\x21\x00\x00\x1e\x01\x1d\x80\x18\x71" + "\x1c\x16\x20\x58\x2c\x25\x00\x81\x91\x21\x00\x00\x9e\x01\x1d\x00" + "\x72\x51\xd0\x1e\x20\x6e\x28\x55\x00\x81\x91\x21\x00\x00\x1e\x8c" + "\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"; + +} + +TEST(OutputUtilTest, ParseEDID) { + uint16 manufacturer_id = 0; + uint16 product_code = 0; + std::string human_readable_name; + EXPECT_TRUE(ParseOutputDeviceData( + kNormalDisplay, charsize(kNormalDisplay), + &manufacturer_id, &product_code, &human_readable_name)); + EXPECT_EQ(0x22f0u, manufacturer_id); + EXPECT_EQ(0x286cu, product_code); + EXPECT_EQ("HP ZR30w", human_readable_name); + + manufacturer_id = 0; + product_code = 0; + human_readable_name.clear(); + EXPECT_TRUE(ParseOutputDeviceData( + kInternalDisplay, charsize(kInternalDisplay), + &manufacturer_id, &product_code, NULL)); + EXPECT_EQ(0x4ca3u, manufacturer_id); + EXPECT_EQ(0x3142u, product_code); + EXPECT_EQ("", human_readable_name); + + // Internal display doesn't have name. + EXPECT_FALSE(ParseOutputDeviceData( + kInternalDisplay, charsize(kInternalDisplay), + NULL, NULL, &human_readable_name)); + + manufacturer_id = 0; + product_code = 0; + human_readable_name.clear(); + EXPECT_TRUE(ParseOutputDeviceData( + kOverscanDisplay, charsize(kOverscanDisplay), + &manufacturer_id, &product_code, &human_readable_name)); + EXPECT_EQ(0x4c2du, manufacturer_id); + EXPECT_EQ(0x08feu, product_code); + EXPECT_EQ("SAMSUNG", human_readable_name); +} + +TEST(OutputUtilTest, ParseBrokenEDID) { + uint16 manufacturer_id = 0; + uint16 product_code = 0; + std::string human_readable_name; + + // length == 0 + EXPECT_FALSE(ParseOutputDeviceData( + kNormalDisplay, 0, + &manufacturer_id, &product_code, &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, &product_code, &human_readable_name)); + + // If |human_readable_name| isn't specified, it skips parsing the name. + manufacturer_id = 0; + product_code = 0; + EXPECT_TRUE(ParseOutputDeviceData( + reinterpret_cast<const unsigned char*>(display_data.data()), + display_data.size(), + &manufacturer_id, &product_code, NULL)); + EXPECT_EQ(0x22f0u, manufacturer_id); + EXPECT_EQ(0x286cu, product_code); +} + +TEST(OutputUtilTest, ParseOverscanFlag) { + bool flag = false; + EXPECT_FALSE(ParseOutputOverscanFlag( + kNormalDisplay, charsize(kNormalDisplay), &flag)); + + flag = false; + EXPECT_FALSE(ParseOutputOverscanFlag( + kInternalDisplay, charsize(kInternalDisplay), &flag)); + + flag = false; + EXPECT_TRUE(ParseOutputOverscanFlag( + kOverscanDisplay, charsize(kOverscanDisplay), &flag)); + EXPECT_TRUE(flag); + + flag = false; + EXPECT_FALSE(ParseOutputOverscanFlag( + kMisdetecedDisplay, charsize(kMisdetecedDisplay), &flag)); + + flag = false; + // Copy |kOverscanDisplay| and set flags to false in it. The overscan flags + // are embedded at byte 150 in this specific example. Fix here too when the + // contents of kOverscanDisplay is altered. + std::string display_data(reinterpret_cast<const char*>(kOverscanDisplay), + charsize(kOverscanDisplay)); + display_data[150] = '\0'; + EXPECT_TRUE(ParseOutputOverscanFlag( + reinterpret_cast<const unsigned char*>(display_data.data()), + display_data.size(), &flag)); + EXPECT_FALSE(flag); +} + +TEST(OutputUtilTest, 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; + EXPECT_FALSE(ParseOutputOverscanFlag(data.get(), 0, &flag)); + EXPECT_FALSE(ParseOutputOverscanFlag(data.get(), 126, &flag)); + + // extending data because ParseOutputOverscanFlag() will access the data. + data.reset(new unsigned char[150]); + // The number of CEA extensions is stored at byte 126. + data[126] = '\x01'; + EXPECT_FALSE(ParseOutputOverscanFlag(data.get(), 128, &flag)); + EXPECT_FALSE(ParseOutputOverscanFlag(data.get(), 150, &flag)); +} + +TEST(OutputUtilTest, IsInternalOutputName) { + EXPECT_TRUE(IsInternalOutputName("LVDS")); + EXPECT_TRUE(IsInternalOutputName("eDP")); + EXPECT_TRUE(IsInternalOutputName("LVDSxx")); + EXPECT_TRUE(IsInternalOutputName("eDPzz")); + + EXPECT_FALSE(IsInternalOutputName("xyz")); + EXPECT_FALSE(IsInternalOutputName("abcLVDS")); + EXPECT_FALSE(IsInternalOutputName("cdeeDP")); + EXPECT_FALSE(IsInternalOutputName("LVD")); + EXPECT_FALSE(IsInternalOutputName("eD")); +} + +} // namespace chromeos diff --git a/chromeos/display/real_output_configurator_delegate.cc b/chromeos/display/real_output_configurator_delegate.cc index 531a46d..a0bb007 100644 --- a/chromeos/display/real_output_configurator_delegate.cc +++ b/chromeos/display/real_output_configurator_delegate.cc @@ -17,6 +17,7 @@ #include "base/message_pump_aurax11.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/power_manager_client.h" +#include "chromeos/display/output_util.h" namespace chromeos { @@ -28,8 +29,7 @@ const float kDpi96 = 96.0; const float kPixelsToMmScale = kMmInInch / kDpi96; bool IsInternalOutput(const XRROutputInfo* output_info) { - return OutputConfigurator::IsInternalOutputName( - std::string(output_info->name)); + return IsInternalOutputName(std::string(output_info->name)); } RRMode GetOutputNativeMode(const XRROutputInfo* output_info) { @@ -121,6 +121,8 @@ RealOutputConfiguratorDelegate::GetOutputs() { if (is_connected) { OutputConfigurator::OutputSnapshot to_populate; to_populate.output = this_id; + to_populate.has_display_id = + GetDisplayId(this_id, i, &to_populate.display_id); (outputs.empty() ? one_info : two_info) = output_info; // Now, look up the current CRTC and any related info. @@ -147,7 +149,6 @@ RealOutputConfiguratorDelegate::GetOutputs() { to_populate.is_aspect_preserving_scaling = IsOutputAspectPreservingScaling(this_id); to_populate.touch_device_id = None; - to_populate.index = i; VLOG(1) << "Found display #" << outputs.size() << " with output " << to_populate.output |