| 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/display/output_configurator.h" | |
| 6 | |
| 7 #include <X11/Xlib.h> | |
| 8 #include <X11/extensions/Xrandr.h> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/logging.h" | |
| 12 #include "base/strings/string_number_conversions.h" | |
| 13 #include "base/strings/stringprintf.h" | |
| 14 #include "base/sys_info.h" | |
| 15 #include "base/time/time.h" | |
| 16 #include "chromeos/display/native_display_delegate_x11.h" | |
| 17 #include "chromeos/display/output_util.h" | |
| 18 #include "chromeos/display/touchscreen_delegate_x11.h" | |
| 19 | |
| 20 namespace chromeos { | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 // The delay to perform configuration after RRNotify. See the comment | |
| 25 // in |Dispatch()|. | |
| 26 const int64 kConfigureDelayMs = 500; | |
| 27 | |
| 28 // Returns a string describing |state|. | |
| 29 std::string DisplayPowerStateToString(DisplayPowerState state) { | |
| 30 switch (state) { | |
| 31 case DISPLAY_POWER_ALL_ON: | |
| 32 return "ALL_ON"; | |
| 33 case DISPLAY_POWER_ALL_OFF: | |
| 34 return "ALL_OFF"; | |
| 35 case DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON: | |
| 36 return "INTERNAL_OFF_EXTERNAL_ON"; | |
| 37 case DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF: | |
| 38 return "INTERNAL_ON_EXTERNAL_OFF"; | |
| 39 default: | |
| 40 return "unknown (" + base::IntToString(state) + ")"; | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 // Returns a string describing |state|. | |
| 45 std::string OutputStateToString(ui::OutputState state) { | |
| 46 switch (state) { | |
| 47 case ui::OUTPUT_STATE_INVALID: | |
| 48 return "INVALID"; | |
| 49 case ui::OUTPUT_STATE_HEADLESS: | |
| 50 return "HEADLESS"; | |
| 51 case ui::OUTPUT_STATE_SINGLE: | |
| 52 return "SINGLE"; | |
| 53 case ui::OUTPUT_STATE_DUAL_MIRROR: | |
| 54 return "DUAL_MIRROR"; | |
| 55 case ui::OUTPUT_STATE_DUAL_EXTENDED: | |
| 56 return "DUAL_EXTENDED"; | |
| 57 } | |
| 58 NOTREACHED() << "Unknown state " << state; | |
| 59 return "INVALID"; | |
| 60 } | |
| 61 | |
| 62 // Returns a string representation of OutputSnapshot. | |
| 63 std::string OutputSnapshotToString( | |
| 64 const OutputConfigurator::OutputSnapshot* output) { | |
| 65 return base::StringPrintf( | |
| 66 "[type=%d, output=%ld, crtc=%ld, mode=%ld, dim=%dx%d]", | |
| 67 output->type, | |
| 68 output->output, | |
| 69 output->crtc, | |
| 70 output->current_mode, | |
| 71 static_cast<int>(output->width_mm), | |
| 72 static_cast<int>(output->height_mm)); | |
| 73 } | |
| 74 | |
| 75 // Returns a string representation of ModeInfo. | |
| 76 std::string ModeInfoToString(const OutputConfigurator::ModeInfo* mode) { | |
| 77 return base::StringPrintf("[%dx%d %srate=%f]", | |
| 78 mode->width, | |
| 79 mode->height, | |
| 80 mode->interlaced ? "interlaced " : "", | |
| 81 mode->refresh_rate); | |
| 82 | |
| 83 } | |
| 84 | |
| 85 // Returns the number of outputs in |outputs| that should be turned on, per | |
| 86 // |state|. If |output_power| is non-NULL, it is updated to contain the | |
| 87 // on/off state of each corresponding entry in |outputs|. | |
| 88 int GetOutputPower( | |
| 89 const std::vector<OutputConfigurator::OutputSnapshot>& outputs, | |
| 90 DisplayPowerState state, | |
| 91 std::vector<bool>* output_power) { | |
| 92 int num_on_outputs = 0; | |
| 93 if (output_power) | |
| 94 output_power->resize(outputs.size()); | |
| 95 | |
| 96 for (size_t i = 0; i < outputs.size(); ++i) { | |
| 97 bool internal = outputs[i].type == ui::OUTPUT_TYPE_INTERNAL; | |
| 98 bool on = state == DISPLAY_POWER_ALL_ON || | |
| 99 (state == DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && !internal) || | |
| 100 (state == DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal); | |
| 101 if (output_power) | |
| 102 (*output_power)[i] = on; | |
| 103 if (on) | |
| 104 num_on_outputs++; | |
| 105 } | |
| 106 return num_on_outputs; | |
| 107 } | |
| 108 | |
| 109 } // namespace | |
| 110 | |
| 111 OutputConfigurator::ModeInfo::ModeInfo() | |
| 112 : width(0), | |
| 113 height(0), | |
| 114 interlaced(false), | |
| 115 refresh_rate(0.0) {} | |
| 116 | |
| 117 OutputConfigurator::ModeInfo::ModeInfo(int width, | |
| 118 int height, | |
| 119 bool interlaced, | |
| 120 float refresh_rate) | |
| 121 : width(width), | |
| 122 height(height), | |
| 123 interlaced(interlaced), | |
| 124 refresh_rate(refresh_rate) {} | |
| 125 | |
| 126 OutputConfigurator::CoordinateTransformation::CoordinateTransformation() | |
| 127 : x_scale(1.0), | |
| 128 x_offset(0.0), | |
| 129 y_scale(1.0), | |
| 130 y_offset(0.0) {} | |
| 131 | |
| 132 OutputConfigurator::OutputSnapshot::OutputSnapshot() | |
| 133 : output(None), | |
| 134 crtc(None), | |
| 135 current_mode(None), | |
| 136 native_mode(None), | |
| 137 mirror_mode(None), | |
| 138 selected_mode(None), | |
| 139 x(0), | |
| 140 y(0), | |
| 141 width_mm(0), | |
| 142 height_mm(0), | |
| 143 is_aspect_preserving_scaling(false), | |
| 144 type(ui::OUTPUT_TYPE_UNKNOWN), | |
| 145 touch_device_id(0), | |
| 146 display_id(0), | |
| 147 has_display_id(false), | |
| 148 index(0) {} | |
| 149 | |
| 150 OutputConfigurator::OutputSnapshot::~OutputSnapshot() {} | |
| 151 | |
| 152 bool OutputConfigurator::TestApi::TriggerConfigureTimeout() { | |
| 153 if (configurator_->configure_timer_.get() && | |
| 154 configurator_->configure_timer_->IsRunning()) { | |
| 155 configurator_->configure_timer_.reset(); | |
| 156 configurator_->ConfigureOutputs(); | |
| 157 return true; | |
| 158 } else { | |
| 159 return false; | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 // static | |
| 164 const OutputConfigurator::ModeInfo* OutputConfigurator::GetModeInfo( | |
| 165 const OutputSnapshot& output, | |
| 166 RRMode mode) { | |
| 167 if (mode == None) | |
| 168 return NULL; | |
| 169 | |
| 170 ModeInfoMap::const_iterator it = output.mode_infos.find(mode); | |
| 171 if (it == output.mode_infos.end()) { | |
| 172 LOG(WARNING) << "Unable to find info about mode " << mode | |
| 173 << " for output " << output.output; | |
| 174 return NULL; | |
| 175 } | |
| 176 return &it->second; | |
| 177 } | |
| 178 | |
| 179 // static | |
| 180 RRMode OutputConfigurator::FindOutputModeMatchingSize( | |
| 181 const OutputSnapshot& output, | |
| 182 int width, | |
| 183 int height) { | |
| 184 RRMode found = None; | |
| 185 float best_rate = 0; | |
| 186 bool non_interlaced_found = false; | |
| 187 for (ModeInfoMap::const_iterator it = output.mode_infos.begin(); | |
| 188 it != output.mode_infos.end(); ++it) { | |
| 189 RRMode mode = it->first; | |
| 190 const ModeInfo& info = it->second; | |
| 191 | |
| 192 if (info.width == width && info.height == height) { | |
| 193 if (info.interlaced) { | |
| 194 if (non_interlaced_found) | |
| 195 continue; | |
| 196 } else { | |
| 197 // Reset the best rate if the non interlaced is | |
| 198 // found the first time. | |
| 199 if (!non_interlaced_found) | |
| 200 best_rate = info.refresh_rate; | |
| 201 non_interlaced_found = true; | |
| 202 } | |
| 203 if (info.refresh_rate < best_rate) | |
| 204 continue; | |
| 205 | |
| 206 found = mode; | |
| 207 best_rate = info.refresh_rate; | |
| 208 } | |
| 209 } | |
| 210 return found; | |
| 211 } | |
| 212 | |
| 213 OutputConfigurator::OutputConfigurator() | |
| 214 : state_controller_(NULL), | |
| 215 mirroring_controller_(NULL), | |
| 216 is_panel_fitting_enabled_(false), | |
| 217 configure_display_(base::SysInfo::IsRunningOnChromeOS()), | |
| 218 output_state_(ui::OUTPUT_STATE_INVALID), | |
| 219 power_state_(DISPLAY_POWER_ALL_ON), | |
| 220 next_output_protection_client_id_(1) {} | |
| 221 | |
| 222 OutputConfigurator::~OutputConfigurator() { | |
| 223 if (native_display_delegate_) | |
| 224 native_display_delegate_->RemoveObserver(this); | |
| 225 } | |
| 226 | |
| 227 void OutputConfigurator::SetNativeDisplayDelegateForTesting( | |
| 228 scoped_ptr<NativeDisplayDelegate> delegate) { | |
| 229 DCHECK(!native_display_delegate_); | |
| 230 | |
| 231 native_display_delegate_ = delegate.Pass(); | |
| 232 native_display_delegate_->AddObserver(this); | |
| 233 configure_display_ = true; | |
| 234 } | |
| 235 | |
| 236 void OutputConfigurator::SetTouchscreenDelegateForTesting( | |
| 237 scoped_ptr<TouchscreenDelegate> delegate) { | |
| 238 DCHECK(!touchscreen_delegate_); | |
| 239 | |
| 240 touchscreen_delegate_ = delegate.Pass(); | |
| 241 } | |
| 242 | |
| 243 void OutputConfigurator::SetInitialDisplayPower(DisplayPowerState power_state) { | |
| 244 DCHECK_EQ(output_state_, ui::OUTPUT_STATE_INVALID); | |
| 245 power_state_ = power_state; | |
| 246 } | |
| 247 | |
| 248 void OutputConfigurator::Init(bool is_panel_fitting_enabled) { | |
| 249 is_panel_fitting_enabled_ = is_panel_fitting_enabled; | |
| 250 if (!configure_display_) | |
| 251 return; | |
| 252 | |
| 253 if (!native_display_delegate_) { | |
| 254 native_display_delegate_.reset(new NativeDisplayDelegateX11()); | |
| 255 native_display_delegate_->AddObserver(this); | |
| 256 } | |
| 257 | |
| 258 if (!touchscreen_delegate_) | |
| 259 touchscreen_delegate_.reset(new TouchscreenDelegateX11()); | |
| 260 } | |
| 261 | |
| 262 void OutputConfigurator::ForceInitialConfigure(uint32 background_color_argb) { | |
| 263 if (!configure_display_) | |
| 264 return; | |
| 265 | |
| 266 native_display_delegate_->GrabServer(); | |
| 267 native_display_delegate_->Initialize(); | |
| 268 | |
| 269 UpdateCachedOutputs(); | |
| 270 if (cached_outputs_.size() > 1 && background_color_argb) | |
| 271 native_display_delegate_->SetBackgroundColor(background_color_argb); | |
| 272 const ui::OutputState new_state = ChooseOutputState(power_state_); | |
| 273 const bool success = EnterStateOrFallBackToSoftwareMirroring( | |
| 274 new_state, power_state_); | |
| 275 | |
| 276 // Force the DPMS on chrome startup as the driver doesn't always detect | |
| 277 // that all displays are on when signing out. | |
| 278 native_display_delegate_->ForceDPMSOn(); | |
| 279 native_display_delegate_->UngrabServer(); | |
| 280 NotifyObservers(success, new_state); | |
| 281 } | |
| 282 | |
| 283 bool OutputConfigurator::ApplyProtections(const DisplayProtections& requests) { | |
| 284 for (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin(); | |
| 285 it != cached_outputs_.end(); ++it) { | |
| 286 uint32_t all_desired = 0; | |
| 287 DisplayProtections::const_iterator request_it = requests.find( | |
| 288 it->display_id); | |
| 289 if (request_it != requests.end()) | |
| 290 all_desired = request_it->second; | |
| 291 switch (it->type) { | |
| 292 case ui::OUTPUT_TYPE_UNKNOWN: | |
| 293 return false; | |
| 294 // DisplayPort, DVI, and HDMI all support HDCP. | |
| 295 case ui::OUTPUT_TYPE_DISPLAYPORT: | |
| 296 case ui::OUTPUT_TYPE_DVI: | |
| 297 case ui::OUTPUT_TYPE_HDMI: { | |
| 298 ui::HDCPState new_desired_state = | |
| 299 (all_desired & ui::OUTPUT_PROTECTION_METHOD_HDCP) | |
| 300 ? ui::HDCP_STATE_DESIRED | |
| 301 : ui::HDCP_STATE_UNDESIRED; | |
| 302 if (!native_display_delegate_->SetHDCPState(*it, new_desired_state)) | |
| 303 return false; | |
| 304 break; | |
| 305 } | |
| 306 case ui::OUTPUT_TYPE_INTERNAL: | |
| 307 case ui::OUTPUT_TYPE_VGA: | |
| 308 case ui::OUTPUT_TYPE_NETWORK: | |
| 309 // No protections for these types. Do nothing. | |
| 310 break; | |
| 311 case ui::OUTPUT_TYPE_NONE: | |
| 312 NOTREACHED(); | |
| 313 break; | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 return true; | |
| 318 } | |
| 319 | |
| 320 OutputConfigurator::OutputProtectionClientId | |
| 321 OutputConfigurator::RegisterOutputProtectionClient() { | |
| 322 if (!configure_display_) | |
| 323 return kInvalidClientId; | |
| 324 | |
| 325 return next_output_protection_client_id_++; | |
| 326 } | |
| 327 | |
| 328 void OutputConfigurator::UnregisterOutputProtectionClient( | |
| 329 OutputProtectionClientId client_id) { | |
| 330 client_protection_requests_.erase(client_id); | |
| 331 | |
| 332 DisplayProtections protections; | |
| 333 for (ProtectionRequests::const_iterator it = | |
| 334 client_protection_requests_.begin(); | |
| 335 it != client_protection_requests_.end(); | |
| 336 ++it) { | |
| 337 for (DisplayProtections::const_iterator it2 = it->second.begin(); | |
| 338 it2 != it->second.end(); ++it2) { | |
| 339 protections[it2->first] |= it2->second; | |
| 340 } | |
| 341 } | |
| 342 | |
| 343 ApplyProtections(protections); | |
| 344 } | |
| 345 | |
| 346 bool OutputConfigurator::QueryOutputProtectionStatus( | |
| 347 OutputProtectionClientId client_id, | |
| 348 int64 display_id, | |
| 349 uint32_t* link_mask, | |
| 350 uint32_t* protection_mask) { | |
| 351 if (!configure_display_) | |
| 352 return false; | |
| 353 | |
| 354 uint32_t enabled = 0; | |
| 355 uint32_t unfulfilled = 0; | |
| 356 *link_mask = 0; | |
| 357 for (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin(); | |
| 358 it != cached_outputs_.end(); ++it) { | |
| 359 if (it->display_id != display_id) | |
| 360 continue; | |
| 361 *link_mask |= it->type; | |
| 362 switch (it->type) { | |
| 363 case ui::OUTPUT_TYPE_UNKNOWN: | |
| 364 return false; | |
| 365 // DisplayPort, DVI, and HDMI all support HDCP. | |
| 366 case ui::OUTPUT_TYPE_DISPLAYPORT: | |
| 367 case ui::OUTPUT_TYPE_DVI: | |
| 368 case ui::OUTPUT_TYPE_HDMI: { | |
| 369 ui::HDCPState state; | |
| 370 if (!native_display_delegate_->GetHDCPState(*it, &state)) | |
| 371 return false; | |
| 372 if (state == ui::HDCP_STATE_ENABLED) | |
| 373 enabled |= ui::OUTPUT_PROTECTION_METHOD_HDCP; | |
| 374 else | |
| 375 unfulfilled |= ui::OUTPUT_PROTECTION_METHOD_HDCP; | |
| 376 break; | |
| 377 } | |
| 378 case ui::OUTPUT_TYPE_INTERNAL: | |
| 379 case ui::OUTPUT_TYPE_VGA: | |
| 380 case ui::OUTPUT_TYPE_NETWORK: | |
| 381 // No protections for these types. Do nothing. | |
| 382 break; | |
| 383 case ui::OUTPUT_TYPE_NONE: | |
| 384 NOTREACHED(); | |
| 385 break; | |
| 386 } | |
| 387 } | |
| 388 | |
| 389 // Don't reveal protections requested by other clients. | |
| 390 ProtectionRequests::iterator it = client_protection_requests_.find(client_id); | |
| 391 if (it != client_protection_requests_.end()) { | |
| 392 uint32_t requested_mask = 0; | |
| 393 if (it->second.find(display_id) != it->second.end()) | |
| 394 requested_mask = it->second[display_id]; | |
| 395 *protection_mask = enabled & ~unfulfilled & requested_mask; | |
| 396 } else { | |
| 397 *protection_mask = 0; | |
| 398 } | |
| 399 return true; | |
| 400 } | |
| 401 | |
| 402 bool OutputConfigurator::EnableOutputProtection( | |
| 403 OutputProtectionClientId client_id, | |
| 404 int64 display_id, | |
| 405 uint32_t desired_method_mask) { | |
| 406 if (!configure_display_) | |
| 407 return false; | |
| 408 | |
| 409 DisplayProtections protections; | |
| 410 for (ProtectionRequests::const_iterator it = | |
| 411 client_protection_requests_.begin(); | |
| 412 it != client_protection_requests_.end(); | |
| 413 ++it) { | |
| 414 for (DisplayProtections::const_iterator it2 = it->second.begin(); | |
| 415 it2 != it->second.end(); ++it2) { | |
| 416 if (it->first == client_id && it2->first == display_id) | |
| 417 continue; | |
| 418 protections[it2->first] |= it2->second; | |
| 419 } | |
| 420 } | |
| 421 protections[display_id] |= desired_method_mask; | |
| 422 | |
| 423 if (!ApplyProtections(protections)) | |
| 424 return false; | |
| 425 | |
| 426 if (desired_method_mask == ui::OUTPUT_PROTECTION_METHOD_NONE) { | |
| 427 if (client_protection_requests_.find(client_id) != | |
| 428 client_protection_requests_.end()) { | |
| 429 client_protection_requests_[client_id].erase(display_id); | |
| 430 if (client_protection_requests_[client_id].size() == 0) | |
| 431 client_protection_requests_.erase(client_id); | |
| 432 } | |
| 433 } else { | |
| 434 client_protection_requests_[client_id][display_id] = desired_method_mask; | |
| 435 } | |
| 436 | |
| 437 return true; | |
| 438 } | |
| 439 | |
| 440 void OutputConfigurator::PrepareForExit() { | |
| 441 configure_display_ = false; | |
| 442 } | |
| 443 | |
| 444 bool OutputConfigurator::SetDisplayPower(DisplayPowerState power_state, | |
| 445 int flags) { | |
| 446 if (!configure_display_) | |
| 447 return false; | |
| 448 | |
| 449 VLOG(1) << "SetDisplayPower: power_state=" | |
| 450 << DisplayPowerStateToString(power_state) << " flags=" << flags | |
| 451 << ", configure timer=" | |
| 452 << ((configure_timer_.get() && configure_timer_->IsRunning()) ? | |
| 453 "Running" : "Stopped"); | |
| 454 if (power_state == power_state_ && !(flags & kSetDisplayPowerForceProbe)) | |
| 455 return true; | |
| 456 | |
| 457 native_display_delegate_->GrabServer(); | |
| 458 UpdateCachedOutputs(); | |
| 459 | |
| 460 const ui::OutputState new_state = ChooseOutputState(power_state); | |
| 461 bool attempted_change = false; | |
| 462 bool success = false; | |
| 463 | |
| 464 bool only_if_single_internal_display = | |
| 465 flags & kSetDisplayPowerOnlyIfSingleInternalDisplay; | |
| 466 bool single_internal_display = | |
| 467 cached_outputs_.size() == 1 && | |
| 468 cached_outputs_[0].type == ui::OUTPUT_TYPE_INTERNAL; | |
| 469 if (single_internal_display || !only_if_single_internal_display) { | |
| 470 success = EnterStateOrFallBackToSoftwareMirroring(new_state, power_state); | |
| 471 attempted_change = true; | |
| 472 | |
| 473 // Force the DPMS on since the driver doesn't always detect that it | |
| 474 // should turn on. This is needed when coming back from idle suspend. | |
| 475 if (success && power_state != DISPLAY_POWER_ALL_OFF) | |
| 476 native_display_delegate_->ForceDPMSOn(); | |
| 477 } | |
| 478 | |
| 479 native_display_delegate_->UngrabServer(); | |
| 480 if (attempted_change) | |
| 481 NotifyObservers(success, new_state); | |
| 482 return true; | |
| 483 } | |
| 484 | |
| 485 bool OutputConfigurator::SetDisplayMode(ui::OutputState new_state) { | |
| 486 if (!configure_display_) | |
| 487 return false; | |
| 488 | |
| 489 VLOG(1) << "SetDisplayMode: state=" << OutputStateToString(new_state); | |
| 490 if (output_state_ == new_state) { | |
| 491 // Cancel software mirroring if the state is moving from | |
| 492 // OUTPUT_STATE_DUAL_EXTENDED to OUTPUT_STATE_DUAL_EXTENDED. | |
| 493 if (mirroring_controller_ && new_state == ui::OUTPUT_STATE_DUAL_EXTENDED) | |
| 494 mirroring_controller_->SetSoftwareMirroring(false); | |
| 495 NotifyObservers(true, new_state); | |
| 496 return true; | |
| 497 } | |
| 498 | |
| 499 native_display_delegate_->GrabServer(); | |
| 500 UpdateCachedOutputs(); | |
| 501 const bool success = EnterStateOrFallBackToSoftwareMirroring( | |
| 502 new_state, power_state_); | |
| 503 native_display_delegate_->UngrabServer(); | |
| 504 | |
| 505 NotifyObservers(success, new_state); | |
| 506 return success; | |
| 507 } | |
| 508 | |
| 509 void OutputConfigurator::OnConfigurationChanged() { | |
| 510 // Configure outputs with |kConfigureDelayMs| delay, | |
| 511 // so that time-consuming ConfigureOutputs() won't be called multiple times. | |
| 512 if (configure_timer_.get()) { | |
| 513 configure_timer_->Reset(); | |
| 514 } else { | |
| 515 configure_timer_.reset(new base::OneShotTimer<OutputConfigurator>()); | |
| 516 configure_timer_->Start( | |
| 517 FROM_HERE, | |
| 518 base::TimeDelta::FromMilliseconds(kConfigureDelayMs), | |
| 519 this, | |
| 520 &OutputConfigurator::ConfigureOutputs); | |
| 521 } | |
| 522 } | |
| 523 | |
| 524 void OutputConfigurator::AddObserver(Observer* observer) { | |
| 525 observers_.AddObserver(observer); | |
| 526 } | |
| 527 | |
| 528 void OutputConfigurator::RemoveObserver(Observer* observer) { | |
| 529 observers_.RemoveObserver(observer); | |
| 530 } | |
| 531 | |
| 532 void OutputConfigurator::SuspendDisplays() { | |
| 533 // If the display is off due to user inactivity and there's only a single | |
| 534 // internal display connected, switch to the all-on state before | |
| 535 // suspending. This shouldn't be very noticeable to the user since the | |
| 536 // backlight is off at this point, and doing this lets us resume directly | |
| 537 // into the "on" state, which greatly reduces resume times. | |
| 538 if (power_state_ == DISPLAY_POWER_ALL_OFF) { | |
| 539 SetDisplayPower(DISPLAY_POWER_ALL_ON, | |
| 540 kSetDisplayPowerOnlyIfSingleInternalDisplay); | |
| 541 | |
| 542 // We need to make sure that the monitor configuration we just did actually | |
| 543 // completes before we return, because otherwise the X message could be | |
| 544 // racing with the HandleSuspendReadiness message. | |
| 545 native_display_delegate_->SyncWithServer(); | |
| 546 } | |
| 547 } | |
| 548 | |
| 549 void OutputConfigurator::ResumeDisplays() { | |
| 550 // Force probing to ensure that we pick up any changes that were made | |
| 551 // while the system was suspended. | |
| 552 SetDisplayPower(power_state_, kSetDisplayPowerForceProbe); | |
| 553 } | |
| 554 | |
| 555 void OutputConfigurator::UpdateCachedOutputs() { | |
| 556 cached_outputs_ = native_display_delegate_->GetOutputs(); | |
| 557 touchscreen_delegate_->AssociateTouchscreens(&cached_outputs_); | |
| 558 | |
| 559 // Set |selected_mode| fields. | |
| 560 for (size_t i = 0; i < cached_outputs_.size(); ++i) { | |
| 561 OutputSnapshot* output = &cached_outputs_[i]; | |
| 562 if (output->has_display_id) { | |
| 563 int width = 0, height = 0; | |
| 564 if (state_controller_ && | |
| 565 state_controller_->GetResolutionForDisplayId( | |
| 566 output->display_id, &width, &height)) { | |
| 567 output->selected_mode = | |
| 568 FindOutputModeMatchingSize(*output, width, height); | |
| 569 } | |
| 570 } | |
| 571 // Fall back to native mode. | |
| 572 if (output->selected_mode == None) | |
| 573 output->selected_mode = output->native_mode; | |
| 574 } | |
| 575 | |
| 576 // Set |mirror_mode| fields. | |
| 577 if (cached_outputs_.size() == 2) { | |
| 578 bool one_is_internal = cached_outputs_[0].type == ui::OUTPUT_TYPE_INTERNAL; | |
| 579 bool two_is_internal = cached_outputs_[1].type == ui::OUTPUT_TYPE_INTERNAL; | |
| 580 int internal_outputs = (one_is_internal ? 1 : 0) + | |
| 581 (two_is_internal ? 1 : 0); | |
| 582 DCHECK_LT(internal_outputs, 2); | |
| 583 LOG_IF(WARNING, internal_outputs == 2) | |
| 584 << "Two internal outputs detected."; | |
| 585 | |
| 586 bool can_mirror = false; | |
| 587 for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) { | |
| 588 // Try preserving external output's aspect ratio on the first attempt. | |
| 589 // If that fails, fall back to the highest matching resolution. | |
| 590 bool preserve_aspect = attempt == 0; | |
| 591 | |
| 592 if (internal_outputs == 1) { | |
| 593 if (one_is_internal) { | |
| 594 can_mirror = FindMirrorMode(&cached_outputs_[0], &cached_outputs_[1], | |
| 595 is_panel_fitting_enabled_, preserve_aspect); | |
| 596 } else { | |
| 597 DCHECK(two_is_internal); | |
| 598 can_mirror = FindMirrorMode(&cached_outputs_[1], &cached_outputs_[0], | |
| 599 is_panel_fitting_enabled_, preserve_aspect); | |
| 600 } | |
| 601 } else { // if (internal_outputs == 0) | |
| 602 // No panel fitting for external outputs, so fall back to exact match. | |
| 603 can_mirror = FindMirrorMode(&cached_outputs_[0], &cached_outputs_[1], | |
| 604 false, preserve_aspect); | |
| 605 if (!can_mirror && preserve_aspect) { | |
| 606 // FindMirrorMode() will try to preserve aspect ratio of what it | |
| 607 // thinks is external display, so if it didn't succeed with one, maybe | |
| 608 // it will succeed with the other. This way we will have the correct | |
| 609 // aspect ratio on at least one of them. | |
| 610 can_mirror = FindMirrorMode(&cached_outputs_[1], &cached_outputs_[0], | |
| 611 false, preserve_aspect); | |
| 612 } | |
| 613 } | |
| 614 } | |
| 615 } | |
| 616 } | |
| 617 | |
| 618 bool OutputConfigurator::FindMirrorMode(OutputSnapshot* internal_output, | |
| 619 OutputSnapshot* external_output, | |
| 620 bool try_panel_fitting, | |
| 621 bool preserve_aspect) { | |
| 622 const ModeInfo* internal_native_info = | |
| 623 GetModeInfo(*internal_output, internal_output->native_mode); | |
| 624 const ModeInfo* external_native_info = | |
| 625 GetModeInfo(*external_output, external_output->native_mode); | |
| 626 if (!internal_native_info || !external_native_info) | |
| 627 return false; | |
| 628 | |
| 629 // Check if some external output resolution can be mirrored on internal. | |
| 630 // Prefer the modes in the order that X sorts them, assuming this is the order | |
| 631 // in which they look better on the monitor. | |
| 632 for (ModeInfoMap::const_iterator external_it = | |
| 633 external_output->mode_infos.begin(); | |
| 634 external_it != external_output->mode_infos.end(); ++external_it) { | |
| 635 const ModeInfo& external_info = external_it->second; | |
| 636 bool is_native_aspect_ratio = | |
| 637 external_native_info->width * external_info.height == | |
| 638 external_native_info->height * external_info.width; | |
| 639 if (preserve_aspect && !is_native_aspect_ratio) | |
| 640 continue; // Allow only aspect ratio preserving modes for mirroring. | |
| 641 | |
| 642 // Try finding an exact match. | |
| 643 for (ModeInfoMap::const_iterator internal_it = | |
| 644 internal_output->mode_infos.begin(); | |
| 645 internal_it != internal_output->mode_infos.end(); ++internal_it) { | |
| 646 const ModeInfo& internal_info = internal_it->second; | |
| 647 if (internal_info.width == external_info.width && | |
| 648 internal_info.height == external_info.height && | |
| 649 internal_info.interlaced == external_info.interlaced) { | |
| 650 internal_output->mirror_mode = internal_it->first; | |
| 651 external_output->mirror_mode = external_it->first; | |
| 652 return true; // Mirror mode found. | |
| 653 } | |
| 654 } | |
| 655 | |
| 656 // Try to create a matching internal output mode by panel fitting. | |
| 657 if (try_panel_fitting) { | |
| 658 // We can downscale by 1.125, and upscale indefinitely. Downscaling looks | |
| 659 // ugly, so, can fit == can upscale. Also, internal panels don't support | |
| 660 // fitting interlaced modes. | |
| 661 bool can_fit = | |
| 662 internal_native_info->width >= external_info.width && | |
| 663 internal_native_info->height >= external_info.height && | |
| 664 !external_info.interlaced; | |
| 665 if (can_fit) { | |
| 666 RRMode mode = external_it->first; | |
| 667 native_display_delegate_->AddMode(*internal_output, mode); | |
| 668 internal_output->mode_infos.insert(std::make_pair(mode, external_info)); | |
| 669 internal_output->mirror_mode = mode; | |
| 670 external_output->mirror_mode = mode; | |
| 671 return true; // Mirror mode created. | |
| 672 } | |
| 673 } | |
| 674 } | |
| 675 | |
| 676 return false; | |
| 677 } | |
| 678 | |
| 679 void OutputConfigurator::ConfigureOutputs() { | |
| 680 configure_timer_.reset(); | |
| 681 | |
| 682 if (!configure_display_) | |
| 683 return; | |
| 684 | |
| 685 native_display_delegate_->GrabServer(); | |
| 686 UpdateCachedOutputs(); | |
| 687 const ui::OutputState new_state = ChooseOutputState(power_state_); | |
| 688 const bool success = EnterStateOrFallBackToSoftwareMirroring( | |
| 689 new_state, power_state_); | |
| 690 native_display_delegate_->UngrabServer(); | |
| 691 | |
| 692 NotifyObservers(success, new_state); | |
| 693 } | |
| 694 | |
| 695 void OutputConfigurator::NotifyObservers(bool success, | |
| 696 ui::OutputState attempted_state) { | |
| 697 if (success) { | |
| 698 FOR_EACH_OBSERVER(Observer, observers_, | |
| 699 OnDisplayModeChanged(cached_outputs_)); | |
| 700 } else { | |
| 701 FOR_EACH_OBSERVER(Observer, observers_, | |
| 702 OnDisplayModeChangeFailed(attempted_state)); | |
| 703 } | |
| 704 } | |
| 705 | |
| 706 bool OutputConfigurator::EnterStateOrFallBackToSoftwareMirroring( | |
| 707 ui::OutputState output_state, | |
| 708 DisplayPowerState power_state) { | |
| 709 bool success = EnterState(output_state, power_state); | |
| 710 if (mirroring_controller_) { | |
| 711 bool enable_software_mirroring = false; | |
| 712 if (!success && output_state == ui::OUTPUT_STATE_DUAL_MIRROR) { | |
| 713 if (output_state_ != ui::OUTPUT_STATE_DUAL_EXTENDED || | |
| 714 power_state_ != power_state) | |
| 715 EnterState(ui::OUTPUT_STATE_DUAL_EXTENDED, power_state); | |
| 716 enable_software_mirroring = success = | |
| 717 output_state_ == ui::OUTPUT_STATE_DUAL_EXTENDED; | |
| 718 } | |
| 719 mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring); | |
| 720 } | |
| 721 return success; | |
| 722 } | |
| 723 | |
| 724 bool OutputConfigurator::EnterState(ui::OutputState output_state, | |
| 725 DisplayPowerState power_state) { | |
| 726 std::vector<bool> output_power; | |
| 727 int num_on_outputs = GetOutputPower( | |
| 728 cached_outputs_, power_state, &output_power); | |
| 729 VLOG(1) << "EnterState: output=" << OutputStateToString(output_state) | |
| 730 << " power=" << DisplayPowerStateToString(power_state); | |
| 731 | |
| 732 // Framebuffer dimensions. | |
| 733 int width = 0, height = 0; | |
| 734 std::vector<OutputSnapshot> updated_outputs = cached_outputs_; | |
| 735 | |
| 736 switch (output_state) { | |
| 737 case ui::OUTPUT_STATE_INVALID: | |
| 738 NOTREACHED() << "Ignoring request to enter invalid state with " | |
| 739 << updated_outputs.size() << " connected output(s)"; | |
| 740 return false; | |
| 741 case ui::OUTPUT_STATE_HEADLESS: | |
| 742 if (updated_outputs.size() != 0) { | |
| 743 LOG(WARNING) << "Ignoring request to enter headless mode with " | |
| 744 << updated_outputs.size() << " connected output(s)"; | |
| 745 return false; | |
| 746 } | |
| 747 break; | |
| 748 case ui::OUTPUT_STATE_SINGLE: { | |
| 749 // If there are multiple outputs connected, only one should be turned on. | |
| 750 if (updated_outputs.size() != 1 && num_on_outputs != 1) { | |
| 751 LOG(WARNING) << "Ignoring request to enter single mode with " | |
| 752 << updated_outputs.size() << " connected outputs and " | |
| 753 << num_on_outputs << " turned on"; | |
| 754 return false; | |
| 755 } | |
| 756 | |
| 757 for (size_t i = 0; i < updated_outputs.size(); ++i) { | |
| 758 OutputSnapshot* output = &updated_outputs[i]; | |
| 759 output->x = 0; | |
| 760 output->y = 0; | |
| 761 output->current_mode = output_power[i] ? output->selected_mode : None; | |
| 762 | |
| 763 if (output_power[i] || updated_outputs.size() == 1) { | |
| 764 const ModeInfo* mode_info = | |
| 765 GetModeInfo(*output, output->selected_mode); | |
| 766 if (!mode_info) | |
| 767 return false; | |
| 768 if (mode_info->width == 1024 && mode_info->height == 768) { | |
| 769 VLOG(1) << "Potentially misdetecting display(1024x768):" | |
| 770 << " outputs size=" << updated_outputs.size() | |
| 771 << ", num_on_outputs=" << num_on_outputs | |
| 772 << ", current size:" << width << "x" << height | |
| 773 << ", i=" << i | |
| 774 << ", output=" << OutputSnapshotToString(output) | |
| 775 << ", mode_info=" << ModeInfoToString(mode_info); | |
| 776 } | |
| 777 width = mode_info->width; | |
| 778 height = mode_info->height; | |
| 779 } | |
| 780 } | |
| 781 break; | |
| 782 } | |
| 783 case ui::OUTPUT_STATE_DUAL_MIRROR: { | |
| 784 if (updated_outputs.size() != 2 || | |
| 785 (num_on_outputs != 0 && num_on_outputs != 2)) { | |
| 786 LOG(WARNING) << "Ignoring request to enter mirrored mode with " | |
| 787 << updated_outputs.size() << " connected output(s) and " | |
| 788 << num_on_outputs << " turned on"; | |
| 789 return false; | |
| 790 } | |
| 791 | |
| 792 if (!updated_outputs[0].mirror_mode) | |
| 793 return false; | |
| 794 const ModeInfo* mode_info = | |
| 795 GetModeInfo(updated_outputs[0], updated_outputs[0].mirror_mode); | |
| 796 if (!mode_info) | |
| 797 return false; | |
| 798 width = mode_info->width; | |
| 799 height = mode_info->height; | |
| 800 | |
| 801 for (size_t i = 0; i < updated_outputs.size(); ++i) { | |
| 802 OutputSnapshot* output = &updated_outputs[i]; | |
| 803 output->x = 0; | |
| 804 output->y = 0; | |
| 805 output->current_mode = output_power[i] ? output->mirror_mode : None; | |
| 806 if (output->touch_device_id) { | |
| 807 // CTM needs to be calculated if aspect preserving scaling is used. | |
| 808 // Otherwise, assume it is full screen, and use identity CTM. | |
| 809 if (output->mirror_mode != output->native_mode && | |
| 810 output->is_aspect_preserving_scaling) { | |
| 811 output->transform = GetMirrorModeCTM(*output); | |
| 812 mirrored_display_area_ratio_map_[output->touch_device_id] = | |
| 813 GetMirroredDisplayAreaRatio(*output); | |
| 814 } | |
| 815 } | |
| 816 } | |
| 817 break; | |
| 818 } | |
| 819 case ui::OUTPUT_STATE_DUAL_EXTENDED: { | |
| 820 if (updated_outputs.size() != 2 || | |
| 821 (num_on_outputs != 0 && num_on_outputs != 2)) { | |
| 822 LOG(WARNING) << "Ignoring request to enter extended mode with " | |
| 823 << updated_outputs.size() << " connected output(s) and " | |
| 824 << num_on_outputs << " turned on"; | |
| 825 return false; | |
| 826 } | |
| 827 | |
| 828 for (size_t i = 0; i < updated_outputs.size(); ++i) { | |
| 829 OutputSnapshot* output = &updated_outputs[i]; | |
| 830 output->x = 0; | |
| 831 output->y = height ? height + kVerticalGap : 0; | |
| 832 output->current_mode = output_power[i] ? output->selected_mode : None; | |
| 833 | |
| 834 // Retain the full screen size even if all outputs are off so the | |
| 835 // same desktop configuration can be restored when the outputs are | |
| 836 // turned back on. | |
| 837 const ModeInfo* mode_info = | |
| 838 GetModeInfo(updated_outputs[i], updated_outputs[i].selected_mode); | |
| 839 if (!mode_info) | |
| 840 return false; | |
| 841 width = std::max<int>(width, mode_info->width); | |
| 842 height += (height ? kVerticalGap : 0) + mode_info->height; | |
| 843 } | |
| 844 | |
| 845 for (size_t i = 0; i < updated_outputs.size(); ++i) { | |
| 846 OutputSnapshot* output = &updated_outputs[i]; | |
| 847 if (output->touch_device_id) | |
| 848 output->transform = GetExtendedModeCTM(*output, width, height); | |
| 849 } | |
| 850 break; | |
| 851 } | |
| 852 } | |
| 853 | |
| 854 // Finally, apply the desired changes. | |
| 855 DCHECK_EQ(cached_outputs_.size(), updated_outputs.size()); | |
| 856 bool all_succeeded = true; | |
| 857 if (!updated_outputs.empty()) { | |
| 858 native_display_delegate_->CreateFrameBuffer(width, height, updated_outputs); | |
| 859 for (size_t i = 0; i < updated_outputs.size(); ++i) { | |
| 860 const OutputSnapshot& output = updated_outputs[i]; | |
| 861 bool configure_succeeded =false; | |
| 862 | |
| 863 while (true) { | |
| 864 if (native_display_delegate_->Configure(output, | |
| 865 output.current_mode, | |
| 866 output.x, | |
| 867 output.y)) { | |
| 868 configure_succeeded = true; | |
| 869 break; | |
| 870 } | |
| 871 | |
| 872 LOG(WARNING) << "Unable to configure CRTC " << output.crtc << ":" | |
| 873 << " mode=" << output.current_mode | |
| 874 << " output=" << output.output | |
| 875 << " x=" << output.x | |
| 876 << " y=" << output.y; | |
| 877 | |
| 878 const ModeInfo* mode_info = GetModeInfo(output, output.current_mode); | |
| 879 if (!mode_info) | |
| 880 break; | |
| 881 | |
| 882 // Find the mode with the next-best resolution and see if that can | |
| 883 // be set. | |
| 884 int best_mode_pixels = 0; | |
| 885 | |
| 886 int current_mode_pixels = mode_info->width * mode_info->height; | |
| 887 for (ModeInfoMap::const_iterator it = output.mode_infos.begin(); | |
| 888 it != output.mode_infos.end(); it++) { | |
| 889 int pixel_count = it->second.width * it->second.height; | |
| 890 if ((pixel_count < current_mode_pixels) && | |
| 891 (pixel_count > best_mode_pixels)) { | |
| 892 updated_outputs[i].current_mode = it->first; | |
| 893 best_mode_pixels = pixel_count; | |
| 894 } | |
| 895 } | |
| 896 | |
| 897 if (best_mode_pixels == 0) | |
| 898 break; | |
| 899 } | |
| 900 | |
| 901 if (configure_succeeded) { | |
| 902 if (output.touch_device_id) | |
| 903 touchscreen_delegate_->ConfigureCTM(output.touch_device_id, | |
| 904 output.transform); | |
| 905 cached_outputs_[i] = updated_outputs[i]; | |
| 906 } else { | |
| 907 all_succeeded = false; | |
| 908 } | |
| 909 | |
| 910 // If we are trying to set mirror mode and one of the modesets fails, | |
| 911 // then the two monitors will be mis-matched. In this case, return | |
| 912 // false to let the observers be aware. | |
| 913 if (output_state == ui::OUTPUT_STATE_DUAL_MIRROR && output_power[i] && | |
| 914 output.current_mode != output.mirror_mode) | |
| 915 all_succeeded = false; | |
| 916 | |
| 917 } | |
| 918 } | |
| 919 | |
| 920 if (all_succeeded) { | |
| 921 output_state_ = output_state; | |
| 922 power_state_ = power_state; | |
| 923 } | |
| 924 return all_succeeded; | |
| 925 } | |
| 926 | |
| 927 ui::OutputState OutputConfigurator::ChooseOutputState( | |
| 928 DisplayPowerState power_state) const { | |
| 929 int num_on_outputs = GetOutputPower(cached_outputs_, power_state, NULL); | |
| 930 switch (cached_outputs_.size()) { | |
| 931 case 0: | |
| 932 return ui::OUTPUT_STATE_HEADLESS; | |
| 933 case 1: | |
| 934 return ui::OUTPUT_STATE_SINGLE; | |
| 935 case 2: { | |
| 936 if (num_on_outputs == 1) { | |
| 937 // If only one output is currently turned on, return the "single" | |
| 938 // state so that its native mode will be used. | |
| 939 return ui::OUTPUT_STATE_SINGLE; | |
| 940 } else { | |
| 941 // With either both outputs on or both outputs off, use one of the | |
| 942 // dual modes. | |
| 943 std::vector<int64> display_ids; | |
| 944 for (size_t i = 0; i < cached_outputs_.size(); ++i) { | |
| 945 // If display id isn't available, switch to extended mode. | |
| 946 if (!cached_outputs_[i].has_display_id) | |
| 947 return ui::OUTPUT_STATE_DUAL_EXTENDED; | |
| 948 display_ids.push_back(cached_outputs_[i].display_id); | |
| 949 } | |
| 950 return state_controller_->GetStateForDisplayIds(display_ids); | |
| 951 } | |
| 952 } | |
| 953 default: | |
| 954 NOTREACHED(); | |
| 955 } | |
| 956 return ui::OUTPUT_STATE_INVALID; | |
| 957 } | |
| 958 | |
| 959 OutputConfigurator::CoordinateTransformation | |
| 960 OutputConfigurator::GetMirrorModeCTM( | |
| 961 const OutputConfigurator::OutputSnapshot& output) { | |
| 962 CoordinateTransformation ctm; // Default to identity | |
| 963 const ModeInfo* native_mode_info = GetModeInfo(output, output.native_mode); | |
| 964 const ModeInfo* mirror_mode_info = GetModeInfo(output, output.mirror_mode); | |
| 965 | |
| 966 if (!native_mode_info || !mirror_mode_info || | |
| 967 native_mode_info->height == 0 || mirror_mode_info->height == 0 || | |
| 968 native_mode_info->width == 0 || mirror_mode_info->width == 0) | |
| 969 return ctm; | |
| 970 | |
| 971 float native_mode_ar = static_cast<float>(native_mode_info->width) / | |
| 972 static_cast<float>(native_mode_info->height); | |
| 973 float mirror_mode_ar = static_cast<float>(mirror_mode_info->width) / | |
| 974 static_cast<float>(mirror_mode_info->height); | |
| 975 | |
| 976 if (mirror_mode_ar > native_mode_ar) { // Letterboxing | |
| 977 ctm.x_scale = 1.0; | |
| 978 ctm.x_offset = 0.0; | |
| 979 ctm.y_scale = mirror_mode_ar / native_mode_ar; | |
| 980 ctm.y_offset = (native_mode_ar / mirror_mode_ar - 1.0) * 0.5; | |
| 981 return ctm; | |
| 982 } | |
| 983 if (native_mode_ar > mirror_mode_ar) { // Pillarboxing | |
| 984 ctm.y_scale = 1.0; | |
| 985 ctm.y_offset = 0.0; | |
| 986 ctm.x_scale = native_mode_ar / mirror_mode_ar; | |
| 987 ctm.x_offset = (mirror_mode_ar / native_mode_ar - 1.0) * 0.5; | |
| 988 return ctm; | |
| 989 } | |
| 990 | |
| 991 return ctm; // Same aspect ratio - return identity | |
| 992 } | |
| 993 | |
| 994 OutputConfigurator::CoordinateTransformation | |
| 995 OutputConfigurator::GetExtendedModeCTM( | |
| 996 const OutputConfigurator::OutputSnapshot& output, | |
| 997 int framebuffer_width, | |
| 998 int framebuffer_height) { | |
| 999 CoordinateTransformation ctm; // Default to identity | |
| 1000 const ModeInfo* mode_info = GetModeInfo(output, output.selected_mode); | |
| 1001 DCHECK(mode_info); | |
| 1002 if (!mode_info) | |
| 1003 return ctm; | |
| 1004 // An example of how to calculate the CTM. | |
| 1005 // Suppose we have 2 monitors, the first one has size 1366 x 768. | |
| 1006 // The second one has size 2560 x 1600 | |
| 1007 // The total size of framebuffer is 2560 x 2428 | |
| 1008 // where 2428 = 768 + 60 (hidden gap) + 1600 | |
| 1009 // and the sceond monitor is translated to Point (0, 828) in the | |
| 1010 // framebuffer. | |
| 1011 // X will first map input event location to [0, 2560) x [0, 2428), | |
| 1012 // then apply CTM on it. | |
| 1013 // So to compute CTM, for monitor1, we have | |
| 1014 // x_scale = (1366 - 1) / (2560 - 1) | |
| 1015 // x_offset = 0 / (2560 - 1) | |
| 1016 // y_scale = (768 - 1) / (2428 - 1) | |
| 1017 // y_offset = 0 / (2428 -1) | |
| 1018 // For Monitor 2, we have | |
| 1019 // x_scale = (2560 - 1) / (2560 - 1) | |
| 1020 // x_offset = 0 / (2560 - 1) | |
| 1021 // y_scale = (1600 - 1) / (2428 - 1) | |
| 1022 // y_offset = 828 / (2428 -1) | |
| 1023 // See the unittest OutputConfiguratorTest.CTMForMultiScreens. | |
| 1024 ctm.x_scale = | |
| 1025 static_cast<float>(mode_info->width - 1) / (framebuffer_width - 1); | |
| 1026 ctm.x_offset = static_cast<float>(output.x) / (framebuffer_width - 1); | |
| 1027 ctm.y_scale = | |
| 1028 static_cast<float>(mode_info->height - 1) / (framebuffer_height - 1); | |
| 1029 ctm.y_offset = static_cast<float>(output.y) / (framebuffer_height - 1); | |
| 1030 return ctm; | |
| 1031 } | |
| 1032 | |
| 1033 float OutputConfigurator::GetMirroredDisplayAreaRatio( | |
| 1034 const OutputConfigurator::OutputSnapshot& output) { | |
| 1035 float area_ratio = 1.0f; | |
| 1036 const ModeInfo* native_mode_info = GetModeInfo(output, output.native_mode); | |
| 1037 const ModeInfo* mirror_mode_info = GetModeInfo(output, output.mirror_mode); | |
| 1038 | |
| 1039 if (!native_mode_info || !mirror_mode_info || | |
| 1040 native_mode_info->height == 0 || mirror_mode_info->height == 0 || | |
| 1041 native_mode_info->width == 0 || mirror_mode_info->width == 0) | |
| 1042 return area_ratio; | |
| 1043 | |
| 1044 float width_ratio = static_cast<float>(mirror_mode_info->width) / | |
| 1045 static_cast<float>(native_mode_info->width); | |
| 1046 float height_ratio = static_cast<float>(mirror_mode_info->height) / | |
| 1047 static_cast<float>(native_mode_info->height); | |
| 1048 | |
| 1049 area_ratio = width_ratio * height_ratio; | |
| 1050 return area_ratio; | |
| 1051 } | |
| 1052 | |
| 1053 } // namespace chromeos | |
| OLD | NEW |