desktop_screen_x11.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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/debug/trace_event.h"
14#include "base/logging.h"
15#include "base/x11/edid_parser_x11.h"
16#include "ui/aura/window.h"
17#include "ui/aura/window_event_dispatcher.h"
18#include "ui/aura/window_tree_host.h"
19#include "ui/base/layout.h"
20#include "ui/base/x/x11_util.h"
21#include "ui/gfx/display.h"
22#include "ui/gfx/display_observer.h"
23#include "ui/gfx/native_widget_types.h"
24#include "ui/gfx/screen.h"
25#include "ui/gfx/x/x11_types.h"
26#include "ui/views/widget/desktop_aura/desktop_screen.h"
27#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
28
29namespace {
30
31// The delay to perform configuration after RRNotify.  See the comment
32// in |Dispatch()|.
33const int64 kConfigureDelayMs = 500;
34
35float GetDeviceScaleFactor(int screen_pixels, int screen_mm) {
36  const int kCSSDefaultDPI = 96;
37  const float kInchInMm = 25.4f;
38
39  float screen_inches = screen_mm / kInchInMm;
40  float screen_dpi = screen_pixels / screen_inches;
41  float scale = screen_dpi / kCSSDefaultDPI;
42
43  return ui::GetImageScale(ui::GetSupportedScaleFactor(scale));
44}
45
46std::vector<gfx::Display> GetFallbackDisplayList() {
47  ::XDisplay* display = gfx::GetXDisplay();
48  ::Screen* screen = DefaultScreenOfDisplay(display);
49  int width = WidthOfScreen(screen);
50  int height = HeightOfScreen(screen);
51  int mm_width = WidthMMOfScreen(screen);
52  int mm_height = HeightMMOfScreen(screen);
53
54  gfx::Rect bounds_in_pixels(0, 0, width, height);
55  gfx::Display gfx_display(0, bounds_in_pixels);
56  if (!gfx::Display::HasForceDeviceScaleFactor() &&
57      !ui::IsXDisplaySizeBlackListed(mm_width, mm_height)) {
58    float device_scale_factor = GetDeviceScaleFactor(width, mm_width);
59    DCHECK_LE(1.0f, device_scale_factor);
60    gfx_display.SetScaleAndBounds(device_scale_factor, bounds_in_pixels);
61  }
62
63  return std::vector<gfx::Display>(1, gfx_display);
64}
65
66// Helper class to GetWindowAtScreenPoint() which returns the topmost window at
67// the location passed to FindAt(). NULL is returned if a window which does not
68// belong to Chromium is topmost at the passed in location.
69class ToplevelWindowFinder : public ui::EnumerateWindowsDelegate {
70 public:
71  ToplevelWindowFinder() : toplevel_(NULL) {
72  }
73
74  virtual ~ToplevelWindowFinder() {
75  }
76
77  aura::Window* FindAt(const gfx::Point& screen_loc) {
78    screen_loc_ = screen_loc;
79    ui::EnumerateTopLevelWindows(this);
80    return toplevel_;
81  }
82
83 protected:
84  virtual bool ShouldStopIterating(XID xid) OVERRIDE {
85    aura::Window* window =
86        views::DesktopWindowTreeHostX11::GetContentWindowForXID(xid);
87    if (window) {
88      if (window->IsVisible() &&
89          window->GetBoundsInScreen().Contains(screen_loc_)) {
90        toplevel_ = window;
91        return true;
92      }
93      return false;
94    }
95
96    if (ui::IsWindowVisible(xid) &&
97        ui::WindowContainsPoint(xid, screen_loc_)) {
98      // toplevel_ = NULL
99      return true;
100    }
101    return false;
102  }
103
104  gfx::Point screen_loc_;
105  aura::Window* toplevel_;
106
107  DISALLOW_COPY_AND_ASSIGN(ToplevelWindowFinder);
108};
109
110}  // namespace
111
112namespace views {
113
114////////////////////////////////////////////////////////////////////////////////
115// DesktopScreenX11, public:
116
117DesktopScreenX11::DesktopScreenX11()
118    : xdisplay_(base::MessagePumpX11::GetDefaultXDisplay()),
119      x_root_window_(DefaultRootWindow(xdisplay_)),
120      has_xrandr_(false),
121      xrandr_event_base_(0) {
122  // We only support 1.3+. There were library changes before this and we should
123  // use the new interface instead of the 1.2 one.
124  int randr_version_major = 0;
125  int randr_version_minor = 0;
126  has_xrandr_ = XRRQueryVersion(
127        xdisplay_, &randr_version_major, &randr_version_minor) &&
128      randr_version_major == 1 &&
129      randr_version_minor >= 3;
130
131  if (has_xrandr_) {
132    int error_base_ignored = 0;
133    XRRQueryExtension(xdisplay_, &xrandr_event_base_, &error_base_ignored);
134
135    base::MessagePumpX11::Current()->AddDispatcherForRootWindow(this);
136    XRRSelectInput(xdisplay_,
137                   x_root_window_,
138                   RRScreenChangeNotifyMask | RROutputChangeNotifyMask);
139
140    displays_ = BuildDisplaysFromXRandRInfo();
141  } else {
142    displays_ = GetFallbackDisplayList();
143  }
144}
145
146DesktopScreenX11::~DesktopScreenX11() {
147  if (has_xrandr_)
148    base::MessagePumpX11::Current()->RemoveDispatcherForRootWindow(this);
149}
150
151void DesktopScreenX11::ProcessDisplayChange(
152    const std::vector<gfx::Display>& incoming) {
153  std::vector<gfx::Display> old_displays = displays_;
154  displays_ = incoming;
155
156  typedef std::vector<gfx::Display>::const_iterator DisplayIt;
157  std::vector<gfx::Display>::const_iterator old_it = old_displays.begin();
158  for (; old_it != old_displays.end(); ++old_it) {
159    bool found = false;
160    for (std::vector<gfx::Display>::const_iterator new_it =
161             displays_.begin(); new_it != displays_.end(); ++new_it) {
162      if (old_it->id() == new_it->id()) {
163        found = true;
164        break;
165      }
166    }
167
168    if (!found) {
169      FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
170                        OnDisplayRemoved(*old_it));
171    }
172  }
173
174  std::vector<gfx::Display>::const_iterator new_it = displays_.begin();
175  for (; new_it != displays_.end(); ++new_it) {
176    bool found = false;
177    for (std::vector<gfx::Display>::const_iterator old_it =
178         old_displays.begin(); old_it != old_displays.end(); ++old_it) {
179      if (new_it->id() == old_it->id()) {
180        if (new_it->bounds() != old_it->bounds()) {
181          FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
182                            OnDisplayBoundsChanged(*new_it));
183        }
184
185        found = true;
186        break;
187      }
188    }
189
190    if (!found) {
191      FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
192                        OnDisplayAdded(*new_it));
193    }
194  }
195}
196
197////////////////////////////////////////////////////////////////////////////////
198// DesktopScreenX11, gfx::Screen implementation:
199
200bool DesktopScreenX11::IsDIPEnabled() {
201  return true;
202}
203
204gfx::Point DesktopScreenX11::GetCursorScreenPoint() {
205  TRACE_EVENT0("views", "DesktopScreenX11::GetCursorScreenPoint()");
206
207  XDisplay* display = gfx::GetXDisplay();
208
209  ::Window root, child;
210  int root_x, root_y, win_x, win_y;
211  unsigned int mask;
212  XQueryPointer(display,
213                DefaultRootWindow(display),
214                &root,
215                &child,
216                &root_x,
217                &root_y,
218                &win_x,
219                &win_y,
220                &mask);
221
222  return gfx::Point(root_x, root_y);
223}
224
225gfx::NativeWindow DesktopScreenX11::GetWindowUnderCursor() {
226  return GetWindowAtScreenPoint(GetCursorScreenPoint());
227}
228
229gfx::NativeWindow DesktopScreenX11::GetWindowAtScreenPoint(
230    const gfx::Point& point) {
231  ToplevelWindowFinder finder;
232  return finder.FindAt(point);
233}
234
235int DesktopScreenX11::GetNumDisplays() const {
236  return displays_.size();
237}
238
239std::vector<gfx::Display> DesktopScreenX11::GetAllDisplays() const {
240  return displays_;
241}
242
243gfx::Display DesktopScreenX11::GetDisplayNearestWindow(
244    gfx::NativeView window) const {
245  // Getting screen bounds here safely is hard.
246  //
247  // You'd think we'd be able to just call window->GetBoundsInScreen(), but we
248  // can't because |window| (and the associated WindowEventDispatcher*) can be
249  // partially initialized at this point; WindowEventDispatcher initializations
250  // call through into GetDisplayNearestWindow(). But the X11 resources are
251  // created before we create the aura::WindowEventDispatcher. So we ask what
252  // the DRWHX11 believes the window bounds are instead of going through the
253  // aura::Window's screen bounds.
254  aura::WindowTreeHost* host = window->GetHost();
255  if (host) {
256    DesktopWindowTreeHostX11* rwh = DesktopWindowTreeHostX11::GetHostForXID(
257        host->GetAcceleratedWidget());
258    if (rwh)
259      return GetDisplayMatching(rwh->GetX11RootWindowBounds());
260  }
261
262  return GetPrimaryDisplay();
263}
264
265gfx::Display DesktopScreenX11::GetDisplayNearestPoint(
266    const gfx::Point& point) const {
267  for (std::vector<gfx::Display>::const_iterator it = displays_.begin();
268       it != displays_.end(); ++it) {
269    if (it->bounds().Contains(point))
270      return *it;
271  }
272
273  return GetPrimaryDisplay();
274}
275
276gfx::Display DesktopScreenX11::GetDisplayMatching(
277    const gfx::Rect& match_rect) const {
278  int max_area = 0;
279  const gfx::Display* matching = NULL;
280  for (std::vector<gfx::Display>::const_iterator it = displays_.begin();
281       it != displays_.end(); ++it) {
282    gfx::Rect intersect = gfx::IntersectRects(it->bounds(), match_rect);
283    int area = intersect.width() * intersect.height();
284    if (area > max_area) {
285      max_area = area;
286      matching = &*it;
287    }
288  }
289  // Fallback to the primary display if there is no matching display.
290  return matching ? *matching : GetPrimaryDisplay();
291}
292
293gfx::Display DesktopScreenX11::GetPrimaryDisplay() const {
294  return displays_.front();
295}
296
297void DesktopScreenX11::AddObserver(gfx::DisplayObserver* observer) {
298  observer_list_.AddObserver(observer);
299}
300
301void DesktopScreenX11::RemoveObserver(gfx::DisplayObserver* observer) {
302  observer_list_.RemoveObserver(observer);
303}
304
305uint32_t DesktopScreenX11::Dispatch(const base::NativeEvent& event) {
306  if (event->type - xrandr_event_base_ == RRScreenChangeNotify) {
307    // Pass the event through to xlib.
308    XRRUpdateConfiguration(event);
309  } else if (event->type - xrandr_event_base_ == RRNotify) {
310    // There's some sort of observer dispatch going on here, but I don't think
311    // it's the screen's?
312    if (configure_timer_.get() && configure_timer_->IsRunning()) {
313      configure_timer_->Reset();
314    } else {
315      configure_timer_.reset(new base::OneShotTimer<DesktopScreenX11>());
316      configure_timer_->Start(
317          FROM_HERE,
318          base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
319          this,
320          &DesktopScreenX11::ConfigureTimerFired);
321    }
322  }
323
324  return POST_DISPATCH_NONE;
325}
326
327////////////////////////////////////////////////////////////////////////////////
328// DesktopScreenX11, private:
329
330DesktopScreenX11::DesktopScreenX11(
331    const std::vector<gfx::Display>& test_displays)
332    : xdisplay_(base::MessagePumpX11::GetDefaultXDisplay()),
333      x_root_window_(DefaultRootWindow(xdisplay_)),
334      has_xrandr_(false),
335      xrandr_event_base_(0),
336      displays_(test_displays) {
337}
338
339std::vector<gfx::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() {
340  std::vector<gfx::Display> displays;
341  XRRScreenResources* resources =
342      XRRGetScreenResourcesCurrent(xdisplay_, x_root_window_);
343  if (!resources) {
344    LOG(ERROR) << "XRandR returned no displays. Falling back to Root Window.";
345    return GetFallbackDisplayList();
346  }
347
348  bool has_work_area = false;
349  gfx::Rect work_area;
350  std::vector<int> value;
351  if (ui::GetIntArrayProperty(x_root_window_, "_NET_WORKAREA", &value) &&
352      value.size() >= 4) {
353    work_area = gfx::Rect(value[0], value[1], value[2], value[3]);
354    has_work_area = true;
355  }
356
357  float device_scale_factor = 1.0f;
358  for (int i = 0; i < resources->noutput; ++i) {
359    RROutput output_id = resources->outputs[i];
360    XRROutputInfo* output_info =
361        XRRGetOutputInfo(xdisplay_, resources, output_id);
362
363    bool is_connected = (output_info->connection == RR_Connected);
364    if (!is_connected) {
365      XRRFreeOutputInfo(output_info);
366      continue;
367    }
368
369    if (output_info->crtc) {
370      XRRCrtcInfo *crtc = XRRGetCrtcInfo(xdisplay_,
371                                         resources,
372                                         output_info->crtc);
373
374      int64 display_id = -1;
375      if (!base::GetDisplayId(output_id, i, &display_id)) {
376        // It isn't ideal, but if we can't parse the EDID data, fallback on the
377        // display number.
378        display_id = i;
379      }
380
381      gfx::Rect crtc_bounds(crtc->x, crtc->y, crtc->width, crtc->height);
382      gfx::Display display(display_id, crtc_bounds);
383
384      if (!gfx::Display::HasForceDeviceScaleFactor()) {
385        if (i == 0 && !ui::IsXDisplaySizeBlackListed(output_info->mm_width,
386                                                     output_info->mm_height)) {
387          // As per display scale factor is not supported right now,
388          // the primary display's scale factor is always used.
389          device_scale_factor = GetDeviceScaleFactor(crtc->width,
390                                                     output_info->mm_width);
391          DCHECK_LE(1.0f, device_scale_factor);
392        }
393        display.SetScaleAndBounds(device_scale_factor, crtc_bounds);
394      }
395
396      if (has_work_area) {
397        gfx::Rect intersection = crtc_bounds;
398        intersection.Intersect(work_area);
399        display.set_work_area(intersection);
400      }
401
402      displays.push_back(display);
403
404      XRRFreeCrtcInfo(crtc);
405    }
406
407    XRRFreeOutputInfo(output_info);
408  }
409
410  XRRFreeScreenResources(resources);
411
412  if (displays.empty())
413    return GetFallbackDisplayList();
414
415  return displays;
416}
417
418void DesktopScreenX11::ConfigureTimerFired() {
419  std::vector<gfx::Display> new_displays = BuildDisplaysFromXRandRInfo();
420  ProcessDisplayChange(new_displays);
421}
422
423////////////////////////////////////////////////////////////////////////////////
424
425gfx::Screen* CreateDesktopScreen() {
426  return new DesktopScreenX11;
427}
428
429}  // namespace views
430