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