| OLD | NEW |
| (Empty) |
| 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 "chromeos/monitor/output_configurator.h" | |
| 6 | |
| 7 #include <X11/Xlib.h> | |
| 8 #include <X11/extensions/dpms.h> | |
| 9 #include <X11/extensions/Xrandr.h> | |
| 10 | |
| 11 // Xlib defines Status as int which causes our include of dbus/bus.h to fail | |
| 12 // when it tries to name an enum Status. Thus, we need to undefine it (note | |
| 13 // that this will cause a problem if code needs to use the Status type). | |
| 14 // RootWindow causes similar problems in that there is a Chromium type with that | |
| 15 // name. | |
| 16 #undef Status | |
| 17 #undef RootWindow | |
| 18 | |
| 19 #include "base/chromeos/chromeos_version.h" | |
| 20 #include "base/logging.h" | |
| 21 #include "base/message_pump_aurax11.h" | |
| 22 #include "chromeos/dbus/dbus_thread_manager.h" | |
| 23 #include "dbus/bus.h" | |
| 24 #include "dbus/exported_object.h" | |
| 25 #include "dbus/message.h" | |
| 26 #include "dbus/object_path.h" | |
| 27 #include "dbus/object_proxy.h" | |
| 28 #include "third_party/cros_system_api/dbus/service_constants.h" | |
| 29 | |
| 30 namespace chromeos { | |
| 31 | |
| 32 namespace { | |
| 33 // DPI measurements. | |
| 34 const float kMmInInch = 25.4; | |
| 35 const float kDpi96 = 96.0; | |
| 36 const float kPixelsToMmScale = kMmInInch / kDpi96; | |
| 37 | |
| 38 // The DPI threshold to detech high density screen. | |
| 39 // Higher DPI than this will use device_scale_factor=2 | |
| 40 // Should be kept in sync with monitor_change_observer_x11.cc | |
| 41 const unsigned int kHighDensityDIPThreshold = 160; | |
| 42 | |
| 43 // Prefixes for the built-in displays. | |
| 44 const char kInternal_LVDS[] = "LVDS"; | |
| 45 const char kInternal_eDP[] = "eDP"; | |
| 46 | |
| 47 // Gap between screens so cursor at bottom of active monitor doesn't partially | |
| 48 // appear on top of inactive monitor. Higher numbers guard against larger | |
| 49 // cursors, but also waste more memory. We will double this gap for screens | |
| 50 // with a device_scale_factor of 2. While this gap will not guard against all | |
| 51 // possible cursors in X, it should handle the ones we actually use. See | |
| 52 // crbug.com/130188 | |
| 53 const int kVerticalGap = 30; | |
| 54 | |
| 55 // TODO: Determine if we need to organize modes in a way which provides better | |
| 56 // than O(n) lookup time. In many call sites, for example, the "next" mode is | |
| 57 // typically what we are looking for so using this helper might be too | |
| 58 // expensive. | |
| 59 static XRRModeInfo* ModeInfoForID(XRRScreenResources* screen, RRMode modeID) { | |
| 60 XRRModeInfo* result = NULL; | |
| 61 for (int i = 0; (i < screen->nmode) && (result == NULL); i++) | |
| 62 if (modeID == screen->modes[i].id) | |
| 63 result = &screen->modes[i]; | |
| 64 | |
| 65 // We can't fail to find a mode referenced from the same screen. | |
| 66 CHECK(result != NULL); | |
| 67 return result; | |
| 68 } | |
| 69 | |
| 70 // Identifies the modes which will be used by the respective outputs when in a | |
| 71 // mirror mode. This means that the two modes will have the same resolution. | |
| 72 // The RROutput IDs |one| and |two| are used to look up the modes and | |
| 73 // |out_one_mode| and |out_two_mode| are the out-parameters for the respective | |
| 74 // modes. | |
| 75 // Returns false if it fails to find a compatible set of modes. | |
| 76 static bool FindMirrorModeForOutputs(Display* display, | |
| 77 XRRScreenResources* screen, | |
| 78 RROutput one, | |
| 79 RROutput two, | |
| 80 RRMode* out_one_mode, | |
| 81 RRMode* out_two_mode) { | |
| 82 XRROutputInfo* primary = XRRGetOutputInfo(display, screen, one); | |
| 83 XRROutputInfo* secondary = XRRGetOutputInfo(display, screen, two); | |
| 84 | |
| 85 int one_index = 0; | |
| 86 int two_index = 0; | |
| 87 bool found = false; | |
| 88 while (!found && | |
| 89 (one_index < primary->nmode) && | |
| 90 (two_index < secondary->nmode)) { | |
| 91 RRMode one_id = primary->modes[one_index]; | |
| 92 RRMode two_id = secondary->modes[two_index]; | |
| 93 XRRModeInfo* one_mode = ModeInfoForID(screen, one_id); | |
| 94 XRRModeInfo* two_mode = ModeInfoForID(screen, two_id); | |
| 95 int one_width = one_mode->width; | |
| 96 int one_height = one_mode->height; | |
| 97 int two_width = two_mode->width; | |
| 98 int two_height = two_mode->height; | |
| 99 if ((one_width == two_width) && (one_height == two_height)) { | |
| 100 *out_one_mode = one_id; | |
| 101 *out_two_mode = two_id; | |
| 102 found = true; | |
| 103 } else { | |
| 104 // The sort order of the modes is NOT by mode area but is sorted by width, | |
| 105 // then by height within each like width. | |
| 106 if (one_width > two_width) { | |
| 107 one_index += 1; | |
| 108 } else if (one_width < two_width) { | |
| 109 two_index += 1; | |
| 110 } else { | |
| 111 if (one_height > two_height) { | |
| 112 one_index += 1; | |
| 113 } else { | |
| 114 two_index += 1; | |
| 115 } | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 XRRFreeOutputInfo(primary); | |
| 120 XRRFreeOutputInfo(secondary); | |
| 121 return found; | |
| 122 } | |
| 123 | |
| 124 // A helper to call XRRSetCrtcConfig with the given options but some of our | |
| 125 // default output count and rotation arguments. | |
| 126 static void ConfigureCrtc(Display *display, | |
| 127 XRRScreenResources* screen, | |
| 128 RRCrtc crtc, | |
| 129 int x, | |
| 130 int y, | |
| 131 RRMode mode, | |
| 132 RROutput output) { | |
| 133 const Rotation kRotate = RR_Rotate_0; | |
| 134 RROutput* outputs = NULL; | |
| 135 int num_outputs = 0; | |
| 136 | |
| 137 // Check the output and mode argument - if either are None, we should disable. | |
| 138 if ((output != None) && (mode != None)) { | |
| 139 outputs = &output; | |
| 140 num_outputs = 1; | |
| 141 } | |
| 142 | |
| 143 XRRSetCrtcConfig(display, | |
| 144 screen, | |
| 145 crtc, | |
| 146 CurrentTime, | |
| 147 x, | |
| 148 y, | |
| 149 mode, | |
| 150 kRotate, | |
| 151 outputs, | |
| 152 num_outputs); | |
| 153 if (num_outputs == 1) { | |
| 154 // We are enabling a display so make sure it is turned on. | |
| 155 CHECK(DPMSEnable(display)); | |
| 156 CHECK(DPMSForceLevel(display, DPMSModeOn)); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 // Called to set the frame buffer (underling XRR "screen") size. Has a | |
| 161 // side-effect of disabling all CRTCs. | |
| 162 static void CreateFrameBuffer(Display* display, | |
| 163 XRRScreenResources* screen, | |
| 164 Window window, | |
| 165 int width, | |
| 166 int height) { | |
| 167 // Note that setting the screen size fails if any CRTCs are currently | |
| 168 // pointing into it so disable them all. | |
| 169 for (int i = 0; i < screen->ncrtc; ++i) { | |
| 170 const int x = 0; | |
| 171 const int y = 0; | |
| 172 const RRMode kMode = None; | |
| 173 const RROutput kOutput = None; | |
| 174 | |
| 175 ConfigureCrtc(display, | |
| 176 screen, | |
| 177 screen->crtcs[i], | |
| 178 x, | |
| 179 y, | |
| 180 kMode, | |
| 181 kOutput); | |
| 182 } | |
| 183 int mm_width = width * kPixelsToMmScale; | |
| 184 int mm_height = height * kPixelsToMmScale; | |
| 185 XRRSetScreenSize(display, window, width, height, mm_width, mm_height); | |
| 186 } | |
| 187 | |
| 188 // A helper to get the current CRTC, Mode, and height for a given output. This | |
| 189 // is read from the XRandR configuration and not any of our caches. | |
| 190 static void GetOutputConfiguration(Display* display, | |
| 191 XRRScreenResources* screen, | |
| 192 RROutput output, | |
| 193 RRCrtc* crtc, | |
| 194 RRMode* mode, | |
| 195 int* height) { | |
| 196 XRROutputInfo* output_info = XRRGetOutputInfo(display, screen, output); | |
| 197 CHECK(output_info != NULL); | |
| 198 *crtc = output_info->crtc; | |
| 199 XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(display, screen, *crtc); | |
| 200 if (crtc_info != NULL) { | |
| 201 *mode = crtc_info->mode; | |
| 202 *height = crtc_info->height; | |
| 203 XRRFreeCrtcInfo(crtc_info); | |
| 204 } | |
| 205 XRRFreeOutputInfo(output_info); | |
| 206 } | |
| 207 | |
| 208 // A helper to determine the device_scale_factor given pixel width and mm_width. | |
| 209 // This currently only reports two scale factors (1.0 and 2.0) | |
| 210 static float ComputeDeviceScaleFactor(unsigned int width, | |
| 211 unsigned long mm_width) { | |
| 212 float device_scale_factor = 1.0f; | |
| 213 if (mm_width > 0 && (kMmInInch * width / mm_width) > kHighDensityDIPThreshold) | |
| 214 device_scale_factor = 2.0f; | |
| 215 return device_scale_factor; | |
| 216 } | |
| 217 | |
| 218 } // namespace | |
| 219 | |
| 220 bool OutputConfigurator::TryRecacheOutputs(Display* display, | |
| 221 XRRScreenResources* screen) { | |
| 222 bool outputs_did_change = false; | |
| 223 int previous_connected_count = 0; | |
| 224 int new_connected_count = 0; | |
| 225 | |
| 226 if (output_count_ != screen->noutput) { | |
| 227 outputs_did_change = true; | |
| 228 } else { | |
| 229 // The outputs might have changed so compare the connected states in the | |
| 230 // screen to our existing cache. | |
| 231 for (int i = 0; (i < output_count_) && !outputs_did_change; ++i) { | |
| 232 RROutput thisID = screen->outputs[i]; | |
| 233 XRROutputInfo* output = XRRGetOutputInfo(display, screen, thisID); | |
| 234 bool now_connected = (RR_Connected == output->connection); | |
| 235 outputs_did_change = (now_connected != output_cache_[i].is_connected); | |
| 236 XRRFreeOutputInfo(output); | |
| 237 | |
| 238 if (output_cache_[i].is_connected) | |
| 239 previous_connected_count += 1; | |
| 240 if (now_connected) | |
| 241 new_connected_count += 1; | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 if (outputs_did_change) { | |
| 246 // We now know that we need to recache so free and re-alloc the buffer. | |
| 247 output_count_ = screen->noutput; | |
| 248 if (output_count_ == 0) { | |
| 249 output_cache_.reset(NULL); | |
| 250 } else { | |
| 251 // Ideally, this would be allocated inline in the OutputConfigurator | |
| 252 // instance since we support at most 2 connected outputs but this dynamic | |
| 253 // allocation was specifically requested. | |
| 254 output_cache_.reset(new CachedOutputDescription[output_count_]); | |
| 255 } | |
| 256 | |
| 257 // TODO: This approach to finding CRTCs only supports two. Expand on this. | |
| 258 RRCrtc used_crtc = None; | |
| 259 primary_output_index_ = -1; | |
| 260 secondary_output_index_ = -1; | |
| 261 | |
| 262 for (int i = 0; i < output_count_; ++i) { | |
| 263 RROutput this_id = screen->outputs[i]; | |
| 264 XRROutputInfo* output = XRRGetOutputInfo(display, screen, this_id); | |
| 265 bool is_connected = (RR_Connected == output->connection); | |
| 266 RRCrtc crtc = None; | |
| 267 RRMode ideal_mode = None; | |
| 268 int x = 0; | |
| 269 int y = 0; | |
| 270 unsigned long mm_width = output->mm_width; | |
| 271 unsigned long mm_height = output->mm_height; | |
| 272 bool is_internal = false; | |
| 273 | |
| 274 if (is_connected) { | |
| 275 for (int j = 0; (j < output->ncrtc) && (None == crtc); ++j) { | |
| 276 RRCrtc possible = output->crtcs[j]; | |
| 277 if (possible != used_crtc) { | |
| 278 crtc = possible; | |
| 279 used_crtc = possible; | |
| 280 } | |
| 281 } | |
| 282 | |
| 283 const char* name = output->name; | |
| 284 is_internal = | |
| 285 (strncmp(kInternal_LVDS, | |
| 286 name, | |
| 287 arraysize(kInternal_LVDS) - 1) == 0) || | |
| 288 (strncmp(kInternal_eDP, | |
| 289 name, | |
| 290 arraysize(kInternal_eDP) - 1) == 0); | |
| 291 if (output->nmode > 0) | |
| 292 ideal_mode = output->modes[0]; | |
| 293 | |
| 294 if (crtc != None) { | |
| 295 XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screen, crtc); | |
| 296 x = crtcInfo->x; | |
| 297 y = crtcInfo->y; | |
| 298 XRRFreeCrtcInfo(crtcInfo); | |
| 299 } | |
| 300 | |
| 301 // Save this for later mirror mode detection. | |
| 302 if (primary_output_index_ == -1) | |
| 303 primary_output_index_ = i; | |
| 304 else if (secondary_output_index_ == -1) | |
| 305 secondary_output_index_ = i; | |
| 306 } | |
| 307 XRRFreeOutputInfo(output); | |
| 308 | |
| 309 // Now save the cached state for this output (we will default to mirror | |
| 310 // disabled and detect that after we have identified the first two | |
| 311 // connected outputs). | |
| 312 VLOG(1) << "Recache output index: " << i | |
| 313 << ", output id: " << this_id | |
| 314 << ", crtc id: " << crtc | |
| 315 << ", ideal mode id: " << ideal_mode | |
| 316 << ", x: " << x | |
| 317 << ", y: " << y | |
| 318 << ", is connected: " << is_connected | |
| 319 << ", is_internal: " << is_internal | |
| 320 << ", mm_width: " << mm_width | |
| 321 << ", mm_height: " << mm_height; | |
| 322 output_cache_[i].output = this_id; | |
| 323 output_cache_[i].crtc = crtc; | |
| 324 output_cache_[i].mirror_mode = None; | |
| 325 output_cache_[i].ideal_mode = ideal_mode; | |
| 326 output_cache_[i].x = x; | |
| 327 output_cache_[i].y = y; | |
| 328 output_cache_[i].is_connected = is_connected; | |
| 329 output_cache_[i].is_powered_on = true; | |
| 330 output_cache_[i].is_internal = is_internal; | |
| 331 output_cache_[i].mm_width = mm_width; | |
| 332 output_cache_[i].mm_height = mm_height; | |
| 333 } | |
| 334 | |
| 335 // Now, detect the mirror modes if we have two connected outputs. | |
| 336 if ((primary_output_index_ != -1) && (secondary_output_index_ != -1)) { | |
| 337 mirror_supported_ = FindMirrorModeForOutputs( | |
| 338 display, | |
| 339 screen, | |
| 340 output_cache_[primary_output_index_].output, | |
| 341 output_cache_[secondary_output_index_].output, | |
| 342 &output_cache_[primary_output_index_].mirror_mode, | |
| 343 &output_cache_[secondary_output_index_].mirror_mode); | |
| 344 | |
| 345 RRMode primary_mode = output_cache_[primary_output_index_].mirror_mode; | |
| 346 RRMode second_mode = output_cache_[secondary_output_index_].mirror_mode; | |
| 347 VLOG(1) << "Mirror mode supported " << mirror_supported_ | |
| 348 << " primary " << primary_mode | |
| 349 << " secondary " << second_mode; | |
| 350 } | |
| 351 } | |
| 352 return outputs_did_change; | |
| 353 } | |
| 354 | |
| 355 OutputConfigurator::OutputConfigurator() | |
| 356 : is_running_on_chrome_os_(base::chromeos::IsRunningOnChromeOS()), | |
| 357 output_count_(0), | |
| 358 output_cache_(NULL), | |
| 359 mirror_supported_(false), | |
| 360 primary_output_index_(-1), | |
| 361 secondary_output_index_(-1), | |
| 362 xrandr_event_base_(0), | |
| 363 output_state_(STATE_INVALID) { | |
| 364 if (is_running_on_chrome_os_) { | |
| 365 // Send the signal to powerd to tell it that we will take over output | |
| 366 // control. | |
| 367 // Note that this can be removed once the legacy powerd support is removed. | |
| 368 chromeos::DBusThreadManager* manager = chromeos::DBusThreadManager::Get(); | |
| 369 dbus::Bus* bus = manager->GetSystemBus(); | |
| 370 dbus::ExportedObject* remote_object = bus->GetExportedObject( | |
| 371 dbus::ObjectPath(power_manager::kPowerManagerServicePath)); | |
| 372 dbus::Signal signal(power_manager::kPowerManagerInterface, | |
| 373 power_manager::kUseNewMonitorConfigSignal); | |
| 374 CHECK(signal.raw_message() != NULL); | |
| 375 remote_object->SendSignal(&signal); | |
| 376 | |
| 377 // Cache the initial output state. | |
| 378 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
| 379 CHECK(display != NULL); | |
| 380 XGrabServer(display); | |
| 381 Window window = DefaultRootWindow(display); | |
| 382 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
| 383 CHECK(screen != NULL); | |
| 384 bool did_detect_outputs = TryRecacheOutputs(display, screen); | |
| 385 CHECK(did_detect_outputs); | |
| 386 State current_state = InferCurrentState(display, screen); | |
| 387 if (current_state == STATE_INVALID) { | |
| 388 // Unknown state. Transition into the default state. | |
| 389 State state = GetDefaultState(); | |
| 390 UpdateCacheAndXrandrToState(display, screen, window, state); | |
| 391 } else { | |
| 392 // This is a valid state so just save it to |output_state_|. | |
| 393 output_state_ = current_state; | |
| 394 } | |
| 395 // Find xrandr_event_base_ since we need it to interpret events, later. | |
| 396 int error_base_ignored = 0; | |
| 397 XRRQueryExtension(display, &xrandr_event_base_, &error_base_ignored); | |
| 398 // Relinquish X resources. | |
| 399 XRRFreeScreenResources(screen); | |
| 400 XUngrabServer(display); | |
| 401 CheckIsProjectingAndNotify(); | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 OutputConfigurator::~OutputConfigurator() { | |
| 406 } | |
| 407 | |
| 408 void OutputConfigurator::UpdateCacheAndXrandrToState( | |
| 409 Display* display, | |
| 410 XRRScreenResources* screen, | |
| 411 Window window, | |
| 412 State new_state) { | |
| 413 // Default rules: | |
| 414 // - single display = rebuild framebuffer and set to ideal_mode. | |
| 415 // - multi display = rebuild framebuffer and set to mirror_mode. | |
| 416 | |
| 417 // First, calculate the width and height of the framebuffer (we could retain | |
| 418 // the existing buffer, if it isn't resizing, but that causes an odd display | |
| 419 // state where the CRTCs are repositioned over the root windows before Chrome | |
| 420 // can move them). It is a feature worth considering, though, and wouldn't | |
| 421 // be difficult to implement (just check the current framebuffer size before | |
| 422 // changing it). | |
| 423 int width = 0; | |
| 424 int height = 0; | |
| 425 int primary_height = 0; | |
| 426 int secondary_height = 0; | |
| 427 int vertical_gap = 0; | |
| 428 if (new_state == STATE_SINGLE) { | |
| 429 CHECK_NE(-1, primary_output_index_); | |
| 430 | |
| 431 XRRModeInfo* ideal_mode = ModeInfoForID( | |
| 432 screen, | |
| 433 output_cache_[primary_output_index_].ideal_mode); | |
| 434 width = ideal_mode->width; | |
| 435 height = ideal_mode->height; | |
| 436 } else if (new_state == STATE_DUAL_MIRROR) { | |
| 437 CHECK_NE(-1, primary_output_index_); | |
| 438 CHECK_NE(-1, secondary_output_index_); | |
| 439 | |
| 440 XRRModeInfo* mirror_mode = ModeInfoForID( | |
| 441 screen, | |
| 442 output_cache_[primary_output_index_].mirror_mode); | |
| 443 width = mirror_mode->width; | |
| 444 height = mirror_mode->height; | |
| 445 } else if ((new_state == STATE_DUAL_PRIMARY_ONLY) || | |
| 446 (new_state == STATE_DUAL_SECONDARY_ONLY)) { | |
| 447 CHECK_NE(-1, primary_output_index_); | |
| 448 CHECK_NE(-1, secondary_output_index_); | |
| 449 | |
| 450 XRRModeInfo* one_ideal = ModeInfoForID( | |
| 451 screen, | |
| 452 output_cache_[primary_output_index_].ideal_mode); | |
| 453 XRRModeInfo* two_ideal = ModeInfoForID( | |
| 454 screen, | |
| 455 output_cache_[secondary_output_index_].ideal_mode); | |
| 456 | |
| 457 // Compute the device scale factor for the topmost display. We only need | |
| 458 // to take this device's scale factor into account as we are creating a gap | |
| 459 // to avoid the cursor drawing onto the second (unused) display when the | |
| 460 // cursor is near the bottom of the topmost display. | |
| 461 float top_scale_factor; | |
| 462 if (new_state == STATE_DUAL_PRIMARY_ONLY) { | |
| 463 top_scale_factor = ComputeDeviceScaleFactor(one_ideal->width, | |
| 464 output_cache_[primary_output_index_].mm_width); | |
| 465 } else { | |
| 466 top_scale_factor = ComputeDeviceScaleFactor(two_ideal->width, | |
| 467 output_cache_[secondary_output_index_].mm_width); | |
| 468 } | |
| 469 vertical_gap = kVerticalGap * top_scale_factor; | |
| 470 | |
| 471 width = std::max<int>(one_ideal->width, two_ideal->width); | |
| 472 height = one_ideal->height + two_ideal->height + vertical_gap; | |
| 473 primary_height = one_ideal->height; | |
| 474 secondary_height = two_ideal->height; | |
| 475 } | |
| 476 CreateFrameBuffer(display, screen, window, width, height); | |
| 477 | |
| 478 // Now, tile the outputs appropriately. | |
| 479 const int x = 0; | |
| 480 const int y = 0; | |
| 481 switch (new_state) { | |
| 482 case STATE_SINGLE: | |
| 483 ConfigureCrtc(display, | |
| 484 screen, | |
| 485 output_cache_[primary_output_index_].crtc, | |
| 486 x, | |
| 487 y, | |
| 488 output_cache_[primary_output_index_].ideal_mode, | |
| 489 output_cache_[primary_output_index_].output); | |
| 490 break; | |
| 491 case STATE_DUAL_MIRROR: | |
| 492 case STATE_DUAL_PRIMARY_ONLY: | |
| 493 case STATE_DUAL_SECONDARY_ONLY: { | |
| 494 RRMode primary_mode = output_cache_[primary_output_index_].mirror_mode; | |
| 495 RRMode secondary_mode = | |
| 496 output_cache_[secondary_output_index_].mirror_mode; | |
| 497 int primary_y = y; | |
| 498 int secondary_y = y; | |
| 499 | |
| 500 if (new_state != STATE_DUAL_MIRROR) { | |
| 501 primary_mode = output_cache_[primary_output_index_].ideal_mode; | |
| 502 secondary_mode = output_cache_[secondary_output_index_].ideal_mode; | |
| 503 } | |
| 504 if (new_state == STATE_DUAL_PRIMARY_ONLY) | |
| 505 secondary_y = y + primary_height + vertical_gap; | |
| 506 if (new_state == STATE_DUAL_SECONDARY_ONLY) | |
| 507 primary_y = y + secondary_height + vertical_gap; | |
| 508 | |
| 509 ConfigureCrtc(display, | |
| 510 screen, | |
| 511 output_cache_[primary_output_index_].crtc, | |
| 512 x, | |
| 513 primary_y, | |
| 514 primary_mode, | |
| 515 output_cache_[primary_output_index_].output); | |
| 516 ConfigureCrtc(display, | |
| 517 screen, | |
| 518 output_cache_[secondary_output_index_].crtc, | |
| 519 x, | |
| 520 secondary_y, | |
| 521 secondary_mode, | |
| 522 output_cache_[secondary_output_index_].output); | |
| 523 } | |
| 524 break; | |
| 525 case STATE_HEADLESS: | |
| 526 // Do nothing. | |
| 527 break; | |
| 528 default: | |
| 529 NOTREACHED() << "Unhandled state " << new_state; | |
| 530 } | |
| 531 output_state_ = new_state; | |
| 532 } | |
| 533 | |
| 534 bool OutputConfigurator::RecacheAndUseDefaultState() { | |
| 535 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
| 536 CHECK(display != NULL); | |
| 537 XGrabServer(display); | |
| 538 Window window = DefaultRootWindow(display); | |
| 539 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
| 540 CHECK(screen != NULL); | |
| 541 | |
| 542 bool did_detect_change = TryRecacheOutputs(display, screen); | |
| 543 if (did_detect_change) { | |
| 544 State state = GetDefaultState(); | |
| 545 UpdateCacheAndXrandrToState(display, screen, window, state); | |
| 546 } | |
| 547 XRRFreeScreenResources(screen); | |
| 548 XUngrabServer(display); | |
| 549 return did_detect_change; | |
| 550 } | |
| 551 | |
| 552 State OutputConfigurator::GetDefaultState() const { | |
| 553 State state = STATE_HEADLESS; | |
| 554 if (-1 != primary_output_index_) { | |
| 555 if (-1 != secondary_output_index_) | |
| 556 state = mirror_supported_ ? STATE_DUAL_MIRROR : STATE_DUAL_PRIMARY_ONLY; | |
| 557 else | |
| 558 state = STATE_SINGLE; | |
| 559 } | |
| 560 return state; | |
| 561 } | |
| 562 | |
| 563 State OutputConfigurator::InferCurrentState(Display* display, | |
| 564 XRRScreenResources* screen) const { | |
| 565 // STATE_INVALID will be our default or "unknown" state. | |
| 566 State state = STATE_INVALID; | |
| 567 // First step: count the number of connected outputs. | |
| 568 if (secondary_output_index_ == -1) { | |
| 569 // No secondary display. | |
| 570 if (primary_output_index_ == -1) { | |
| 571 // No primary display implies HEADLESS. | |
| 572 state = STATE_HEADLESS; | |
| 573 } else { | |
| 574 // The common case of primary-only. | |
| 575 // The only sanity check we require in this case is that the current mode | |
| 576 // of the output's CRTC is the ideal mode we determined for it. | |
| 577 RRCrtc primary_crtc = None; | |
| 578 RRMode primary_mode = None; | |
| 579 int primary_height = 0; | |
| 580 GetOutputConfiguration(display, | |
| 581 screen, | |
| 582 output_cache_[primary_output_index_].output, | |
| 583 &primary_crtc, | |
| 584 &primary_mode, | |
| 585 &primary_height); | |
| 586 if (primary_mode == output_cache_[primary_output_index_].ideal_mode) | |
| 587 state = STATE_SINGLE; | |
| 588 } | |
| 589 } else { | |
| 590 // We have two displays attached so we need to look at their configuration. | |
| 591 // Note that, for simplicity, we will only detect the states that we would | |
| 592 // have used and will assume anything unexpected is INVALID (which should | |
| 593 // not happen in any expected usage scenario). | |
| 594 RRCrtc primary_crtc = None; | |
| 595 RRMode primary_mode = None; | |
| 596 int primary_height = 0; | |
| 597 GetOutputConfiguration(display, | |
| 598 screen, | |
| 599 output_cache_[primary_output_index_].output, | |
| 600 &primary_crtc, | |
| 601 &primary_mode, | |
| 602 &primary_height); | |
| 603 RRCrtc secondary_crtc = None; | |
| 604 RRMode secondary_mode = None; | |
| 605 int secondary_height = 0; | |
| 606 GetOutputConfiguration(display, | |
| 607 screen, | |
| 608 output_cache_[secondary_output_index_].output, | |
| 609 &secondary_crtc, | |
| 610 &secondary_mode, | |
| 611 &secondary_height); | |
| 612 // Make sure the CRTCs are matched to the expected outputs. | |
| 613 if ((output_cache_[primary_output_index_].crtc == primary_crtc) && | |
| 614 (output_cache_[secondary_output_index_].crtc == secondary_crtc)) { | |
| 615 // Check the mode matching: either both mirror or both ideal. | |
| 616 if ((output_cache_[primary_output_index_].mirror_mode == primary_mode) && | |
| 617 (output_cache_[secondary_output_index_].mirror_mode == | |
| 618 secondary_mode)) { | |
| 619 // We are already in mirror mode. | |
| 620 state = STATE_DUAL_MIRROR; | |
| 621 } else if ((output_cache_[primary_output_index_].ideal_mode == | |
| 622 primary_mode) && | |
| 623 (output_cache_[secondary_output_index_].ideal_mode == | |
| 624 secondary_mode)) { | |
| 625 // Both outputs are in their "ideal" mode so check their Y-offsets to | |
| 626 // see which "ideal" configuration this is. | |
| 627 if (primary_height == output_cache_[secondary_output_index_].y) { | |
| 628 // Secondary is tiled first. | |
| 629 state = STATE_DUAL_SECONDARY_ONLY; | |
| 630 } else if (secondary_height == output_cache_[primary_output_index_].y) { | |
| 631 // Primary is tiled first. | |
| 632 state = STATE_DUAL_PRIMARY_ONLY; | |
| 633 } | |
| 634 } | |
| 635 } | |
| 636 } | |
| 637 | |
| 638 return state; | |
| 639 } | |
| 640 | |
| 641 bool OutputConfigurator::CycleDisplayMode() { | |
| 642 VLOG(1) << "CycleDisplayMode"; | |
| 643 bool did_change = false; | |
| 644 if (is_running_on_chrome_os_) { | |
| 645 // Rules: | |
| 646 // - if there are 0 or 1 displays, do nothing and return false. | |
| 647 // - use y-coord of CRTCs to determine if we are mirror, primary-first, or | |
| 648 // secondary-first. The cycle order is: | |
| 649 // mirror->primary->secondary->mirror. | |
| 650 State new_state = STATE_INVALID; | |
| 651 switch (output_state_) { | |
| 652 case STATE_DUAL_MIRROR: | |
| 653 new_state = STATE_DUAL_PRIMARY_ONLY; | |
| 654 break; | |
| 655 case STATE_DUAL_PRIMARY_ONLY: | |
| 656 new_state = STATE_DUAL_SECONDARY_ONLY; | |
| 657 break; | |
| 658 case STATE_DUAL_SECONDARY_ONLY: | |
| 659 new_state = mirror_supported_ ? | |
| 660 STATE_DUAL_MIRROR : | |
| 661 STATE_DUAL_PRIMARY_ONLY; | |
| 662 break; | |
| 663 default: | |
| 664 // Do nothing - we aren't in a mode which we can rotate. | |
| 665 break; | |
| 666 } | |
| 667 if (STATE_INVALID != new_state) | |
| 668 did_change = SetDisplayMode(new_state); | |
| 669 } | |
| 670 return did_change; | |
| 671 } | |
| 672 | |
| 673 bool OutputConfigurator::ScreenPowerSet(bool power_on, bool all_displays) { | |
| 674 VLOG(1) << "OutputConfigurator::SetScreensOn " << power_on | |
| 675 << " all displays " << all_displays; | |
| 676 bool success = false; | |
| 677 if (is_running_on_chrome_os_) { | |
| 678 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
| 679 CHECK(display != NULL); | |
| 680 XGrabServer(display); | |
| 681 Window window = DefaultRootWindow(display); | |
| 682 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
| 683 CHECK(screen != NULL); | |
| 684 | |
| 685 // Set the CRTCs based on whether we want to turn the power on or off and | |
| 686 // select the outputs to operate on by name or all_displays. | |
| 687 for (int i = 0; i < output_count_; ++i) { | |
| 688 if (all_displays || output_cache_[i].is_internal) { | |
| 689 const int x = output_cache_[i].x; | |
| 690 const int y = output_cache_[i].y; | |
| 691 RROutput output = output_cache_[i].output; | |
| 692 RRCrtc crtc = output_cache_[i].crtc; | |
| 693 RRMode mode = None; | |
| 694 if (power_on) { | |
| 695 mode = (STATE_DUAL_MIRROR == output_state_) ? | |
| 696 output_cache_[i].mirror_mode : | |
| 697 output_cache_[i].ideal_mode; | |
| 698 } | |
| 699 | |
| 700 VLOG(1) << "SET POWER crtc: " << crtc | |
| 701 << ", mode " << mode | |
| 702 << ", output " << output | |
| 703 << ", x " << x | |
| 704 << ", y " << y; | |
| 705 ConfigureCrtc(display, | |
| 706 screen, | |
| 707 crtc, | |
| 708 x, | |
| 709 y, | |
| 710 mode, | |
| 711 output); | |
| 712 output_cache_[i].is_powered_on = power_on; | |
| 713 success = true; | |
| 714 } | |
| 715 } | |
| 716 | |
| 717 // Force the DPMS on since the driver doesn't always detect that it should | |
| 718 // turn on. | |
| 719 if (power_on) { | |
| 720 CHECK(DPMSEnable(display)); | |
| 721 CHECK(DPMSForceLevel(display, DPMSModeOn)); | |
| 722 } | |
| 723 | |
| 724 XRRFreeScreenResources(screen); | |
| 725 XUngrabServer(display); | |
| 726 } | |
| 727 return success; | |
| 728 } | |
| 729 | |
| 730 bool OutputConfigurator::SetDisplayMode(State new_state) { | |
| 731 if (output_state_ == STATE_INVALID || | |
| 732 output_state_ == STATE_HEADLESS || | |
| 733 output_state_ == STATE_SINGLE) | |
| 734 return false; | |
| 735 | |
| 736 Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); | |
| 737 CHECK(display != NULL); | |
| 738 XGrabServer(display); | |
| 739 Window window = DefaultRootWindow(display); | |
| 740 XRRScreenResources* screen = XRRGetScreenResources(display, window); | |
| 741 CHECK(screen != NULL); | |
| 742 | |
| 743 UpdateCacheAndXrandrToState(display, | |
| 744 screen, | |
| 745 window, | |
| 746 new_state); | |
| 747 XRRFreeScreenResources(screen); | |
| 748 XUngrabServer(display); | |
| 749 return true; | |
| 750 } | |
| 751 | |
| 752 bool OutputConfigurator::Dispatch(const base::NativeEvent& event) { | |
| 753 // Ignore this event if the Xrandr extension isn't supported. | |
| 754 if (is_running_on_chrome_os_ && | |
| 755 (event->type - xrandr_event_base_ == RRNotify)) { | |
| 756 XEvent* xevent = static_cast<XEvent*>(event); | |
| 757 XRRNotifyEvent* notify_event = | |
| 758 reinterpret_cast<XRRNotifyEvent*>(xevent); | |
| 759 if (notify_event->subtype == RRNotify_OutputChange) { | |
| 760 XRROutputChangeNotifyEvent* output_change_event = | |
| 761 reinterpret_cast<XRROutputChangeNotifyEvent*>(xevent); | |
| 762 if ((output_change_event->connection == RR_Connected) || | |
| 763 (output_change_event->connection == RR_Disconnected)) { | |
| 764 RecacheAndUseDefaultState(); | |
| 765 CheckIsProjectingAndNotify(); | |
| 766 } | |
| 767 // Ignore the case of RR_UnkownConnection. | |
| 768 } | |
| 769 } | |
| 770 return true; | |
| 771 } | |
| 772 | |
| 773 void OutputConfigurator::CheckIsProjectingAndNotify() { | |
| 774 // Determine if there is an "internal" output and how many outputs are | |
| 775 // connected. | |
| 776 bool has_internal_output = false; | |
| 777 int connected_output_count = 0; | |
| 778 for (int i = 0; i < output_count_; ++i) { | |
| 779 if (output_cache_[i].is_connected) { | |
| 780 connected_output_count += 1; | |
| 781 has_internal_output |= output_cache_[i].is_internal; | |
| 782 } | |
| 783 } | |
| 784 | |
| 785 // "Projecting" is defined as having more than 1 output connected while at | |
| 786 // least one of them is an internal output. | |
| 787 bool is_projecting = has_internal_output && (connected_output_count > 1); | |
| 788 chromeos::DBusThreadManager* manager = chromeos::DBusThreadManager::Get(); | |
| 789 dbus::Bus* bus = manager->GetSystemBus(); | |
| 790 dbus::ObjectProxy* power_manager_proxy = bus->GetObjectProxy( | |
| 791 power_manager::kPowerManagerServiceName, | |
| 792 dbus::ObjectPath(power_manager::kPowerManagerServicePath)); | |
| 793 dbus::MethodCall method_call( | |
| 794 power_manager::kPowerManagerInterface, | |
| 795 power_manager::kSetIsProjectingMethod); | |
| 796 dbus::MessageWriter writer(&method_call); | |
| 797 writer.AppendBool(is_projecting); | |
| 798 power_manager_proxy->CallMethod( | |
| 799 &method_call, | |
| 800 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | |
| 801 dbus::ObjectProxy::EmptyResponseCallback()); | |
| 802 } | |
| 803 | |
| 804 } // namespace chromeos | |
| OLD | NEW |