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