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