1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/x11/edid_parser_x11.h"
6
7#include <X11/extensions/Xrandr.h>
8#include <X11/Xatom.h>
9#include <X11/Xlib.h>
10
11#include "base/hash.h"
12#include "base/message_loop/message_loop.h"
13#include "base/strings/string_util.h"
14#include "base/sys_byteorder.h"
15
16namespace {
17
18// Returns 64-bit persistent ID for the specified manufacturer's ID and
19// product_code_hash, and the index of the output it is connected to.
20// |output_index| is used to distinguish the displays of the same type. For
21// example, swapping two identical display between two outputs will not be
22// treated as swap. The 'serial number' field in EDID isn't used here because
23// it is not guaranteed to have unique number and it may have the same fixed
24// value (like 0).
25int64 GetID(uint16 manufacturer_id,
26            uint32 product_code_hash,
27            uint8 output_index) {
28  return ((static_cast<int64>(manufacturer_id) << 40) |
29          (static_cast<int64>(product_code_hash) << 8) | output_index);
30}
31
32bool IsRandRAvailable() {
33  int randr_version_major = 0;
34  int randr_version_minor = 0;
35  static bool is_randr_available = XRRQueryVersion(
36      base::MessagePumpX11::GetDefaultXDisplay(),
37      &randr_version_major, &randr_version_minor);
38  return is_randr_available;
39}
40
41}  // namespace
42
43namespace base {
44
45bool GetEDIDProperty(XID output, unsigned long* nitems, unsigned char** prop) {
46  if (!IsRandRAvailable())
47    return false;
48
49  Display* display = base::MessagePumpX11::GetDefaultXDisplay();
50
51  static Atom edid_property = XInternAtom(
52      base::MessagePumpX11::GetDefaultXDisplay(),
53      RR_PROPERTY_RANDR_EDID, false);
54
55  bool has_edid_property = false;
56  int num_properties = 0;
57  Atom* properties = XRRListOutputProperties(display, output, &num_properties);
58  for (int i = 0; i < num_properties; ++i) {
59    if (properties[i] == edid_property) {
60      has_edid_property = true;
61      break;
62    }
63  }
64  XFree(properties);
65  if (!has_edid_property)
66    return false;
67
68  Atom actual_type;
69  int actual_format;
70  unsigned long bytes_after;
71  XRRGetOutputProperty(display,
72                       output,
73                       edid_property,
74                       0,                // offset
75                       128,              // length
76                       false,            // _delete
77                       false,            // pending
78                       AnyPropertyType,  // req_type
79                       &actual_type,
80                       &actual_format,
81                       nitems,
82                       &bytes_after,
83                       prop);
84  DCHECK_EQ(XA_INTEGER, actual_type);
85  DCHECK_EQ(8, actual_format);
86  return true;
87}
88
89bool GetDisplayId(XID output_id, size_t output_index, int64* display_id_out) {
90  unsigned long nitems = 0;
91  unsigned char* prop = NULL;
92  if (!GetEDIDProperty(output_id, &nitems, &prop))
93    return false;
94
95  bool result =
96      GetDisplayIdFromEDID(prop, nitems, output_index, display_id_out);
97  XFree(prop);
98  return result;
99}
100
101bool GetDisplayIdFromEDID(const unsigned char* prop,
102                          unsigned long nitems,
103                          size_t output_index,
104                          int64* display_id_out) {
105  uint16 manufacturer_id = 0;
106  std::string product_name;
107
108  // ParseOutputDeviceData fails if it doesn't have product_name.
109  ParseOutputDeviceData(prop, nitems, &manufacturer_id, &product_name);
110
111  // Generates product specific value from product_name instead of product code.
112  // See crbug.com/240341
113  uint32 product_code_hash = product_name.empty() ?
114      0 : base::Hash(product_name);
115  if (manufacturer_id != 0) {
116    // An ID based on display's index will be assigned later if this call
117    // fails.
118    *display_id_out = GetID(
119        manufacturer_id, product_code_hash, output_index);
120    return true;
121  }
122  return false;
123}
124
125bool ParseOutputDeviceData(const unsigned char* prop,
126                           unsigned long nitems,
127                           uint16* manufacturer_id,
128                           std::string* human_readable_name) {
129  // See http://en.wikipedia.org/wiki/Extended_display_identification_data
130  // for the details of EDID data format.  We use the following data:
131  //   bytes 8-9: manufacturer EISA ID, in big-endian
132  //   bytes 54-125: four descriptors (18-bytes each) which may contain
133  //     the display name.
134  const unsigned int kManufacturerOffset = 8;
135  const unsigned int kManufacturerLength = 2;
136  const unsigned int kDescriptorOffset = 54;
137  const unsigned int kNumDescriptors = 4;
138  const unsigned int kDescriptorLength = 18;
139  // The specifier types.
140  const unsigned char kMonitorNameDescriptor = 0xfc;
141
142  if (manufacturer_id) {
143    if (nitems < kManufacturerOffset + kManufacturerLength) {
144      LOG(ERROR) << "too short EDID data: manifacturer id";
145      return false;
146    }
147
148    *manufacturer_id =
149        *reinterpret_cast<const uint16*>(prop + kManufacturerOffset);
150#if defined(ARCH_CPU_LITTLE_ENDIAN)
151    *manufacturer_id = base::ByteSwap(*manufacturer_id);
152#endif
153  }
154
155  if (!human_readable_name)
156    return true;
157
158  human_readable_name->clear();
159  for (unsigned int i = 0; i < kNumDescriptors; ++i) {
160    if (nitems < kDescriptorOffset + (i + 1) * kDescriptorLength)
161      break;
162
163    const unsigned char* desc_buf =
164        prop + kDescriptorOffset + i * kDescriptorLength;
165    // If the descriptor contains the display name, it has the following
166    // structure:
167    //   bytes 0-2, 4: \0
168    //   byte 3: descriptor type, defined above.
169    //   bytes 5-17: text data, ending with \r, padding with spaces
170    // we should check bytes 0-2 and 4, since it may have other values in
171    // case that the descriptor contains other type of data.
172    if (desc_buf[0] == 0 && desc_buf[1] == 0 && desc_buf[2] == 0 &&
173        desc_buf[4] == 0) {
174      if (desc_buf[3] == kMonitorNameDescriptor) {
175        std::string found_name(
176            reinterpret_cast<const char*>(desc_buf + 5), kDescriptorLength - 5);
177        TrimWhitespaceASCII(found_name, TRIM_TRAILING, human_readable_name);
178        break;
179      }
180    }
181  }
182
183  // Verify if the |human_readable_name| consists of printable characters only.
184  for (size_t i = 0; i < human_readable_name->size(); ++i) {
185    char c = (*human_readable_name)[i];
186    if (!isascii(c) || !isprint(c)) {
187      human_readable_name->clear();
188      LOG(ERROR) << "invalid EDID: human unreadable char in name";
189      return false;
190    }
191  }
192
193  return true;
194}
195
196}  // namespace base
197