1 // Copyright (c) 2013 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/display/real_output_configurator_delegate.h" 6 7 #include <X11/Xatom.h> 8 #include <X11/Xlib.h> 9 #include <X11/extensions/dpms.h> 10 #include <X11/extensions/XInput.h> 11 #include <X11/extensions/XInput2.h> 12 #include <X11/extensions/Xrandr.h> 13 14 #include <cmath> 15 #include <set> 16 #include <utility> 17 18 #include "base/logging.h" 19 #include "base/message_loop/message_pump_x11.h" 20 #include "base/x11/edid_parser_x11.h" 21 #include "base/x11/x11_error_tracker.h" 22 #include "chromeos/dbus/dbus_thread_manager.h" 23 #include "chromeos/dbus/power_manager_client.h" 24 #include "chromeos/display/output_util.h" 25 26 namespace chromeos { 27 28 namespace { 29 30 // DPI measurements. 31 const float kMmInInch = 25.4; 32 const float kDpi96 = 96.0; 33 const float kPixelsToMmScale = kMmInInch / kDpi96; 34 35 // Prefixes of output name 36 const char kOutputName_VGA[] = "VGA"; 37 const char kOutputName_HDMI[] = "HDMI"; 38 const char kOutputName_DVI[] = "DVI"; 39 const char kOutputName_DisplayPort[] = "DP"; 40 41 const char kContentProtectionAtomName[] = "Content Protection"; 42 const char kProtectionUndesiredAtomName[] = "Undesired"; 43 const char kProtectionDesiredAtomName[] = "Desired"; 44 const char kProtectionEnabledAtomName[] = "Enabled"; 45 46 bool IsInternalOutput(const XRROutputInfo* output_info) { 47 return IsInternalOutputName(std::string(output_info->name)); 48 } 49 50 RRMode GetOutputNativeMode(const XRROutputInfo* output_info) { 51 return output_info->nmode > 0 ? output_info->modes[0] : None; 52 } 53 54 } // namespace 55 56 RealOutputConfiguratorDelegate::RealOutputConfiguratorDelegate() 57 : display_(base::MessagePumpX11::GetDefaultXDisplay()), 58 window_(DefaultRootWindow(display_)), 59 screen_(NULL) { 60 } 61 62 RealOutputConfiguratorDelegate::~RealOutputConfiguratorDelegate() { 63 } 64 65 void RealOutputConfiguratorDelegate::InitXRandRExtension(int* event_base) { 66 int error_base_ignored = 0; 67 XRRQueryExtension(display_, event_base, &error_base_ignored); 68 } 69 70 void RealOutputConfiguratorDelegate::UpdateXRandRConfiguration( 71 const base::NativeEvent& event) { 72 XRRUpdateConfiguration(event); 73 } 74 75 void RealOutputConfiguratorDelegate::GrabServer() { 76 CHECK(!screen_) << "Server already grabbed"; 77 XGrabServer(display_); 78 screen_ = XRRGetScreenResources(display_, window_); 79 CHECK(screen_); 80 } 81 82 void RealOutputConfiguratorDelegate::UngrabServer() { 83 CHECK(screen_) << "Server not grabbed"; 84 XRRFreeScreenResources(screen_); 85 screen_ = NULL; 86 XUngrabServer(display_); 87 } 88 89 void RealOutputConfiguratorDelegate::SyncWithServer() { 90 XSync(display_, 0); 91 } 92 93 void RealOutputConfiguratorDelegate::SetBackgroundColor(uint32 color_argb) { 94 // Configuring CRTCs/Framebuffer clears the boot screen image. Set the 95 // same background color while configuring the display to minimize the 96 // duration of black screen at boot time. The background is filled with 97 // black later in ash::DisplayManager. crbug.com/171050. 98 XSetWindowAttributes swa = {0}; 99 XColor color; 100 Colormap colormap = DefaultColormap(display_, 0); 101 // XColor uses 16 bits per color. 102 color.red = (color_argb & 0x00FF0000) >> 8; 103 color.green = (color_argb & 0x0000FF00); 104 color.blue = (color_argb & 0x000000FF) << 8; 105 color.flags = DoRed | DoGreen | DoBlue; 106 XAllocColor(display_, colormap, &color); 107 swa.background_pixel = color.pixel; 108 XChangeWindowAttributes(display_, window_, CWBackPixel, &swa); 109 XFreeColors(display_, colormap, &color.pixel, 1, 0); 110 } 111 112 void RealOutputConfiguratorDelegate::ForceDPMSOn() { 113 CHECK(DPMSEnable(display_)); 114 CHECK(DPMSForceLevel(display_, DPMSModeOn)); 115 } 116 117 std::vector<OutputConfigurator::OutputSnapshot> 118 RealOutputConfiguratorDelegate::GetOutputs() { 119 CHECK(screen_) << "Server not grabbed"; 120 121 std::vector<OutputConfigurator::OutputSnapshot> outputs; 122 RRCrtc last_used_crtc = None; 123 124 for (int i = 0; i < screen_->noutput && outputs.size() < 2; ++i) { 125 RROutput output_id = screen_->outputs[i]; 126 XRROutputInfo* output_info = XRRGetOutputInfo(display_, screen_, output_id); 127 if (output_info->connection == RR_Connected) { 128 OutputConfigurator::OutputSnapshot output = InitOutputSnapshot( 129 output_id, output_info, &last_used_crtc, i); 130 VLOG(2) << "Found display " << outputs.size() << ":" 131 << " output=" << output.output 132 << " crtc=" << output.crtc 133 << " current_mode=" << output.current_mode; 134 outputs.push_back(output); 135 } 136 XRRFreeOutputInfo(output_info); 137 } 138 139 GetTouchscreens(&outputs); 140 return outputs; 141 } 142 143 void RealOutputConfiguratorDelegate::AddOutputMode(RROutput output, 144 RRMode mode) { 145 CHECK(screen_) << "Server not grabbed"; 146 VLOG(1) << "AddOutputMode: output=" << output << " mode=" << mode; 147 XRRAddOutputMode(display_, output, mode); 148 } 149 150 bool RealOutputConfiguratorDelegate::ConfigureCrtc( 151 RRCrtc crtc, 152 RRMode mode, 153 RROutput output, 154 int x, 155 int y) { 156 CHECK(screen_) << "Server not grabbed"; 157 VLOG(1) << "ConfigureCrtc: crtc=" << crtc 158 << " mode=" << mode 159 << " output=" << output 160 << " x=" << x 161 << " y=" << y; 162 // Xrandr.h is full of lies. XRRSetCrtcConfig() is defined as returning a 163 // Status, which is typically 0 for failure and 1 for success. In 164 // actuality it returns a RRCONFIGSTATUS, which uses 0 for success. 165 return XRRSetCrtcConfig(display_, 166 screen_, 167 crtc, 168 CurrentTime, 169 x, 170 y, 171 mode, 172 RR_Rotate_0, 173 (output && mode) ? &output : NULL, 174 (output && mode) ? 1 : 0) == RRSetConfigSuccess; 175 } 176 177 void RealOutputConfiguratorDelegate::CreateFrameBuffer( 178 int width, 179 int height, 180 const std::vector<OutputConfigurator::OutputSnapshot>& outputs) { 181 CHECK(screen_) << "Server not grabbed"; 182 int current_width = DisplayWidth(display_, DefaultScreen(display_)); 183 int current_height = DisplayHeight(display_, DefaultScreen(display_)); 184 VLOG(1) << "CreateFrameBuffer: new=" << width << "x" << height 185 << " current=" << current_width << "x" << current_height; 186 if (width == current_width && height == current_height) 187 return; 188 189 DestroyUnusedCrtcs(outputs); 190 int mm_width = width * kPixelsToMmScale; 191 int mm_height = height * kPixelsToMmScale; 192 XRRSetScreenSize(display_, window_, width, height, mm_width, mm_height); 193 } 194 195 void RealOutputConfiguratorDelegate::ConfigureCTM( 196 int touch_device_id, 197 const OutputConfigurator::CoordinateTransformation& ctm) { 198 VLOG(1) << "ConfigureCTM: id=" << touch_device_id 199 << " scale=" << ctm.x_scale << "x" << ctm.y_scale 200 << " offset=(" << ctm.x_offset << ", " << ctm.y_offset << ")"; 201 int ndevices = 0; 202 XIDeviceInfo* info = XIQueryDevice(display_, touch_device_id, &ndevices); 203 Atom prop = XInternAtom(display_, "Coordinate Transformation Matrix", False); 204 Atom float_atom = XInternAtom(display_, "FLOAT", False); 205 if (ndevices == 1 && prop != None && float_atom != None) { 206 Atom type; 207 int format; 208 unsigned long num_items; 209 unsigned long bytes_after; 210 unsigned char* data = NULL; 211 // Verify that the property exists with correct format, type, etc. 212 int status = XIGetProperty(display_, info->deviceid, prop, 0, 0, False, 213 AnyPropertyType, &type, &format, &num_items, &bytes_after, &data); 214 if (data) 215 XFree(data); 216 if (status == Success && type == float_atom && format == 32) { 217 float value[3][3] = { 218 { ctm.x_scale, 0.0, ctm.x_offset }, 219 { 0.0, ctm.y_scale, ctm.y_offset }, 220 { 0.0, 0.0, 1.0 } 221 }; 222 XIChangeProperty(display_, 223 info->deviceid, 224 prop, 225 type, 226 format, 227 PropModeReplace, 228 reinterpret_cast<unsigned char*>(value), 229 9); 230 } 231 } 232 XIFreeDeviceInfo(info); 233 } 234 235 void RealOutputConfiguratorDelegate::SendProjectingStateToPowerManager( 236 bool projecting) { 237 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 238 SetIsProjecting(projecting); 239 } 240 241 bool RealOutputConfiguratorDelegate::InitModeInfo( 242 RRMode mode, 243 OutputConfigurator::ModeInfo* mode_info) { 244 DCHECK(mode_info); 245 CHECK(screen_) << "Server not grabbed"; 246 // TODO: Determine if we need to organize modes in a way which provides 247 // better than O(n) lookup time. In many call sites, for example, the 248 // "next" mode is typically what we are looking for so using this 249 // helper might be too expensive. 250 for (int i = 0; i < screen_->nmode; ++i) { 251 if (mode == screen_->modes[i].id) { 252 const XRRModeInfo& info = screen_->modes[i]; 253 mode_info->width = info.width; 254 mode_info->height = info.height; 255 mode_info->interlaced = info.modeFlags & RR_Interlace; 256 if (info.hTotal && info.vTotal) { 257 mode_info->refresh_rate = static_cast<float>(info.dotClock) / 258 (static_cast<float>(info.hTotal) * 259 static_cast<float>(info.vTotal)); 260 } else { 261 mode_info->refresh_rate = 0.0f; 262 } 263 return true; 264 } 265 } 266 return false; 267 } 268 269 OutputConfigurator::OutputSnapshot 270 RealOutputConfiguratorDelegate::InitOutputSnapshot( 271 RROutput id, 272 XRROutputInfo* info, 273 RRCrtc* last_used_crtc, 274 int index) { 275 OutputConfigurator::OutputSnapshot output; 276 output.output = id; 277 output.width_mm = info->mm_width; 278 output.height_mm = info->mm_height; 279 output.has_display_id = base::GetDisplayId(id, index, &output.display_id); 280 output.is_internal = IsInternalOutput(info); 281 output.index = index; 282 283 // Use the index as a valid display ID even if the internal 284 // display doesn't have valid EDID because the index 285 // will never change. 286 if (!output.has_display_id && output.is_internal) 287 output.has_display_id = true; 288 289 if (info->crtc) { 290 XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(display_, screen_, info->crtc); 291 output.current_mode = crtc_info->mode; 292 output.x = crtc_info->x; 293 output.y = crtc_info->y; 294 XRRFreeCrtcInfo(crtc_info); 295 } 296 297 // Assign a CRTC that isn't already in use. 298 for (int i = 0; i < info->ncrtc; ++i) { 299 if (info->crtcs[i] != *last_used_crtc) { 300 output.crtc = info->crtcs[i]; 301 *last_used_crtc = output.crtc; 302 break; 303 } 304 } 305 306 output.native_mode = GetOutputNativeMode(info); 307 output.is_aspect_preserving_scaling = IsOutputAspectPreservingScaling(id); 308 output.touch_device_id = None; 309 310 for (int i = 0; i < info->nmode; ++i) { 311 const RRMode mode = info->modes[i]; 312 OutputConfigurator::ModeInfo mode_info; 313 if (InitModeInfo(mode, &mode_info)) 314 output.mode_infos.insert(std::make_pair(mode, mode_info)); 315 else 316 LOG(WARNING) << "Unable to find XRRModeInfo for mode " << mode; 317 } 318 319 std::string name(info->name); 320 if (output.is_internal) { 321 output.type = OUTPUT_TYPE_INTERNAL; 322 } else if (name.find(kOutputName_VGA) == 0) { 323 output.type = OUTPUT_TYPE_VGA; 324 } else if (name.find(kOutputName_HDMI) == 0) { 325 output.type = OUTPUT_TYPE_HDMI; 326 } else if (name.find(kOutputName_DVI) == 0) { 327 output.type = OUTPUT_TYPE_DVI; 328 } else if (name.find(kOutputName_DisplayPort) == 0) { 329 output.type = OUTPUT_TYPE_DISPLAYPORT; 330 } else { 331 LOG(ERROR) << "Unknown link type: " << name; 332 output.type = OUTPUT_TYPE_UNKNOWN; 333 } 334 335 return output; 336 } 337 338 bool RealOutputConfiguratorDelegate::GetHDCPState(RROutput id, 339 HDCPState* state) { 340 unsigned char* values = NULL; 341 int actual_format = 0; 342 unsigned long nitems = 0; 343 unsigned long bytes_after = 0; 344 Atom actual_type = None; 345 int success = 0; 346 // TODO(kcwu): Use X11AtomCache to save round trip time of XInternAtom. 347 Atom prop = XInternAtom(display_, kContentProtectionAtomName, False); 348 349 bool ok = true; 350 // TODO(kcwu): Move this to x11_util (similar method calls in this file and 351 // output_util.cc) 352 success = XRRGetOutputProperty(display_, id, prop, 0, 100, False, 353 False, AnyPropertyType, &actual_type, 354 &actual_format, &nitems, &bytes_after, 355 &values); 356 if (actual_type == None) { 357 LOG(ERROR) << "Property '" << kContentProtectionAtomName 358 << "' does not exist"; 359 ok = false; 360 } else if (success == Success && actual_type == XA_ATOM && 361 actual_format == 32 && nitems == 1) { 362 Atom value = reinterpret_cast<Atom*>(values)[0]; 363 if (value == XInternAtom(display_, kProtectionUndesiredAtomName, False)) { 364 *state = HDCP_STATE_UNDESIRED; 365 } else if (value == XInternAtom(display_, kProtectionDesiredAtomName, 366 False)) { 367 *state = HDCP_STATE_DESIRED; 368 } else if (value == XInternAtom(display_, kProtectionEnabledAtomName, 369 False)) { 370 *state = HDCP_STATE_ENABLED; 371 } else { 372 LOG(ERROR) << "Unknown " << kContentProtectionAtomName << " value: " 373 << value; 374 ok = false; 375 } 376 } else { 377 LOG(ERROR) << "XRRGetOutputProperty failed"; 378 ok = false; 379 } 380 if (values) 381 XFree(values); 382 383 VLOG(3) << "HDCP state: " << ok << "," << *state; 384 return ok; 385 } 386 387 bool RealOutputConfiguratorDelegate::SetHDCPState(RROutput id, 388 HDCPState state) { 389 Atom name = XInternAtom(display_, kContentProtectionAtomName, False); 390 Atom value = None; 391 switch (state) { 392 case HDCP_STATE_UNDESIRED: 393 value = XInternAtom(display_, kProtectionUndesiredAtomName, False); 394 break; 395 case HDCP_STATE_DESIRED: 396 value = XInternAtom(display_, kProtectionDesiredAtomName, False); 397 break; 398 default: 399 NOTREACHED() << "Invalid HDCP state: " << state; 400 return false; 401 } 402 base::X11ErrorTracker err_tracker; 403 unsigned char* data = reinterpret_cast<unsigned char*>(&value); 404 XRRChangeOutputProperty(display_, id, name, XA_ATOM, 32, 405 PropModeReplace, data, 1); 406 if (err_tracker.FoundNewError()) { 407 LOG(ERROR) << "XRRChangeOutputProperty failed"; 408 return false; 409 } else { 410 return true; 411 } 412 } 413 414 void RealOutputConfiguratorDelegate::DestroyUnusedCrtcs( 415 const std::vector<OutputConfigurator::OutputSnapshot>& outputs) { 416 CHECK(screen_) << "Server not grabbed"; 417 // Setting the screen size will fail if any CRTC doesn't fit afterwards. 418 // At the same time, turning CRTCs off and back on uses up a lot of time. 419 // This function tries to be smart to avoid too many off/on cycles: 420 // - We disable all the CRTCs we won't need after the FB resize. 421 // - We set the new modes on CRTCs, if they fit in both the old and new 422 // FBs, and park them at (0,0) 423 // - We disable the CRTCs we will need but don't fit in the old FB. Those 424 // will be reenabled after the resize. 425 // We don't worry about the cached state of the outputs here since we are 426 // not interested in the state we are setting - we just try to get the CRTCs 427 // out of the way so we can rebuild the frame buffer. 428 for (int i = 0; i < screen_->ncrtc; ++i) { 429 // Default config is to disable the crtcs. 430 RRCrtc crtc = screen_->crtcs[i]; 431 RRMode mode = None; 432 RROutput output = None; 433 const OutputConfigurator::ModeInfo* mode_info = NULL; 434 for (std::vector<OutputConfigurator::OutputSnapshot>::const_iterator it = 435 outputs.begin(); it != outputs.end(); ++it) { 436 if (crtc == it->crtc) { 437 mode = it->current_mode; 438 output = it->output; 439 if (mode != None) 440 mode_info = OutputConfigurator::GetModeInfo(*it, mode); 441 break; 442 } 443 } 444 445 if (mode_info) { 446 // In case our CRTC doesn't fit in our current framebuffer, disable it. 447 // It'll get reenabled after we resize the framebuffer. 448 int current_width = DisplayWidth(display_, DefaultScreen(display_)); 449 int current_height = DisplayHeight(display_, DefaultScreen(display_)); 450 if (mode_info->width > current_width || 451 mode_info->height > current_height) { 452 mode = None; 453 output = None; 454 mode_info = NULL; 455 } 456 } 457 458 ConfigureCrtc(crtc, mode, output, 0, 0); 459 } 460 } 461 462 bool RealOutputConfiguratorDelegate::IsOutputAspectPreservingScaling( 463 RROutput id) { 464 bool ret = false; 465 466 Atom scaling_prop = XInternAtom(display_, "scaling mode", False); 467 Atom full_aspect_atom = XInternAtom(display_, "Full aspect", False); 468 if (scaling_prop == None || full_aspect_atom == None) 469 return false; 470 471 int nprop = 0; 472 Atom* props = XRRListOutputProperties(display_, id, &nprop); 473 for (int j = 0; j < nprop && !ret; j++) { 474 Atom prop = props[j]; 475 if (scaling_prop == prop) { 476 unsigned char* values = NULL; 477 int actual_format; 478 unsigned long nitems; 479 unsigned long bytes_after; 480 Atom actual_type; 481 int success; 482 483 success = XRRGetOutputProperty(display_, id, prop, 0, 100, False, False, 484 AnyPropertyType, &actual_type, &actual_format, &nitems, 485 &bytes_after, &values); 486 if (success == Success && actual_type == XA_ATOM && 487 actual_format == 32 && nitems == 1) { 488 Atom value = reinterpret_cast<Atom*>(values)[0]; 489 if (full_aspect_atom == value) 490 ret = true; 491 } 492 if (values) 493 XFree(values); 494 } 495 } 496 if (props) 497 XFree(props); 498 499 return ret; 500 } 501 502 void RealOutputConfiguratorDelegate::GetTouchscreens( 503 std::vector<OutputConfigurator::OutputSnapshot>* outputs) { 504 int ndevices = 0; 505 Atom valuator_x = XInternAtom(display_, "Abs MT Position X", False); 506 Atom valuator_y = XInternAtom(display_, "Abs MT Position Y", False); 507 if (valuator_x == None || valuator_y == None) 508 return; 509 510 std::set<int> no_match_touchscreen; 511 XIDeviceInfo* info = XIQueryDevice(display_, XIAllDevices, &ndevices); 512 for (int i = 0; i < ndevices; i++) { 513 if (!info[i].enabled || info[i].use != XIFloatingSlave) 514 continue; // Assume all touchscreens are floating slaves 515 516 double width = -1.0; 517 double height = -1.0; 518 bool is_direct_touch = false; 519 520 for (int j = 0; j < info[i].num_classes; j++) { 521 XIAnyClassInfo* class_info = info[i].classes[j]; 522 523 if (class_info->type == XIValuatorClass) { 524 XIValuatorClassInfo* valuator_info = 525 reinterpret_cast<XIValuatorClassInfo*>(class_info); 526 527 if (valuator_x == valuator_info->label) { 528 // Ignore X axis valuator with unexpected properties 529 if (valuator_info->number == 0 && valuator_info->mode == Absolute && 530 valuator_info->min == 0.0) { 531 width = valuator_info->max; 532 } 533 } else if (valuator_y == valuator_info->label) { 534 // Ignore Y axis valuator with unexpected properties 535 if (valuator_info->number == 1 && valuator_info->mode == Absolute && 536 valuator_info->min == 0.0) { 537 height = valuator_info->max; 538 } 539 } 540 } 541 #if defined(USE_XI2_MT) 542 if (class_info->type == XITouchClass) { 543 XITouchClassInfo* touch_info = 544 reinterpret_cast<XITouchClassInfo*>(class_info); 545 is_direct_touch = touch_info->mode == XIDirectTouch; 546 } 547 #endif 548 } 549 550 // Touchscreens should have absolute X and Y axes, 551 // and be direct touch devices. 552 if (width > 0.0 && height > 0.0 && is_direct_touch) { 553 size_t k = 0; 554 for (; k < outputs->size(); k++) { 555 OutputConfigurator::OutputSnapshot* output = &(*outputs)[k]; 556 if (output->native_mode == None || output->touch_device_id != None) 557 continue; 558 559 const OutputConfigurator::ModeInfo* mode_info = 560 OutputConfigurator::GetModeInfo(*output, output->native_mode); 561 if (!mode_info) 562 continue; 563 564 // Allow 1 pixel difference between screen and touchscreen 565 // resolutions. Because in some cases for monitor resolution 566 // 1024x768 touchscreen's resolution would be 1024x768, but for 567 // some 1023x767. It really depends on touchscreen's firmware 568 // configuration. 569 if (std::abs(mode_info->width - width) <= 1.0 && 570 std::abs(mode_info->height - height) <= 1.0) { 571 output->touch_device_id = info[i].deviceid; 572 573 VLOG(2) << "Found touchscreen for output #" << k 574 << " id " << output->touch_device_id 575 << " width " << width 576 << " height " << height; 577 break; 578 } 579 } 580 581 if (k == outputs->size()) { 582 no_match_touchscreen.insert(info[i].deviceid); 583 VLOG(2) << "No matching output for touchscreen" 584 << " id " << info[i].deviceid 585 << " width " << width 586 << " height " << height; 587 } 588 589 } 590 } 591 592 // Sometimes we can't find a matching screen for the touchscreen, e.g. 593 // due to the touchscreen's reporting range having no correlation with the 594 // screen's resolution. In this case, we arbitrarily assign unmatched 595 // touchscreens to unmatched screens. 596 for (std::set<int>::iterator it = no_match_touchscreen.begin(); 597 it != no_match_touchscreen.end(); 598 it++) { 599 for (size_t i = 0; i < outputs->size(); i++) { 600 if ((*outputs)[i].is_internal == false && 601 (*outputs)[i].native_mode != None && 602 (*outputs)[i].touch_device_id == None ) { 603 (*outputs)[i].touch_device_id = *it; 604 VLOG(2) << "Arbitrarily matching touchscreen " 605 << (*outputs)[i].touch_device_id << " to output #" << i; 606 break; 607 } 608 } 609 } 610 611 XIFreeDeviceInfo(info); 612 } 613 614 } // namespace chromeos 615