Home | History | Annotate | Download | only in display
      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 "chromeos/display/output_util.h"
      6 
      7 #include <X11/Xlib.h>
      8 #include <X11/extensions/Xrandr.h>
      9 #include <X11/Xatom.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 
     16 namespace chromeos {
     17 namespace {
     18 
     19 // Prefixes for the built-in displays.
     20 const char kInternal_LVDS[] = "LVDS";
     21 const char kInternal_eDP[] = "eDP";
     22 const char kInternal_DSI[] = "DSI";
     23 
     24 // Returns 64-bit persistent ID for the specified manufacturer's ID and
     25 // product_code_hash, and the index of the output it is connected to.
     26 // |output_index| is used to distinguish the displays of the same type. For
     27 // example, swapping two identical display between two outputs will not be
     28 // treated as swap. The 'serial number' field in EDID isn't used here because
     29 // it is not guaranteed to have unique number and it may have the same fixed
     30 // value (like 0).
     31 int64 GetID(uint16 manufacturer_id,
     32             uint32 product_code_hash,
     33             uint8 output_index) {
     34   return ((static_cast<int64>(manufacturer_id) << 40) |
     35           (static_cast<int64>(product_code_hash) << 8) | output_index);
     36 }
     37 
     38 bool IsRandRAvailable() {
     39   int randr_version_major = 0;
     40   int randr_version_minor = 0;
     41   static bool is_randr_available = XRRQueryVersion(
     42       base::MessagePumpAuraX11::GetDefaultXDisplay(),
     43       &randr_version_major, &randr_version_minor);
     44   return is_randr_available;
     45 }
     46 
     47 // Get the EDID data from the |output| and stores to |prop|. |nitem| will store
     48 // the number of characters |prop| will have. It doesn't take the ownership of
     49 // |prop|, so caller must release it by XFree().
     50 // Returns true if EDID property is successfully obtained. Otherwise returns
     51 // false and does not touch |prop| and |nitems|.
     52 bool GetEDIDProperty(XID output, unsigned long* nitems, unsigned char** prop) {
     53   if (!IsRandRAvailable())
     54     return false;
     55 
     56   Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay();
     57 
     58   static Atom edid_property = XInternAtom(
     59       base::MessagePumpAuraX11::GetDefaultXDisplay(),
     60       RR_PROPERTY_RANDR_EDID, false);
     61 
     62   bool has_edid_property = false;
     63   int num_properties = 0;
     64   Atom* properties = XRRListOutputProperties(display, output, &num_properties);
     65   for (int i = 0; i < num_properties; ++i) {
     66     if (properties[i] == edid_property) {
     67       has_edid_property = true;
     68       break;
     69     }
     70   }
     71   XFree(properties);
     72   if (!has_edid_property)
     73     return false;
     74 
     75   Atom actual_type;
     76   int actual_format;
     77   unsigned long bytes_after;
     78   XRRGetOutputProperty(display,
     79                        output,
     80                        edid_property,
     81                        0,                // offset
     82                        128,              // length
     83                        false,            // _delete
     84                        false,            // pending
     85                        AnyPropertyType,  // req_type
     86                        &actual_type,
     87                        &actual_format,
     88                        nitems,
     89                        &bytes_after,
     90                        prop);
     91   DCHECK_EQ(XA_INTEGER, actual_type);
     92   DCHECK_EQ(8, actual_format);
     93   return true;
     94 }
     95 
     96 // Gets some useful data from the specified output device, such like
     97 // manufacturer's ID, product code, and human readable name. Returns false if it
     98 // fails to get those data and doesn't touch manufacturer ID/product code/name.
     99 // NULL can be passed for unwanted output parameters.
    100 bool GetOutputDeviceData(XID output,
    101                          uint16* manufacturer_id,
    102                          std::string* human_readable_name) {
    103   unsigned long nitems = 0;
    104   unsigned char *prop = NULL;
    105   if (!GetEDIDProperty(output, &nitems, &prop))
    106     return false;
    107 
    108   bool result = ParseOutputDeviceData(
    109       prop, nitems, manufacturer_id, human_readable_name);
    110   XFree(prop);
    111   return result;
    112 }
    113 
    114 float GetRefreshRate(const XRRModeInfo* mode_info) {
    115   if (mode_info->hTotal && mode_info->vTotal) {
    116     return static_cast<float>(mode_info->dotClock) /
    117         (static_cast<float>(mode_info->hTotal) *
    118          static_cast<float>(mode_info->vTotal));
    119   } else {
    120     return 0.0f;
    121   }
    122 }
    123 
    124 }  // namespace
    125 
    126 std::string GetDisplayName(XID output_id) {
    127   std::string display_name;
    128   GetOutputDeviceData(output_id, NULL, &display_name);
    129   return display_name;
    130 }
    131 
    132 bool GetDisplayId(XID output_id, size_t output_index, int64* display_id_out) {
    133   unsigned long nitems = 0;
    134   unsigned char* prop = NULL;
    135   if (!GetEDIDProperty(output_id, &nitems, &prop))
    136     return false;
    137 
    138   bool result =
    139       GetDisplayIdFromEDID(prop, nitems, output_index, display_id_out);
    140   XFree(prop);
    141   return result;
    142 }
    143 
    144 bool GetDisplayIdFromEDID(const unsigned char* prop,
    145                           unsigned long nitems,
    146                           size_t output_index,
    147                           int64* display_id_out) {
    148   uint16 manufacturer_id = 0;
    149   std::string product_name;
    150 
    151   // ParseOutputDeviceData fails if it doesn't have product_name.
    152   ParseOutputDeviceData(prop, nitems, &manufacturer_id, &product_name);
    153 
    154   // Generates product specific value from product_name instead of product code.
    155   // See crbug.com/240341
    156   uint32 product_code_hash = product_name.empty() ?
    157       0 : base::Hash(product_name);
    158   if (manufacturer_id != 0) {
    159     // An ID based on display's index will be assigned later if this call
    160     // fails.
    161     *display_id_out = GetID(
    162         manufacturer_id, product_code_hash, output_index);
    163     return true;
    164   }
    165   return false;
    166 }
    167 
    168 bool ParseOutputDeviceData(const unsigned char* prop,
    169                            unsigned long nitems,
    170                            uint16* manufacturer_id,
    171                            std::string* human_readable_name) {
    172   // See http://en.wikipedia.org/wiki/Extended_display_identification_data
    173   // for the details of EDID data format.  We use the following data:
    174   //   bytes 8-9: manufacturer EISA ID, in big-endian
    175   //   bytes 54-125: four descriptors (18-bytes each) which may contain
    176   //     the display name.
    177   const unsigned int kManufacturerOffset = 8;
    178   const unsigned int kManufacturerLength = 2;
    179   const unsigned int kDescriptorOffset = 54;
    180   const unsigned int kNumDescriptors = 4;
    181   const unsigned int kDescriptorLength = 18;
    182   // The specifier types.
    183   const unsigned char kMonitorNameDescriptor = 0xfc;
    184 
    185   if (manufacturer_id) {
    186     if (nitems < kManufacturerOffset + kManufacturerLength) {
    187       LOG(ERROR) << "too short EDID data: manifacturer id";
    188       return false;
    189     }
    190 
    191     *manufacturer_id =
    192         *reinterpret_cast<const uint16*>(prop + kManufacturerOffset);
    193 #if defined(ARCH_CPU_LITTLE_ENDIAN)
    194     *manufacturer_id = base::ByteSwap(*manufacturer_id);
    195 #endif
    196   }
    197 
    198   if (!human_readable_name)
    199     return true;
    200 
    201   human_readable_name->clear();
    202   for (unsigned int i = 0; i < kNumDescriptors; ++i) {
    203     if (nitems < kDescriptorOffset + (i + 1) * kDescriptorLength)
    204       break;
    205 
    206     const unsigned char* desc_buf =
    207         prop + kDescriptorOffset + i * kDescriptorLength;
    208     // If the descriptor contains the display name, it has the following
    209     // structure:
    210     //   bytes 0-2, 4: \0
    211     //   byte 3: descriptor type, defined above.
    212     //   bytes 5-17: text data, ending with \r, padding with spaces
    213     // we should check bytes 0-2 and 4, since it may have other values in
    214     // case that the descriptor contains other type of data.
    215     if (desc_buf[0] == 0 && desc_buf[1] == 0 && desc_buf[2] == 0 &&
    216         desc_buf[4] == 0) {
    217       if (desc_buf[3] == kMonitorNameDescriptor) {
    218         std::string found_name(
    219             reinterpret_cast<const char*>(desc_buf + 5), kDescriptorLength - 5);
    220         TrimWhitespaceASCII(found_name, TRIM_TRAILING, human_readable_name);
    221         break;
    222       }
    223     }
    224   }
    225 
    226   // Verify if the |human_readable_name| consists of printable characters only.
    227   for (size_t i = 0; i < human_readable_name->size(); ++i) {
    228     char c = (*human_readable_name)[i];
    229     if (!isascii(c) || !isprint(c)) {
    230       human_readable_name->clear();
    231       LOG(ERROR) << "invalid EDID: human unreadable char in name";
    232       return false;
    233     }
    234   }
    235 
    236   return true;
    237 }
    238 
    239 bool GetOutputOverscanFlag(XID output, bool* flag) {
    240   unsigned long nitems = 0;
    241   unsigned char *prop = NULL;
    242   if (!GetEDIDProperty(output, &nitems, &prop))
    243     return false;
    244 
    245   bool found = ParseOutputOverscanFlag(prop, nitems, flag);
    246   XFree(prop);
    247   return found;
    248 }
    249 
    250 bool ParseOutputOverscanFlag(const unsigned char* prop,
    251                              unsigned long nitems,
    252                              bool *flag) {
    253   // See http://en.wikipedia.org/wiki/Extended_display_identification_data
    254   // for the extension format of EDID.  Also see EIA/CEA-861 spec for
    255   // the format of the extensions and how video capability is encoded.
    256   //  - byte 0: tag.  should be 02h.
    257   //  - byte 1: revision.  only cares revision 3 (03h).
    258   //  - byte 4-: data block.
    259   const unsigned int kExtensionBase = 128;
    260   const unsigned int kExtensionSize = 128;
    261   const unsigned int kNumExtensionsOffset = 126;
    262   const unsigned int kDataBlockOffset = 4;
    263   const unsigned char kCEAExtensionTag = '\x02';
    264   const unsigned char kExpectedExtensionRevision = '\x03';
    265   const unsigned char kExtendedTag = 7;
    266   const unsigned char kExtendedVideoCapabilityTag = 0;
    267   const unsigned int kPTOverscan = 4;
    268   const unsigned int kITOverscan = 2;
    269   const unsigned int kCEOverscan = 0;
    270 
    271   if (nitems <= kNumExtensionsOffset)
    272     return false;
    273 
    274   unsigned char num_extensions = prop[kNumExtensionsOffset];
    275 
    276   for (size_t i = 0; i < num_extensions; ++i) {
    277     // Skip parsing the whole extension if size is not enough.
    278     if (nitems < kExtensionBase + (i + 1) * kExtensionSize)
    279       break;
    280 
    281     const unsigned char* extension = prop + kExtensionBase + i * kExtensionSize;
    282     unsigned char tag = extension[0];
    283     unsigned char revision = extension[1];
    284     if (tag != kCEAExtensionTag || revision != kExpectedExtensionRevision)
    285       continue;
    286 
    287     unsigned char timing_descriptors_start =
    288         std::min(extension[2], static_cast<unsigned char>(kExtensionSize));
    289     const unsigned char* data_block = extension + kDataBlockOffset;
    290     while (data_block < extension + timing_descriptors_start) {
    291       // A data block is encoded as:
    292       // - byte 1 high 3 bits: tag. '07' for extended tags.
    293       // - byte 1 remaining bits: the length of data block.
    294       // - byte 2: the extended tag.  '0' for video capability.
    295       // - byte 3: the capability.
    296       unsigned char tag = data_block[0] >> 5;
    297       unsigned char payload_length = data_block[0] & 0x1f;
    298       if (static_cast<unsigned long>(data_block + payload_length - prop) >
    299           nitems)
    300         break;
    301 
    302       if (tag != kExtendedTag || payload_length < 2) {
    303         data_block += payload_length + 1;
    304         continue;
    305       }
    306 
    307       unsigned char extended_tag_code = data_block[1];
    308       if (extended_tag_code != kExtendedVideoCapabilityTag) {
    309         data_block += payload_length + 1;
    310         continue;
    311       }
    312 
    313       // The difference between preferred, IT, and CE video formats
    314       // doesn't matter. Sets |flag| to true if any of these flags are true.
    315       if ((data_block[2] & (1 << kPTOverscan)) ||
    316           (data_block[2] & (1 << kITOverscan)) ||
    317           (data_block[2] & (1 << kCEOverscan))) {
    318         *flag = true;
    319       } else {
    320         *flag = false;
    321       }
    322       return true;
    323     }
    324   }
    325 
    326   return false;
    327 }
    328 
    329 bool IsInternalOutputName(const std::string& name) {
    330   return name.find(kInternal_LVDS) == 0 || name.find(kInternal_eDP) == 0 ||
    331       name.find(kInternal_DSI) == 0;
    332 }
    333 
    334 const XRRModeInfo* FindModeInfo(const XRRScreenResources* screen_resources,
    335                                 XID current_mode) {
    336   for (int m = 0; m < screen_resources->nmode; m++) {
    337     XRRModeInfo *mode = &screen_resources->modes[m];
    338     if (mode->id == current_mode)
    339       return mode;
    340   }
    341   return NULL;
    342 }
    343 
    344 // Find a mode that matches the given size with highest
    345 // reflesh rate.
    346 RRMode FindOutputModeMatchingSize(
    347     const XRRScreenResources* screen_resources,
    348     const XRROutputInfo* output_info,
    349     size_t width,
    350     size_t height) {
    351   RRMode found = None;
    352   float best_rate = 0;
    353   bool non_interlaced_found = false;
    354   for (int i = 0; i < output_info->nmode; ++i) {
    355     RRMode mode = output_info->modes[i];
    356     const XRRModeInfo* info = FindModeInfo(screen_resources, mode);
    357 
    358     if (info->width == width && info->height == height) {
    359       float rate = GetRefreshRate(info);
    360 
    361       if (info->modeFlags & RR_Interlace) {
    362         if (non_interlaced_found)
    363           continue;
    364       } else {
    365         // Reset the best rate if the non interlaced is
    366         // found the first time.
    367         if (!non_interlaced_found) {
    368           best_rate = rate;
    369         }
    370         non_interlaced_found = true;
    371       }
    372       if (rate < best_rate)
    373         continue;
    374 
    375       found = mode;
    376       best_rate = rate;
    377     }
    378   }
    379   return found;
    380 }
    381 
    382 namespace test {
    383 
    384 XRRModeInfo CreateModeInfo(int id,
    385                            int width,
    386                            int height,
    387                            bool interlaced,
    388                            float refresh_rate) {
    389   XRRModeInfo mode_info = {0};
    390   mode_info.id = id;
    391   mode_info.width = width;
    392   mode_info.height = height;
    393   if (interlaced)
    394     mode_info.modeFlags = RR_Interlace;
    395   if (refresh_rate != 0.0f) {
    396     mode_info.hTotal = 1;
    397     mode_info.vTotal = 1;
    398     mode_info.dotClock = refresh_rate;
    399   }
    400   return mode_info;
    401 }
    402 
    403 }  // namespace test
    404 
    405 }  // namespace chromeos
    406