1// Copyright (c) 2012 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 "ash/display/display_change_observer_x11.h"
6
7#include <algorithm>
8#include <map>
9#include <set>
10#include <vector>
11
12#include <X11/extensions/Xrandr.h>
13
14#include "ash/ash_switches.h"
15#include "ash/display/display_info.h"
16#include "ash/display/display_layout_store.h"
17#include "ash/display/display_manager.h"
18#include "ash/display/display_util_x11.h"
19#include "ash/shell.h"
20#include "base/command_line.h"
21#include "base/message_loop/message_pump_aurax11.h"
22#include "chromeos/display/output_util.h"
23#include "grit/ash_strings.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/compositor/dip_util.h"
26#include "ui/gfx/display.h"
27
28namespace ash {
29namespace internal {
30namespace {
31
32// The DPI threshold to detect high density screen.
33// Higher DPI than this will use device_scale_factor=2.
34const unsigned int kHighDensityDPIThreshold = 160;
35
36// 1 inch in mm.
37const float kInchInMm = 25.4f;
38
39int64 GetDisplayId(XID output, size_t output_index) {
40  int64 display_id;
41  if (chromeos::GetDisplayId(output, output_index, &display_id))
42    return display_id;
43  return gfx::Display::kInvalidDisplayID;
44}
45
46}  // namespace
47
48DisplayChangeObserverX11::DisplayChangeObserverX11()
49    : xdisplay_(base::MessagePumpAuraX11::GetDefaultXDisplay()),
50      x_root_window_(DefaultRootWindow(xdisplay_)),
51      xrandr_event_base_(0) {
52  int error_base_ignored;
53  XRRQueryExtension(xdisplay_, &xrandr_event_base_, &error_base_ignored);
54
55  Shell::GetInstance()->AddShellObserver(this);
56}
57
58DisplayChangeObserverX11::~DisplayChangeObserverX11() {
59  Shell::GetInstance()->RemoveShellObserver(this);
60}
61
62chromeos::OutputState DisplayChangeObserverX11::GetStateForDisplayIds(
63    const std::vector<int64>& display_ids) const {
64  if (CommandLine::ForCurrentProcess()->HasSwitch(
65          switches::kAshForceMirrorMode)) {
66    return chromeos::STATE_DUAL_MIRROR;
67  }
68
69  CHECK_EQ(2U, display_ids.size());
70  DisplayIdPair pair = std::make_pair(display_ids[0], display_ids[1]);
71  DisplayLayout layout = Shell::GetInstance()->display_manager()->
72      layout_store()->GetRegisteredDisplayLayout(pair);
73  return layout.mirrored ?
74      chromeos::STATE_DUAL_MIRROR : chromeos::STATE_DUAL_EXTENDED;
75}
76
77bool DisplayChangeObserverX11::GetResolutionForDisplayId(int64 display_id,
78                                                         int* width,
79                                                         int* height) const {
80
81  gfx::Size resolution;
82  if (!Shell::GetInstance()->display_manager()->
83      GetSelectedResolutionForDisplayId(display_id, &resolution)) {
84    return false;
85  }
86
87  *width = resolution.width();
88  *height = resolution.height();
89  return true;
90}
91
92void DisplayChangeObserverX11::OnDisplayModeChanged() {
93  XRRScreenResources* screen_resources =
94      XRRGetScreenResources(xdisplay_, x_root_window_);
95  std::map<XID, XRRCrtcInfo*> crtc_info_map;
96
97  for (int c = 0; c < screen_resources->ncrtc; c++) {
98    XID crtc_id = screen_resources->crtcs[c];
99    XRRCrtcInfo *crtc_info =
100        XRRGetCrtcInfo(xdisplay_, screen_resources, crtc_id);
101    crtc_info_map[crtc_id] = crtc_info;
102  }
103
104  std::vector<DisplayInfo> displays;
105  std::set<int64> ids;
106  for (int output_index = 0; output_index < screen_resources->noutput;
107       output_index++) {
108    XID output = screen_resources->outputs[output_index];
109    XRROutputInfo *output_info =
110        XRRGetOutputInfo(xdisplay_, screen_resources, output);
111
112    const bool is_internal = chromeos::IsInternalOutputName(
113        std::string(output_info->name));
114
115    if (is_internal &&
116        gfx::Display::InternalDisplayId() == gfx::Display::kInvalidDisplayID) {
117      int64 id = GetDisplayId(output, output_index);
118      // Fallback to output index. crbug.com/180100
119      gfx::Display::SetInternalDisplayId(
120          id == gfx::Display::kInvalidDisplayID ? output_index : id);
121    }
122
123    if (output_info->connection != RR_Connected) {
124      XRRFreeOutputInfo(output_info);
125      continue;
126    }
127    const XRRCrtcInfo* crtc_info = crtc_info_map[output_info->crtc];
128    if (!crtc_info) {
129      LOG(WARNING) << "Crtc not found for output: output_index="
130                   << output_index;
131      continue;
132    }
133    const XRRModeInfo* mode =
134        chromeos::FindModeInfo(screen_resources, crtc_info->mode);
135    if (!mode) {
136      LOG(WARNING) << "Could not find a mode for the output: output_index="
137                   << output_index;
138      continue;
139    }
140
141    float device_scale_factor = 1.0f;
142    if (!ShouldIgnoreSize(output_info->mm_width, output_info->mm_height) &&
143        (kInchInMm * mode->width / output_info->mm_width) >
144        kHighDensityDPIThreshold) {
145      device_scale_factor = 2.0f;
146    }
147    gfx::Rect display_bounds(
148        crtc_info->x, crtc_info->y, mode->width, mode->height);
149
150    std::vector<Resolution> resolutions;
151    if (!is_internal)
152      resolutions = GetResolutionList(screen_resources, output_info);
153
154    XRRFreeOutputInfo(output_info);
155
156    std::string name = is_internal ?
157        l10n_util::GetStringUTF8(IDS_ASH_INTERNAL_DISPLAY_NAME) :
158        chromeos::GetDisplayName(output);
159    if (name.empty())
160      name = l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
161
162    bool has_overscan = false;
163    chromeos::GetOutputOverscanFlag(output, &has_overscan);
164
165    int64 id = GetDisplayId(output, output_index);
166
167    // If ID is invalid or there is an duplicate, just use output index.
168    if (id == gfx::Display::kInvalidDisplayID || ids.find(id) != ids.end())
169      id = output_index;
170    ids.insert(id);
171
172    displays.push_back(DisplayInfo(id, name, has_overscan));
173    displays.back().set_device_scale_factor(device_scale_factor);
174    displays.back().SetBounds(display_bounds);
175    displays.back().set_native(true);
176    displays.back().set_resolutions(resolutions);
177  }
178
179  // Free all allocated resources.
180  for (std::map<XID, XRRCrtcInfo*>::const_iterator iter = crtc_info_map.begin();
181       iter != crtc_info_map.end(); ++iter) {
182    XRRFreeCrtcInfo(iter->second);
183  }
184  XRRFreeScreenResources(screen_resources);
185
186  // DisplayManager can be null during the boot.
187  Shell::GetInstance()->display_manager()->OnNativeDisplaysChanged(displays);
188}
189
190void DisplayChangeObserverX11::OnAppTerminating() {
191#if defined(USE_ASH)
192  // Stop handling display configuration events once the shutdown
193  // process starts. crbug.com/177014.
194  Shell::GetInstance()->output_configurator()->Stop();
195#endif
196}
197
198}  // namespace internal
199}  // namespace ash
200