blob: 76db7948117ba732a386ca60fe56d0cea6a24af5 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/widget/desktop_aura/desktop_screen_x11.h"
#include <X11/extensions/Xrandr.h>
#include <X11/Xlib.h>
// It clashes with out RootWindow.
#undef RootWindow
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/layout.h"
#include "ui/display/util/display_util.h"
#include "ui/display/util/x11/edid_parser_x11.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/display.h"
#include "ui/gfx/display_observer.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/widget/desktop_aura/desktop_screen.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
#include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
namespace {
// The delay to perform configuration after RRNotify. See the comment
// in |Dispatch()|.
const int64 kConfigureDelayMs = 500;
// TODO(oshima): Consider using gtk-xft-dpi instead.
float GetDeviceScaleFactor(int screen_pixels, int screen_mm) {
const int kCSSDefaultDPI = 96;
const float kInchInMm = 25.4f;
float screen_inches = screen_mm / kInchInMm;
float screen_dpi = screen_pixels / screen_inches;
float scale = screen_dpi / kCSSDefaultDPI;
return ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactor(scale));
}
std::vector<gfx::Display> GetFallbackDisplayList() {
::XDisplay* display = gfx::GetXDisplay();
::Screen* screen = DefaultScreenOfDisplay(display);
int width = WidthOfScreen(screen);
int height = HeightOfScreen(screen);
gfx::Size physical_size(WidthMMOfScreen(screen), HeightMMOfScreen(screen));
gfx::Rect bounds_in_pixels(0, 0, width, height);
gfx::Display gfx_display(0, bounds_in_pixels);
if (!gfx::Display::HasForceDeviceScaleFactor() &&
!ui::IsDisplaySizeBlackListed(physical_size)) {
float device_scale_factor = GetDeviceScaleFactor(
width, physical_size.width());
DCHECK_LE(1.0f, device_scale_factor);
gfx_display.SetScaleAndBounds(device_scale_factor, bounds_in_pixels);
}
return std::vector<gfx::Display>(1, gfx_display);
}
} // namespace
namespace views {
////////////////////////////////////////////////////////////////////////////////
// DesktopScreenX11, public:
DesktopScreenX11::DesktopScreenX11()
: xdisplay_(gfx::GetXDisplay()),
x_root_window_(DefaultRootWindow(xdisplay_)),
has_xrandr_(false),
xrandr_event_base_(0) {
// We only support 1.3+. There were library changes before this and we should
// use the new interface instead of the 1.2 one.
int randr_version_major = 0;
int randr_version_minor = 0;
has_xrandr_ = XRRQueryVersion(
xdisplay_, &randr_version_major, &randr_version_minor) &&
randr_version_major == 1 &&
randr_version_minor >= 3;
if (has_xrandr_) {
int error_base_ignored = 0;
XRRQueryExtension(xdisplay_, &xrandr_event_base_, &error_base_ignored);
if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
XRRSelectInput(xdisplay_,
x_root_window_,
RRScreenChangeNotifyMask |
RROutputChangeNotifyMask |
RRCrtcChangeNotifyMask);
displays_ = BuildDisplaysFromXRandRInfo();
} else {
displays_ = GetFallbackDisplayList();
}
}
DesktopScreenX11::~DesktopScreenX11() {
if (has_xrandr_ && ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
}
void DesktopScreenX11::ProcessDisplayChange(
const std::vector<gfx::Display>& incoming) {
std::vector<gfx::Display> old_displays = displays_;
displays_ = incoming;
typedef std::vector<gfx::Display>::const_iterator DisplayIt;
std::vector<gfx::Display>::const_iterator old_it = old_displays.begin();
for (; old_it != old_displays.end(); ++old_it) {
bool found = false;
for (std::vector<gfx::Display>::const_iterator new_it =
displays_.begin(); new_it != displays_.end(); ++new_it) {
if (old_it->id() == new_it->id()) {
found = true;
break;
}
}
if (!found) {
FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
OnDisplayRemoved(*old_it));
}
}
std::vector<gfx::Display>::const_iterator new_it = displays_.begin();
for (; new_it != displays_.end(); ++new_it) {
bool found = false;
for (std::vector<gfx::Display>::const_iterator old_it =
old_displays.begin(); old_it != old_displays.end(); ++old_it) {
if (new_it->id() != old_it->id())
continue;
uint32_t metrics = gfx::DisplayObserver::DISPLAY_METRIC_NONE;
if (new_it->bounds() != old_it->bounds())
metrics |= gfx::DisplayObserver::DISPLAY_METRIC_BOUNDS;
if (new_it->rotation() != old_it->rotation())
metrics |= gfx::DisplayObserver::DISPLAY_METRIC_ROTATION;
if (new_it->work_area() != old_it->work_area())
metrics |= gfx::DisplayObserver::DISPLAY_METRIC_WORK_AREA;
if (new_it->device_scale_factor() != old_it->device_scale_factor())
metrics |= gfx::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR;
if (metrics != gfx::DisplayObserver::DISPLAY_METRIC_NONE) {
FOR_EACH_OBSERVER(gfx::DisplayObserver,
observer_list_,
OnDisplayMetricsChanged(*new_it, metrics));
}
found = true;
break;
}
if (!found) {
FOR_EACH_OBSERVER(gfx::DisplayObserver, observer_list_,
OnDisplayAdded(*new_it));
}
}
}
////////////////////////////////////////////////////////////////////////////////
// DesktopScreenX11, gfx::Screen implementation:
bool DesktopScreenX11::IsDIPEnabled() {
return true;
}
gfx::Point DesktopScreenX11::GetCursorScreenPoint() {
TRACE_EVENT0("views", "DesktopScreenX11::GetCursorScreenPoint()");
XDisplay* display = gfx::GetXDisplay();
::Window root, child;
int root_x, root_y, win_x, win_y;
unsigned int mask;
XQueryPointer(display,
DefaultRootWindow(display),
&root,
&child,
&root_x,
&root_y,
&win_x,
&win_y,
&mask);
return gfx::Point(root_x, root_y);
}
gfx::NativeWindow DesktopScreenX11::GetWindowUnderCursor() {
return GetWindowAtScreenPoint(GetCursorScreenPoint());
}
gfx::NativeWindow DesktopScreenX11::GetWindowAtScreenPoint(
const gfx::Point& point) {
X11TopmostWindowFinder finder;
return finder.FindLocalProcessWindowAt(point, std::set<aura::Window*>());
}
int DesktopScreenX11::GetNumDisplays() const {
return displays_.size();
}
std::vector<gfx::Display> DesktopScreenX11::GetAllDisplays() const {
return displays_;
}
gfx::Display DesktopScreenX11::GetDisplayNearestWindow(
gfx::NativeView window) const {
// Getting screen bounds here safely is hard.
//
// You'd think we'd be able to just call window->GetBoundsInScreen(), but we
// can't because |window| (and the associated WindowEventDispatcher*) can be
// partially initialized at this point; WindowEventDispatcher initializations
// call through into GetDisplayNearestWindow(). But the X11 resources are
// created before we create the aura::WindowEventDispatcher. So we ask what
// the DRWHX11 believes the window bounds are instead of going through the
// aura::Window's screen bounds.
aura::WindowTreeHost* host = window->GetHost();
if (host) {
DesktopWindowTreeHostX11* rwh = DesktopWindowTreeHostX11::GetHostForXID(
host->GetAcceleratedWidget());
if (rwh)
return GetDisplayMatching(rwh->GetX11RootWindowBounds());
}
return GetPrimaryDisplay();
}
gfx::Display DesktopScreenX11::GetDisplayNearestPoint(
const gfx::Point& point) const {
for (std::vector<gfx::Display>::const_iterator it = displays_.begin();
it != displays_.end(); ++it) {
if (it->bounds().Contains(point))
return *it;
}
return GetPrimaryDisplay();
}
gfx::Display DesktopScreenX11::GetDisplayMatching(
const gfx::Rect& match_rect) const {
int max_area = 0;
const gfx::Display* matching = NULL;
for (std::vector<gfx::Display>::const_iterator it = displays_.begin();
it != displays_.end(); ++it) {
gfx::Rect intersect = gfx::IntersectRects(it->bounds(), match_rect);
int area = intersect.width() * intersect.height();
if (area > max_area) {
max_area = area;
matching = &*it;
}
}
// Fallback to the primary display if there is no matching display.
return matching ? *matching : GetPrimaryDisplay();
}
gfx::Display DesktopScreenX11::GetPrimaryDisplay() const {
return displays_.front();
}
void DesktopScreenX11::AddObserver(gfx::DisplayObserver* observer) {
observer_list_.AddObserver(observer);
}
void DesktopScreenX11::RemoveObserver(gfx::DisplayObserver* observer) {
observer_list_.RemoveObserver(observer);
}
bool DesktopScreenX11::CanDispatchEvent(const ui::PlatformEvent& event) {
return event->type - xrandr_event_base_ == RRScreenChangeNotify ||
event->type - xrandr_event_base_ == RRNotify;
}
uint32_t DesktopScreenX11::DispatchEvent(const ui::PlatformEvent& event) {
if (event->type - xrandr_event_base_ == RRScreenChangeNotify) {
// Pass the event through to xlib.
XRRUpdateConfiguration(event);
} else if (event->type - xrandr_event_base_ == RRNotify) {
// There's some sort of observer dispatch going on here, but I don't think
// it's the screen's?
if (configure_timer_.get() && configure_timer_->IsRunning()) {
configure_timer_->Reset();
} else {
configure_timer_.reset(new base::OneShotTimer<DesktopScreenX11>());
configure_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
this,
&DesktopScreenX11::ConfigureTimerFired);
}
} else {
NOTREACHED();
}
return ui::POST_DISPATCH_NONE;
}
////////////////////////////////////////////////////////////////////////////////
// DesktopScreenX11, private:
DesktopScreenX11::DesktopScreenX11(
const std::vector<gfx::Display>& test_displays)
: xdisplay_(gfx::GetXDisplay()),
x_root_window_(DefaultRootWindow(xdisplay_)),
has_xrandr_(false),
xrandr_event_base_(0),
displays_(test_displays) {
}
std::vector<gfx::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() {
std::vector<gfx::Display> displays;
XRRScreenResources* resources =
XRRGetScreenResourcesCurrent(xdisplay_, x_root_window_);
if (!resources) {
LOG(ERROR) << "XRandR returned no displays. Falling back to Root Window.";
return GetFallbackDisplayList();
}
bool has_work_area = false;
gfx::Rect work_area;
std::vector<int> value;
if (ui::GetIntArrayProperty(x_root_window_, "_NET_WORKAREA", &value) &&
value.size() >= 4) {
work_area = gfx::Rect(value[0], value[1], value[2], value[3]);
has_work_area = true;
}
float device_scale_factor = 1.0f;
for (int i = 0; i < resources->noutput; ++i) {
RROutput output_id = resources->outputs[i];
XRROutputInfo* output_info =
XRRGetOutputInfo(xdisplay_, resources, output_id);
bool is_connected = (output_info->connection == RR_Connected);
if (!is_connected) {
XRRFreeOutputInfo(output_info);
continue;
}
if (output_info->crtc) {
XRRCrtcInfo *crtc = XRRGetCrtcInfo(xdisplay_,
resources,
output_info->crtc);
int64 display_id = -1;
if (!ui::GetDisplayId(output_id, static_cast<uint8>(i), &display_id)) {
// It isn't ideal, but if we can't parse the EDID data, fallback on the
// display number.
display_id = i;
}
gfx::Rect crtc_bounds(crtc->x, crtc->y, crtc->width, crtc->height);
gfx::Display display(display_id, crtc_bounds);
if (!gfx::Display::HasForceDeviceScaleFactor()) {
if (i == 0 && !ui::IsDisplaySizeBlackListed(
gfx::Size(output_info->mm_width, output_info->mm_height))) {
// As per display scale factor is not supported right now,
// the primary display's scale factor is always used.
device_scale_factor = GetDeviceScaleFactor(crtc->width,
output_info->mm_width);
DCHECK_LE(1.0f, device_scale_factor);
}
display.SetScaleAndBounds(device_scale_factor, crtc_bounds);
}
if (has_work_area) {
gfx::Rect intersection = crtc_bounds;
intersection.Intersect(work_area);
display.set_work_area(intersection);
}
switch (crtc->rotation) {
case RR_Rotate_0:
display.set_rotation(gfx::Display::ROTATE_0);
break;
case RR_Rotate_90:
display.set_rotation(gfx::Display::ROTATE_90);
break;
case RR_Rotate_180:
display.set_rotation(gfx::Display::ROTATE_180);
break;
case RR_Rotate_270:
display.set_rotation(gfx::Display::ROTATE_270);
break;
}
displays.push_back(display);
XRRFreeCrtcInfo(crtc);
}
XRRFreeOutputInfo(output_info);
}
XRRFreeScreenResources(resources);
if (displays.empty())
return GetFallbackDisplayList();
return displays;
}
void DesktopScreenX11::ConfigureTimerFired() {
std::vector<gfx::Display> new_displays = BuildDisplaysFromXRandRInfo();
ProcessDisplayChange(new_displays);
}
////////////////////////////////////////////////////////////////////////////////
gfx::Screen* CreateDesktopScreen() {
return new DesktopScreenX11;
}
} // namespace views