desktop_screen_x11.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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 "ui/views/widget/desktop_aura/desktop_screen_x11.h"
6
7#include <X11/extensions/Xrandr.h>
8#include <X11/Xlib.h>
9
10// It clashes with out RootWindow.
11#undef RootWindow
12
13#include "base/logging.h"
14#include "base/x11/edid_parser_x11.h"
15#include "ui/aura/root_window.h"
16#include "ui/aura/root_window_host.h"
17#include "ui/base/x/x11_util.h"
18#include "ui/gfx/display.h"
19#include "ui/gfx/display_observer.h"
20#include "ui/gfx/native_widget_types.h"
21#include "ui/gfx/screen.h"
22#include "ui/gfx/x/x11_types.h"
23#include "ui/views/widget/desktop_aura/desktop_screen.h"
24
25namespace {
26
27// The delay to perform configuration after RRNotify.  See the comment
28// in |Dispatch()|.
29const int64 kConfigureDelayMs = 500;
30
31std::vector<gfx::Display> GetFallbackDisplayList() {
32  ::XDisplay* display = gfx::GetXDisplay();
33  ::Screen* screen = DefaultScreenOfDisplay(display);
34  int width = WidthOfScreen(screen);
35  int height = HeightOfScreen(screen);
36
37  return std::vector<gfx::Display>(
38      1, gfx::Display(0, gfx::Rect(0, 0, width, height)));
39}
40
41}  // namespace
42
43namespace views {
44
45////////////////////////////////////////////////////////////////////////////////
46// DesktopScreenX11, public:
47
48DesktopScreenX11::DesktopScreenX11()
49    : xdisplay_(base::MessagePumpX11::GetDefaultXDisplay()),
50      x_root_window_(DefaultRootWindow(xdisplay_)),
51      has_xrandr_(false),
52      xrandr_event_base_(0) {
53  // We only support 1.3+. There were library changes before this and we should
54  // use the new interface instead of the 1.2 one.
55  int randr_version_major = 0;
56  int randr_version_minor = 0;
57  has_xrandr_ = XRRQueryVersion(
58        xdisplay_, &randr_version_major, &randr_version_minor) &&
59      randr_version_major == 1 &&
60      randr_version_minor >= 3;
61
62  if (has_xrandr_) {
63    int error_base_ignored = 0;
64    XRRQueryExtension(xdisplay_, &xrandr_event_base_, &error_base_ignored);
65
66    base::MessagePumpX11::Current()->AddDispatcherForRootWindow(this);
67    XRRSelectInput(xdisplay_,
68                   x_root_window_,
69                   RRScreenChangeNotifyMask | RROutputChangeNotifyMask);
70
71    displays_ = BuildDisplaysFromXRandRInfo();
72  } else {
73    displays_ = GetFallbackDisplayList();
74  }
75}
76
77DesktopScreenX11::~DesktopScreenX11() {
78  if (has_xrandr_)
79    base::MessagePumpX11::Current()->RemoveDispatcherForRootWindow(this);
80}
81
82void DesktopScreenX11::ProcessDisplayChange(
83    const std::vector<gfx::Display>& incoming) {
84  std::vector<gfx::Display>::const_iterator cur_it = displays_.begin();
85  for (; cur_it != displays_.end(); ++cur_it) {
86    bool found = false;
87    for (std::vector<gfx::Display>::const_iterator incoming_it =
88             incoming.begin(); incoming_it != incoming.end(); ++incoming_it) {
89      if (cur_it->id() == incoming_it->id()) {
90        found = true;
91        break;
92      }
93    }
94
95    if (!found) {
96      FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
97                        OnDisplayRemoved(*cur_it));
98    }
99  }
100
101  std::vector<gfx::Display>::const_iterator incoming_it = incoming.begin();
102  for (; incoming_it != incoming.end(); ++incoming_it) {
103    bool found = false;
104    for (std::vector<gfx::Display>::const_iterator cur_it = displays_.begin();
105         cur_it != displays_.end(); ++cur_it) {
106      if (incoming_it->id() == cur_it->id()) {
107        if (incoming_it->bounds() != cur_it->bounds()) {
108          FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
109                            OnDisplayBoundsChanged(*incoming_it));
110        }
111
112        found = true;
113        break;
114      }
115    }
116
117    if (!found) {
118      FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
119                        OnDisplayAdded(*incoming_it));
120    }
121  }
122
123  displays_ = incoming;
124}
125
126////////////////////////////////////////////////////////////////////////////////
127// DesktopScreenX11, gfx::Screen implementation:
128
129bool DesktopScreenX11::IsDIPEnabled() {
130  return false;
131}
132
133gfx::Point DesktopScreenX11::GetCursorScreenPoint() {
134  XDisplay* display = gfx::GetXDisplay();
135
136  ::Window root, child;
137  int root_x, root_y, win_x, win_y;
138  unsigned int mask;
139  XQueryPointer(display,
140                DefaultRootWindow(display),
141                &root,
142                &child,
143                &root_x,
144                &root_y,
145                &win_x,
146                &win_y,
147                &mask);
148
149  return gfx::Point(root_x, root_y);
150}
151
152gfx::NativeWindow DesktopScreenX11::GetWindowUnderCursor() {
153  return GetWindowAtScreenPoint(GetCursorScreenPoint());
154}
155
156gfx::NativeWindow DesktopScreenX11::GetWindowAtScreenPoint(
157    const gfx::Point& point) {
158  // TODO(erg): Implement using the discussion at
159  // http://codereview.chromium.org/10279005/
160  NOTIMPLEMENTED();
161  return NULL;
162}
163
164int DesktopScreenX11::GetNumDisplays() const {
165  return displays_.size();
166}
167
168std::vector<gfx::Display> DesktopScreenX11::GetAllDisplays() const {
169  return displays_;
170}
171
172gfx::Display DesktopScreenX11::GetDisplayNearestWindow(
173    gfx::NativeView window) const {
174  // TODO(erg): This should theoretically be easy, but it isn't. At the time we
175  // get called here, our aura::Window has not been Init()ed, because this
176  // method is called to get the device scale factor as part of
177  // RootWindow::Init(), before Window::Init(). This seems very confused; we're
178  // trying to get a display nearest window even before we've allocated the
179  // root window. Once fixed, the correct implementation should probably be:
180  //
181  //   return GetDisplayMatching(window->GetBoundsInScreen());
182  //
183  // But at least for now, we'll just fallback:
184  return GetPrimaryDisplay();
185}
186
187gfx::Display DesktopScreenX11::GetDisplayNearestPoint(
188    const gfx::Point& point) const {
189  for (std::vector<gfx::Display>::const_iterator it = displays_.begin();
190       it != displays_.end(); ++it) {
191    if (it->bounds().Contains(point))
192      return *it;
193  }
194
195  return GetPrimaryDisplay();
196}
197
198gfx::Display DesktopScreenX11::GetDisplayMatching(
199    const gfx::Rect& match_rect) const {
200  int max_area = 0;
201  const gfx::Display* matching = NULL;
202  for (std::vector<gfx::Display>::const_iterator it = displays_.begin();
203       it != displays_.end(); ++it) {
204    gfx::Rect intersect = gfx::IntersectRects(it->bounds(), match_rect);
205    int area = intersect.width() * intersect.height();
206    if (area > max_area) {
207      max_area = area;
208      matching = &*it;
209    }
210  }
211  // Fallback to the primary display if there is no matching display.
212  return matching ? *matching : GetPrimaryDisplay();
213}
214
215gfx::Display DesktopScreenX11::GetPrimaryDisplay() const {
216  return displays_.front();
217}
218
219void DesktopScreenX11::AddObserver(gfx::DisplayObserver* observer) {
220  observer_list_.AddObserver(observer);
221}
222
223void DesktopScreenX11::RemoveObserver(gfx::DisplayObserver* observer) {
224  observer_list_.RemoveObserver(observer);
225}
226
227bool DesktopScreenX11::Dispatch(const base::NativeEvent& event) {
228  if (event->type - xrandr_event_base_ == RRScreenChangeNotify) {
229    // Pass the event through to xlib.
230    XRRUpdateConfiguration(event);
231  } else if (event->type - xrandr_event_base_ == RRNotify) {
232    // There's some sort of observer dispatch going on here, but I don't think
233    // it's the screen's?
234    DLOG(ERROR) << "DesktopScreenX11::Dispatch() -> RRNotify";
235
236    if (configure_timer_.get()) {
237      configure_timer_->Reset();
238    } else {
239      configure_timer_.reset(new base::OneShotTimer<DesktopScreenX11>());
240      configure_timer_->Start(
241          FROM_HERE,
242          base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
243          this,
244          &DesktopScreenX11::ConfigureTimerFired);
245    }
246  }
247
248  return true;
249}
250
251////////////////////////////////////////////////////////////////////////////////
252// DesktopScreenX11, private:
253
254DesktopScreenX11::DesktopScreenX11(
255    const std::vector<gfx::Display>& test_displays)
256    : xdisplay_(base::MessagePumpX11::GetDefaultXDisplay()),
257      x_root_window_(DefaultRootWindow(xdisplay_)),
258      has_xrandr_(false),
259      xrandr_event_base_(0),
260      displays_(test_displays) {
261}
262
263std::vector<gfx::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() {
264  std::vector<gfx::Display> displays;
265  XRRScreenResources* resources =
266      XRRGetScreenResourcesCurrent(xdisplay_, x_root_window_);
267  if (!resources) {
268    LOG(ERROR) << "XRandR returned no displays. Falling back to Root Window.";
269    return GetFallbackDisplayList();
270  }
271
272  bool has_work_area = false;
273  gfx::Rect work_area;
274  std::vector<int> value;
275  if (ui::GetIntArrayProperty(x_root_window_, "_NET_WORKAREA", &value) &&
276      value.size() >= 4) {
277    work_area = gfx::Rect(value[0], value[1], value[2], value[3]);
278    has_work_area = true;
279  }
280
281  for (int i = 0; i < resources->noutput; ++i) {
282    RROutput output_id = resources->outputs[i];
283    XRROutputInfo* output_info =
284        XRRGetOutputInfo(xdisplay_, resources, output_id);
285
286    bool is_connected = (output_info->connection == RR_Connected);
287    if (!is_connected) {
288      XRRFreeOutputInfo(output_info);
289      continue;
290    }
291
292    if (output_info->crtc) {
293      XRRCrtcInfo *crtc = XRRGetCrtcInfo(xdisplay_,
294                                         resources,
295                                         output_info->crtc);
296
297      int64 display_id = -1;
298      if (!base::GetDisplayId(output_id, i, &display_id)) {
299        // It isn't ideal, but if we can't parse the EDID data, fallback on the
300        // display number.
301        display_id = i;
302      }
303
304      gfx::Rect crtc_bounds(crtc->x, crtc->y, crtc->width, crtc->height);
305      gfx::Display display(display_id, crtc_bounds);
306      if (has_work_area) {
307        gfx::Rect intersection = crtc_bounds;
308        intersection.Intersect(work_area);
309        display.set_work_area(intersection);
310      }
311
312      displays.push_back(display);
313
314      XRRFreeCrtcInfo(crtc);
315    }
316
317    XRRFreeOutputInfo(output_info);
318  }
319
320  XRRFreeScreenResources(resources);
321
322  if (displays.empty())
323    return GetFallbackDisplayList();
324
325  return displays;
326}
327
328void DesktopScreenX11::ConfigureTimerFired() {
329  std::vector<gfx::Display> new_displays = BuildDisplaysFromXRandRInfo();
330  ProcessDisplayChange(new_displays);
331}
332
333////////////////////////////////////////////////////////////////////////////////
334
335gfx::Screen* CreateDesktopScreen() {
336  return new DesktopScreenX11;
337}
338
339}  // namespace views
340