desktop_screen_x11.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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_(base::MessagePumpX11::GetDefaultXDisplay()), 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_(base::MessagePumpX11::GetDefaultXDisplay()), 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