display_info.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <stdio.h>
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <string>
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <vector>
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/display/display_info.h"
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/logging.h"
11c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/strings/string_number_conversions.h"
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_util.h"
13868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/stringprintf.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/display.h"
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/size_conversions.h"
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/size_f.h"
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#if defined(OS_WIN)
19f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/aura/window_tree_host.h"
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "ui/gfx/win/dpi.h"
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#endif
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace ash {
24a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochnamespace {
25a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccibool use_125_dsf_for_ui_scaling = false;
27a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// Check the content of |spec| and fill |bounds| and |device_scale_factor|.
296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// Returns true when |bounds| is found.
306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)bool GetDisplayBounds(
316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    const std::string& spec, gfx::Rect* bounds, float* device_scale_factor) {
326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  int width = 0;
336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  int height = 0;
346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  int x = 0;
356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  int y = 0;
366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if (sscanf(spec.c_str(), "%dx%d*%f",
376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)             &width, &height, device_scale_factor) >= 2 ||
386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      sscanf(spec.c_str(), "%d+%d-%dx%d*%f", &x, &y, &width, &height,
396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)             device_scale_factor) >= 4) {
406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    bounds->SetRect(x, y, width, height);
416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    return true;
426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  return false;
446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)}
456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}  // namespace
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)DisplayMode::DisplayMode()
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    : refresh_rate(0.0f),
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      interlaced(false),
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      native(false),
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      ui_scale(1.0f),
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      device_scale_factor(1.0f) {}
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)DisplayMode::DisplayMode(const gfx::Size& size,
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                         float refresh_rate,
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                         bool interlaced,
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                         bool native)
59a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    : size(size),
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      refresh_rate(refresh_rate),
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      interlaced(interlaced),
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      native(native),
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      ui_scale(1.0f),
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      device_scale_factor(1.0f) {}
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)gfx::Size DisplayMode::GetSizeInDIP() const {
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  gfx::SizeF size_dip(size);
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  size_dip.Scale(ui_scale);
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  size_dip.Scale(1.0f / device_scale_factor);
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return gfx::ToFlooredSize(size_dip);
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)bool DisplayMode::IsEquivalent(const DisplayMode& other) const {
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  const float kEpsilon = 0.0001f;
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return size == other.size &&
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      std::abs(ui_scale - other.ui_scale) < kEpsilon &&
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      std::abs(device_scale_factor - other.device_scale_factor) < kEpsilon;
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}
79a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// satic
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)DisplayInfo DisplayInfo::CreateFromSpec(const std::string& spec) {
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return CreateFromSpecWithID(spec, gfx::Display::kInvalidDisplayID);
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// static
861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci// static
881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DisplayInfo::SetUse125DSFForUIScaling(bool enable) {
891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  use_125_dsf_for_ui_scaling = enable;
90a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch}
91a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
92a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch// static
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)DisplayInfo DisplayInfo::CreateFromSpecWithID(const std::string& spec,
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                              int64 id) {
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Default bounds for a display.
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const int kDefaultHostWindowX = 200;
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const int kDefaultHostWindowY = 200;
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const int kDefaultHostWindowWidth = 1366;
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const int kDefaultHostWindowHeight = 768;
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
101868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Use larger than max int to catch overflow early.
102868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  static int64 synthesized_display_id = 2200000000LL;
1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#if defined(OS_WIN)
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  gfx::Rect bounds_in_native(aura::WindowTreeHost::GetNativeScreenSize());
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#else
10768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  gfx::Rect bounds_in_native(kDefaultHostWindowX, kDefaultHostWindowY,
10868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                             kDefaultHostWindowWidth, kDefaultHostWindowHeight);
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#endif
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  std::string main_spec = spec;
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  float ui_scale = 1.0f;
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  std::vector<std::string> parts;
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (Tokenize(main_spec, "@", &parts) == 2) {
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    double scale_in_double = 0;
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (base::StringToDouble(parts[1], &scale_in_double))
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      ui_scale = scale_in_double;
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    main_spec = parts[0];
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  size_t count = Tokenize(main_spec, "/", &parts);
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::Display::Rotation rotation(gfx::Display::ROTATE_0);
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  bool has_overscan = false;
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (count) {
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    main_spec = parts[0];
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (count >= 2) {
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      std::string options = parts[1];
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      for (size_t i = 0; i < options.size(); ++i) {
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        char c = options[i];
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        switch (c) {
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          case 'o':
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            has_overscan = true;
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            break;
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          case 'r':  // rotate 90 degrees to 'right'.
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            rotation = gfx::Display::ROTATE_90;
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            break;
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          case 'u':  // 180 degrees, 'u'pside-down.
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            rotation = gfx::Display::ROTATE_180;
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            break;
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          case 'l':  // rotate 90 degrees to 'left'.
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            rotation = gfx::Display::ROTATE_270;
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            break;
1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      }
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  float device_scale_factor = 1.0f;
1496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if (!GetDisplayBounds(main_spec, &bounds_in_native, &device_scale_factor)) {
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(OS_WIN)
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (gfx::IsHighDPIEnabled()) {
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      device_scale_factor = gfx::GetDPIScale();
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::vector<DisplayMode> display_modes;
1583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (Tokenize(main_spec, "#", &parts) == 2) {
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    size_t native_mode = 0;
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    int largest_area = -1;
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    float highest_refresh_rate = -1.0f;
1623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    main_spec = parts[0];
1633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    std::string resolution_list = parts[1];
1643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    count = Tokenize(resolution_list, "|", &parts);
1653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    for (size_t i = 0; i < count; ++i) {
1666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      DisplayMode mode;
1676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      gfx::Rect mode_bounds;
1686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      std::vector<std::string> resolution;
1696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      Tokenize(parts[i], "%", &resolution);
1706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      if (GetDisplayBounds(
1716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)              resolution[0], &mode_bounds, &mode.device_scale_factor)) {
1726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        mode.size = mode_bounds.size();
1736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (resolution.size() > 1)
1746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          sscanf(resolution[1].c_str(), "%f", &mode.refresh_rate);
1756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (mode.size.GetArea() >= largest_area &&
1766e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            mode.refresh_rate > highest_refresh_rate) {
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          // Use mode with largest area and highest refresh rate as native.
1786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          largest_area = mode.size.GetArea();
1796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          highest_refresh_rate = mode.refresh_rate;
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          native_mode = i;
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        }
1826e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        display_modes.push_back(mode);
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      }
1843551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    }
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    display_modes[native_mode].native = true;
1863551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
1873551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (id == gfx::Display::kInvalidDisplayID)
1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    id = synthesized_display_id++;
1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  DisplayInfo display_info(
1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      id, base::StringPrintf("Display-%d", static_cast<int>(id)), has_overscan);
1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  display_info.set_device_scale_factor(device_scale_factor);
1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  display_info.set_rotation(rotation);
194f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  display_info.set_configured_ui_scale(ui_scale);
19568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  display_info.SetBounds(bounds_in_native);
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  display_info.set_display_modes(display_modes);
197eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
198eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // To test the overscan, it creates the default 5% overscan.
199eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (has_overscan) {
20068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    int width = bounds_in_native.width() / device_scale_factor / 40;
20168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    int height = bounds_in_native.height() / device_scale_factor / 40;
202eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    display_info.SetOverscanInsets(gfx::Insets(height, width, height, width));
203eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    display_info.UpdateDisplaySize();
204eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  }
205eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  DVLOG(1) << "DisplayInfoFromSpec info=" << display_info.ToString()
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)           << ", spec=" << spec;
2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return display_info;
2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)DisplayInfo::DisplayInfo()
2122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    : id_(gfx::Display::kInvalidDisplayID),
2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      has_overscan_(false),
2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      rotation_(gfx::Display::ROTATE_0),
2151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      touch_support_(gfx::Display::TOUCH_SUPPORT_UNKNOWN),
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      touch_device_id_(0),
2172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      device_scale_factor_(1.0f),
2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      overscan_insets_in_dip_(0, 0, 0, 0),
219f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      configured_ui_scale_(1.0f),
220effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      native_(false),
221116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      is_aspect_preserving_scaling_(false),
222effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      color_profile_(ui::COLOR_PROFILE_STANDARD) {
2232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)DisplayInfo::DisplayInfo(int64 id,
2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                         const std::string& name,
2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                         bool has_overscan)
2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    : id_(id),
2292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      name_(name),
2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      has_overscan_(has_overscan),
2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      rotation_(gfx::Display::ROTATE_0),
2321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      touch_support_(gfx::Display::TOUCH_SUPPORT_UNKNOWN),
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      touch_device_id_(0),
2342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      device_scale_factor_(1.0f),
2352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      overscan_insets_in_dip_(0, 0, 0, 0),
236f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      configured_ui_scale_(1.0f),
237effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      native_(false),
238effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      color_profile_(ui::COLOR_PROFILE_STANDARD) {
2392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)DisplayInfo::~DisplayInfo() {
2422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void DisplayInfo::Copy(const DisplayInfo& native_info) {
2452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  DCHECK(id_ == native_info.id_);
2462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  name_ = native_info.name_;
2472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  has_overscan_ = native_info.has_overscan_;
2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
24968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  DCHECK(!native_info.bounds_in_native_.IsEmpty());
25068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  bounds_in_native_ = native_info.bounds_in_native_;
2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  size_in_pixel_ = native_info.size_in_pixel_;
2522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  device_scale_factor_ = native_info.device_scale_factor_;
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  display_modes_ = native_info.display_modes_;
2541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  touch_support_ = native_info.touch_support_;
255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  touch_device_id_ = native_info.touch_device_id_;
2562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
257eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // Copy overscan_insets_in_dip_ if it's not empty. This is for test
258eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // cases which use "/o" annotation which sets the overscan inset
259eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // to native, and that overscan has to be propagated. This does not
260eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  // happen on the real environment.
261eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (!native_info.overscan_insets_in_dip_.empty())
262eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    overscan_insets_in_dip_ = native_info.overscan_insets_in_dip_;
263eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
264effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // Rotation_ and ui_scale_ color_profile_ are given by preference,
265effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // or unit tests. Don't copy if this native_info came from
2663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // DisplayChangeObserver.
2672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (!native_info.native()) {
2682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    rotation_ = native_info.rotation_;
269f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    configured_ui_scale_ = native_info.configured_ui_scale_;
270effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    color_profile_ = native_info.color_profile();
2712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
272effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
273effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  available_color_profiles_ = native_info.available_color_profiles();
274effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
2752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Don't copy insets as it may be given by preference.  |rotation_|
2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // is treated as a native so that it can be specified in
2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // |CreateFromSpec|.
2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
28068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)void DisplayInfo::SetBounds(const gfx::Rect& new_bounds_in_native) {
28168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  bounds_in_native_ = new_bounds_in_native;
28268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  size_in_pixel_ = new_bounds_in_native.size();
2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  UpdateDisplaySize();
2842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
286a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochfloat DisplayInfo::GetEffectiveDeviceScaleFactor() const {
2871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (use_125_dsf_for_ui_scaling && device_scale_factor_ == 1.25f)
2881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return (configured_ui_scale_ == 0.8f) ? 1.25f : 1.0f;
2891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (device_scale_factor_ == configured_ui_scale_)
290a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    return 1.0f;
291a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  return device_scale_factor_;
292a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch}
293a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
294f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)float DisplayInfo::GetEffectiveUIScale() const {
2951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (use_125_dsf_for_ui_scaling && device_scale_factor_ == 1.25f)
2961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return (configured_ui_scale_ == 0.8f) ? 1.0f : configured_ui_scale_;
2971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (device_scale_factor_ == configured_ui_scale_)
298f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return 1.0f;
299f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return configured_ui_scale_;
300f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
301f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
3022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void DisplayInfo::UpdateDisplaySize() {
30368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  size_in_pixel_ = bounds_in_native_.size();
304eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (!overscan_insets_in_dip_.empty()) {
3052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    gfx::Insets insets_in_pixel =
3062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        overscan_insets_in_dip_.Scale(device_scale_factor_);
3072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    size_in_pixel_.Enlarge(-insets_in_pixel.width(), -insets_in_pixel.height());
3082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  } else {
3092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    overscan_insets_in_dip_.Set(0, 0, 0, 0);
3102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
3112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (rotation_ == gfx::Display::ROTATE_90 ||
3132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      rotation_ == gfx::Display::ROTATE_270)
3142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    size_in_pixel_.SetSize(size_in_pixel_.height(), size_in_pixel_.width());
3152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::SizeF size_f(size_in_pixel_);
316f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  size_f.Scale(GetEffectiveUIScale());
3172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  size_in_pixel_ = gfx::ToFlooredSize(size_f);
3182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
3192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
320eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid DisplayInfo::SetOverscanInsets(const gfx::Insets& insets_in_dip) {
3212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  overscan_insets_in_dip_ = insets_in_dip;
3222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
3232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)gfx::Insets DisplayInfo::GetOverscanInsetsInPixel() const {
3252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return overscan_insets_in_dip_.Scale(device_scale_factor_);
3262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
3272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccigfx::Size DisplayInfo::GetNativeModeSize() const {
3291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  for (size_t i = 0; i < display_modes_.size(); ++i) {
3301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if (display_modes_[i].native)
3311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return display_modes_[i].size;
3321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
3331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return gfx::Size();
3351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
3361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)std::string DisplayInfo::ToString() const {
3382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  int rotation_degree = static_cast<int>(rotation_) * 90;
3392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return base::StringPrintf(
3407dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      "DisplayInfo[%lld] native bounds=%s, size=%s, scale=%f, "
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      "overscan=%s, rotation=%d, ui-scale=%f, touchscreen=%s, "
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      "touch-device-id=%d",
3432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      static_cast<long long int>(id_),
34468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      bounds_in_native_.ToString().c_str(),
3452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      size_in_pixel_.ToString().c_str(),
3462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      device_scale_factor_,
3472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      overscan_insets_in_dip_.ToString().c_str(),
3482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      rotation_degree,
349f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      configured_ui_scale_,
3505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      touch_support_ == gfx::Display::TOUCH_SUPPORT_AVAILABLE
3515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          ? "yes"
3525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          : touch_support_ == gfx::Display::TOUCH_SUPPORT_UNAVAILABLE
3535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                ? "no"
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                : "unknown",
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      touch_device_id_);
3562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
3572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
358a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)std::string DisplayInfo::ToFullString() const {
3595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::string display_modes_str;
3605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::vector<DisplayMode>::const_iterator iter = display_modes_.begin();
3615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (; iter != display_modes_.end(); ++iter) {
3625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (!display_modes_str.empty())
3635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      display_modes_str += ",";
3645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    base::StringAppendF(&display_modes_str,
3655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "(%dx%d@%f%c%s)",
3665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        iter->size.width(),
3675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        iter->size.height(),
3685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        iter->refresh_rate,
3695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        iter->interlaced ? 'I' : 'P',
3705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        iter->native ? "(N)" : "");
371a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
3725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return ToString() + ", display_modes==" + display_modes_str;
373a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)}
374a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
375effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid DisplayInfo::SetColorProfile(ui::ColorCalibrationProfile profile) {
376effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (IsColorProfileAvailable(profile))
377effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    color_profile_ = profile;
378effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
379effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
380effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochbool DisplayInfo::IsColorProfileAvailable(
381effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    ui::ColorCalibrationProfile profile) const {
382effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return std::find(available_color_profiles_.begin(),
383effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                   available_color_profiles_.end(),
384effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                   profile) != available_color_profiles_.end();
385effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
386effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
3872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}  // namespace ash
388