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 #include <X11/extensions/XInput2.h> 10 11 #include "base/bind.h" 12 #include "base/chromeos/chromeos_version.h" 13 #include "base/logging.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/time/time.h" 16 #include "chromeos/display/output_util.h" 17 #include "chromeos/display/real_output_configurator_delegate.h" 18 19 namespace chromeos { 20 21 namespace { 22 23 // The delay to perform configuration after RRNotify. See the comment 24 // in |Dispatch()|. 25 const int64 kConfigureDelayMs = 500; 26 27 // Returns a string describing |state|. 28 std::string DisplayPowerStateToString(DisplayPowerState state) { 29 switch (state) { 30 case DISPLAY_POWER_ALL_ON: 31 return "ALL_ON"; 32 case DISPLAY_POWER_ALL_OFF: 33 return "ALL_OFF"; 34 case DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON: 35 return "INTERNAL_OFF_EXTERNAL_ON"; 36 case DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF: 37 return "INTERNAL_ON_EXTERNAL_OFF"; 38 default: 39 return "unknown (" + base::IntToString(state) + ")"; 40 } 41 } 42 43 // Returns a string describing |state|. 44 std::string OutputStateToString(OutputState state) { 45 switch (state) { 46 case STATE_INVALID: 47 return "INVALID"; 48 case STATE_HEADLESS: 49 return "HEADLESS"; 50 case STATE_SINGLE: 51 return "SINGLE"; 52 case STATE_DUAL_MIRROR: 53 return "DUAL_MIRROR"; 54 case STATE_DUAL_EXTENDED: 55 return "DUAL_EXTENDED"; 56 } 57 NOTREACHED() << "Unknown state " << state; 58 return "INVALID"; 59 } 60 61 // Returns the number of outputs in |outputs| that should be turned on, per 62 // |state|. If |output_power| is non-NULL, it is updated to contain the 63 // on/off state of each corresponding entry in |outputs|. 64 int GetOutputPower( 65 const std::vector<OutputConfigurator::OutputSnapshot>& outputs, 66 DisplayPowerState state, 67 std::vector<bool>* output_power) { 68 int num_on_outputs = 0; 69 if (output_power) 70 output_power->resize(outputs.size()); 71 72 for (size_t i = 0; i < outputs.size(); ++i) { 73 bool internal = outputs[i].is_internal; 74 bool on = state == DISPLAY_POWER_ALL_ON || 75 (state == DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && !internal) || 76 (state == DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal); 77 if (output_power) 78 (*output_power)[i] = on; 79 if (on) 80 num_on_outputs++; 81 } 82 return num_on_outputs; 83 } 84 85 // Determine if there is an "internal" output and how many outputs are 86 // connected. 87 bool IsProjecting( 88 const std::vector<OutputConfigurator::OutputSnapshot>& outputs) { 89 bool has_internal_output = false; 90 int connected_output_count = outputs.size(); 91 for (size_t i = 0; i < outputs.size(); ++i) 92 has_internal_output |= outputs[i].is_internal; 93 94 // "Projecting" is defined as having more than 1 output connected while at 95 // least one of them is an internal output. 96 return has_internal_output && (connected_output_count > 1); 97 } 98 99 } // namespace 100 101 OutputConfigurator::CoordinateTransformation::CoordinateTransformation() 102 : x_scale(1.0), 103 x_offset(0.0), 104 y_scale(1.0), 105 y_offset(0.0) {} 106 107 OutputConfigurator::OutputSnapshot::OutputSnapshot() 108 : output(None), 109 crtc(None), 110 current_mode(None), 111 native_mode(None), 112 mirror_mode(None), 113 selected_mode(None), 114 x(0), 115 y(0), 116 is_internal(false), 117 is_aspect_preserving_scaling(false), 118 touch_device_id(0), 119 display_id(0), 120 has_display_id(false) {} 121 122 bool OutputConfigurator::TestApi::SendOutputChangeEvents(bool connected) { 123 XRRScreenChangeNotifyEvent screen_event; 124 memset(&screen_event, 0, sizeof(screen_event)); 125 screen_event.type = xrandr_event_base_ + RRScreenChangeNotify; 126 configurator_->Dispatch( 127 reinterpret_cast<const base::NativeEvent>(&screen_event)); 128 129 XRROutputChangeNotifyEvent notify_event; 130 memset(¬ify_event, 0, sizeof(notify_event)); 131 notify_event.type = xrandr_event_base_ + RRNotify; 132 notify_event.subtype = RRNotify_OutputChange; 133 notify_event.connection = connected ? RR_Connected : RR_Disconnected; 134 configurator_->Dispatch( 135 reinterpret_cast<const base::NativeEvent>(¬ify_event)); 136 137 if (!configurator_->configure_timer_->IsRunning()) { 138 LOG(ERROR) << "ConfigureOutputs() timer not running"; 139 return false; 140 } 141 142 configurator_->ConfigureOutputs(); 143 return true; 144 } 145 146 OutputConfigurator::OutputConfigurator() 147 : state_controller_(NULL), 148 mirroring_controller_(NULL), 149 configure_display_(base::chromeos::IsRunningOnChromeOS()), 150 xrandr_event_base_(0), 151 output_state_(STATE_INVALID), 152 power_state_(DISPLAY_POWER_ALL_ON) { 153 } 154 155 OutputConfigurator::~OutputConfigurator() {} 156 157 void OutputConfigurator::SetDelegateForTesting(scoped_ptr<Delegate> delegate) { 158 delegate_ = delegate.Pass(); 159 configure_display_ = true; 160 } 161 162 void OutputConfigurator::SetInitialDisplayPower(DisplayPowerState power_state) { 163 DCHECK_EQ(output_state_, STATE_INVALID); 164 power_state_ = power_state; 165 } 166 167 void OutputConfigurator::Init(bool is_panel_fitting_enabled) { 168 if (!configure_display_) 169 return; 170 171 if (!delegate_) 172 delegate_.reset(new RealOutputConfiguratorDelegate()); 173 delegate_->SetPanelFittingEnabled(is_panel_fitting_enabled); 174 } 175 176 void OutputConfigurator::Start(uint32 background_color_argb) { 177 if (!configure_display_) 178 return; 179 180 delegate_->GrabServer(); 181 delegate_->InitXRandRExtension(&xrandr_event_base_); 182 183 std::vector<OutputSnapshot> outputs = 184 delegate_->GetOutputs(state_controller_); 185 if (outputs.size() > 1 && background_color_argb) 186 delegate_->SetBackgroundColor(background_color_argb); 187 EnterStateOrFallBackToSoftwareMirroring( 188 GetOutputState(outputs, power_state_), power_state_, outputs); 189 190 // Force the DPMS on chrome startup as the driver doesn't always detect 191 // that all displays are on when signing out. 192 delegate_->ForceDPMSOn(); 193 delegate_->UngrabServer(); 194 delegate_->SendProjectingStateToPowerManager(IsProjecting(outputs)); 195 NotifyOnDisplayChanged(); 196 } 197 198 void OutputConfigurator::Stop() { 199 configure_display_ = false; 200 } 201 202 bool OutputConfigurator::SetDisplayPower(DisplayPowerState power_state, 203 int flags) { 204 if (!configure_display_) 205 return false; 206 207 VLOG(1) << "SetDisplayPower: power_state=" 208 << DisplayPowerStateToString(power_state) << " flags=" << flags; 209 if (power_state == power_state_ && !(flags & kSetDisplayPowerForceProbe)) 210 return true; 211 212 delegate_->GrabServer(); 213 std::vector<OutputSnapshot> outputs = 214 delegate_->GetOutputs(state_controller_); 215 216 bool only_if_single_internal_display = 217 flags & kSetDisplayPowerOnlyIfSingleInternalDisplay; 218 bool single_internal_display = outputs.size() == 1 && outputs[0].is_internal; 219 if ((single_internal_display || !only_if_single_internal_display) && 220 EnterStateOrFallBackToSoftwareMirroring( 221 GetOutputState(outputs, power_state), power_state, outputs)) { 222 if (power_state != DISPLAY_POWER_ALL_OFF) { 223 // Force the DPMS on since the driver doesn't always detect that it 224 // should turn on. This is needed when coming back from idle suspend. 225 delegate_->ForceDPMSOn(); 226 } 227 } 228 229 delegate_->UngrabServer(); 230 return true; 231 } 232 233 bool OutputConfigurator::SetDisplayMode(OutputState new_state) { 234 if (!configure_display_) 235 return false; 236 237 VLOG(1) << "SetDisplayMode: state=" << OutputStateToString(new_state); 238 if (output_state_ == new_state) { 239 // Cancel software mirroring if the state is moving from 240 // STATE_DUAL_EXTENDED to STATE_DUAL_EXTENDED. 241 if (mirroring_controller_ && new_state == STATE_DUAL_EXTENDED) 242 mirroring_controller_->SetSoftwareMirroring(false); 243 NotifyOnDisplayChanged(); 244 return true; 245 } 246 247 delegate_->GrabServer(); 248 std::vector<OutputSnapshot> outputs = 249 delegate_->GetOutputs(state_controller_); 250 bool success = EnterStateOrFallBackToSoftwareMirroring( 251 new_state, power_state_, outputs); 252 delegate_->UngrabServer(); 253 254 if (success) { 255 NotifyOnDisplayChanged(); 256 } else { 257 FOR_EACH_OBSERVER( 258 Observer, observers_, OnDisplayModeChangeFailed(new_state)); 259 } 260 return success; 261 } 262 263 bool OutputConfigurator::Dispatch(const base::NativeEvent& event) { 264 if (!configure_display_) 265 return true; 266 267 if (event->type - xrandr_event_base_ == RRScreenChangeNotify) { 268 delegate_->UpdateXRandRConfiguration(event); 269 return true; 270 } 271 272 if (event->type - xrandr_event_base_ != RRNotify) 273 return true; 274 275 XEvent* xevent = static_cast<XEvent*>(event); 276 XRRNotifyEvent* notify_event = 277 reinterpret_cast<XRRNotifyEvent*>(xevent); 278 if (notify_event->subtype == RRNotify_OutputChange) { 279 XRROutputChangeNotifyEvent* output_change_event = 280 reinterpret_cast<XRROutputChangeNotifyEvent*>(xevent); 281 if ((output_change_event->connection == RR_Connected) || 282 (output_change_event->connection == RR_Disconnected)) { 283 // Connecting/Disconnecting display may generate multiple 284 // RRNotify. Defer configuring outputs to avoid 285 // grabbing X and configuring displays multiple times. 286 ScheduleConfigureOutputs(); 287 } 288 } 289 290 return true; 291 } 292 293 base::EventStatus OutputConfigurator::WillProcessEvent( 294 const base::NativeEvent& event) { 295 // XI_HierarchyChanged events are special. There is no window associated with 296 // these events. So process them directly from here. 297 if (configure_display_ && event->type == GenericEvent && 298 event->xgeneric.evtype == XI_HierarchyChanged) { 299 // Defer configuring outputs to not stall event processing. 300 // This also takes care of same event being received twice. 301 ScheduleConfigureOutputs(); 302 } 303 304 return base::EVENT_CONTINUE; 305 } 306 307 void OutputConfigurator::DidProcessEvent(const base::NativeEvent& event) { 308 } 309 310 void OutputConfigurator::AddObserver(Observer* observer) { 311 observers_.AddObserver(observer); 312 } 313 314 void OutputConfigurator::RemoveObserver(Observer* observer) { 315 observers_.RemoveObserver(observer); 316 } 317 318 void OutputConfigurator::SuspendDisplays() { 319 // If the display is off due to user inactivity and there's only a single 320 // internal display connected, switch to the all-on state before 321 // suspending. This shouldn't be very noticeable to the user since the 322 // backlight is off at this point, and doing this lets us resume directly 323 // into the "on" state, which greatly reduces resume times. 324 if (power_state_ == DISPLAY_POWER_ALL_OFF) { 325 SetDisplayPower(DISPLAY_POWER_ALL_ON, 326 kSetDisplayPowerOnlyIfSingleInternalDisplay); 327 328 // We need to make sure that the monitor configuration we just did actually 329 // completes before we return, because otherwise the X message could be 330 // racing with the HandleSuspendReadiness message. 331 delegate_->SyncWithServer(); 332 } 333 } 334 335 void OutputConfigurator::ResumeDisplays() { 336 // Force probing to ensure that we pick up any changes that were made 337 // while the system was suspended. 338 SetDisplayPower(power_state_, kSetDisplayPowerForceProbe); 339 } 340 341 void OutputConfigurator::ScheduleConfigureOutputs() { 342 if (configure_timer_.get()) { 343 configure_timer_->Reset(); 344 } else { 345 configure_timer_.reset(new base::OneShotTimer<OutputConfigurator>()); 346 configure_timer_->Start( 347 FROM_HERE, 348 base::TimeDelta::FromMilliseconds(kConfigureDelayMs), 349 this, 350 &OutputConfigurator::ConfigureOutputs); 351 } 352 } 353 354 void OutputConfigurator::ConfigureOutputs() { 355 configure_timer_.reset(); 356 357 delegate_->GrabServer(); 358 std::vector<OutputSnapshot> outputs = 359 delegate_->GetOutputs(state_controller_); 360 OutputState new_state = GetOutputState(outputs, power_state_); 361 bool success = EnterStateOrFallBackToSoftwareMirroring( 362 new_state, power_state_, outputs); 363 delegate_->UngrabServer(); 364 365 if (success) { 366 NotifyOnDisplayChanged(); 367 } else { 368 FOR_EACH_OBSERVER( 369 Observer, observers_, OnDisplayModeChangeFailed(new_state)); 370 } 371 delegate_->SendProjectingStateToPowerManager(IsProjecting(outputs)); 372 } 373 374 void OutputConfigurator::NotifyOnDisplayChanged() { 375 FOR_EACH_OBSERVER(Observer, observers_, OnDisplayModeChanged()); 376 } 377 378 bool OutputConfigurator::EnterStateOrFallBackToSoftwareMirroring( 379 OutputState output_state, 380 DisplayPowerState power_state, 381 const std::vector<OutputSnapshot>& outputs) { 382 bool success = EnterState(output_state, power_state, outputs); 383 if (mirroring_controller_) { 384 bool enable_software_mirroring = false; 385 if (!success && output_state == STATE_DUAL_MIRROR) { 386 if (output_state_ != STATE_DUAL_EXTENDED || power_state_ != power_state) 387 EnterState(STATE_DUAL_EXTENDED, power_state, outputs); 388 enable_software_mirroring = success = 389 output_state_ == STATE_DUAL_EXTENDED; 390 } 391 mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring); 392 } 393 return success; 394 } 395 396 bool OutputConfigurator::EnterState( 397 OutputState output_state, 398 DisplayPowerState power_state, 399 const std::vector<OutputSnapshot>& outputs) { 400 std::vector<bool> output_power; 401 int num_on_outputs = GetOutputPower(outputs, power_state, &output_power); 402 VLOG(1) << "EnterState: output=" << OutputStateToString(output_state) 403 << " power=" << DisplayPowerStateToString(power_state); 404 405 // Framebuffer dimensions. 406 int width = 0, height = 0; 407 std::vector<OutputSnapshot> updated_outputs = outputs; 408 409 switch (output_state) { 410 case STATE_INVALID: 411 NOTREACHED() << "Ignoring request to enter invalid state with " 412 << outputs.size() << " connected output(s)"; 413 return false; 414 case STATE_HEADLESS: 415 if (outputs.size() != 0) { 416 LOG(WARNING) << "Ignoring request to enter headless mode with " 417 << outputs.size() << " connected output(s)"; 418 return false; 419 } 420 break; 421 case STATE_SINGLE: { 422 // If there are multiple outputs connected, only one should be turned on. 423 if (outputs.size() != 1 && num_on_outputs != 1) { 424 LOG(WARNING) << "Ignoring request to enter single mode with " 425 << outputs.size() << " connected outputs and " 426 << num_on_outputs << " turned on"; 427 return false; 428 } 429 430 for (size_t i = 0; i < updated_outputs.size(); ++i) { 431 OutputSnapshot* output = &updated_outputs[i]; 432 output->x = 0; 433 output->y = 0; 434 output->current_mode = output_power[i] ? output->selected_mode : None; 435 436 if (output_power[i] || outputs.size() == 1) { 437 if (!delegate_->GetModeDetails( 438 output->selected_mode, &width, &height, NULL)) 439 return false; 440 } 441 } 442 break; 443 } 444 case STATE_DUAL_MIRROR: { 445 if (outputs.size() != 2 || (num_on_outputs != 0 && num_on_outputs != 2)) { 446 LOG(WARNING) << "Ignoring request to enter mirrored mode with " 447 << outputs.size() << " connected output(s) and " 448 << num_on_outputs << " turned on"; 449 return false; 450 } 451 452 if (!delegate_->GetModeDetails( 453 outputs[0].mirror_mode, &width, &height, NULL)) 454 return false; 455 456 for (size_t i = 0; i < outputs.size(); ++i) { 457 OutputSnapshot* output = &updated_outputs[i]; 458 output->x = 0; 459 output->y = 0; 460 output->current_mode = output_power[i] ? output->mirror_mode : None; 461 if (output->touch_device_id) { 462 // CTM needs to be calculated if aspect preserving scaling is used. 463 // Otherwise, assume it is full screen, and use identity CTM. 464 if (output->mirror_mode != output->native_mode && 465 output->is_aspect_preserving_scaling) { 466 output->transform = GetMirrorModeCTM(output); 467 mirrored_display_area_ratio_map_[output->touch_device_id] = 468 GetMirroredDisplayAreaRatio(output); 469 } 470 } 471 } 472 break; 473 } 474 case STATE_DUAL_EXTENDED: { 475 if (outputs.size() != 2 || (num_on_outputs != 0 && num_on_outputs != 2)) { 476 LOG(WARNING) << "Ignoring request to enter extended mode with " 477 << outputs.size() << " connected output(s) and " 478 << num_on_outputs << " turned on"; 479 return false; 480 } 481 482 // Pairs are [width, height] corresponding to the given output's mode. 483 std::vector<std::pair<int, int> > mode_sizes(outputs.size()); 484 485 for (size_t i = 0; i < outputs.size(); ++i) { 486 if (!delegate_->GetModeDetails(outputs[i].selected_mode, 487 &(mode_sizes[i].first), &(mode_sizes[i].second), NULL)) { 488 return false; 489 } 490 491 OutputSnapshot* output = &updated_outputs[i]; 492 output->x = 0; 493 output->y = height ? height + kVerticalGap : 0; 494 output->current_mode = output_power[i] ? output->selected_mode : None; 495 496 // Retain the full screen size even if all outputs are off so the 497 // same desktop configuration can be restored when the outputs are 498 // turned back on. 499 width = std::max<int>(width, mode_sizes[i].first); 500 height += (height ? kVerticalGap : 0) + mode_sizes[i].second; 501 } 502 503 for (size_t i = 0; i < outputs.size(); ++i) { 504 OutputSnapshot* output = &updated_outputs[i]; 505 if (output->touch_device_id) { 506 CoordinateTransformation* ctm = &(output->transform); 507 ctm->x_scale = static_cast<float>(mode_sizes[i].first) / width; 508 ctm->x_offset = static_cast<float>(output->x) / width; 509 ctm->y_scale = static_cast<float>(mode_sizes[i].second) / height; 510 ctm->y_offset = static_cast<float>(output->y) / height; 511 } 512 } 513 break; 514 } 515 } 516 517 // Finally, apply the desired changes. 518 DCHECK_EQ(outputs.size(), updated_outputs.size()); 519 if (!outputs.empty()) { 520 delegate_->CreateFrameBuffer(width, height, updated_outputs); 521 for (size_t i = 0; i < outputs.size(); ++i) { 522 const OutputSnapshot& output = updated_outputs[i]; 523 if (delegate_->ConfigureCrtc(output.crtc, output.current_mode, 524 output.output, output.x, output.y)) { 525 if (output.touch_device_id) 526 delegate_->ConfigureCTM(output.touch_device_id, output.transform); 527 } else { 528 LOG(WARNING) << "Unable to configure CRTC " << output.crtc << ":" 529 << " mode=" << output.current_mode 530 << " output=" << output.output 531 << " x=" << output.x 532 << " y=" << output.y; 533 updated_outputs[i] = outputs[i]; 534 } 535 } 536 } 537 538 output_state_ = output_state; 539 power_state_ = power_state; 540 cached_outputs_ = updated_outputs; 541 return true; 542 } 543 544 OutputState OutputConfigurator::GetOutputState( 545 const std::vector<OutputSnapshot>& outputs, 546 DisplayPowerState power_state) const { 547 int num_on_outputs = GetOutputPower(outputs, power_state, NULL); 548 switch (outputs.size()) { 549 case 0: 550 return STATE_HEADLESS; 551 case 1: 552 return STATE_SINGLE; 553 case 2: { 554 if (num_on_outputs == 1) { 555 // If only one output is currently turned on, return the "single" 556 // state so that its native mode will be used. 557 return STATE_SINGLE; 558 } else { 559 // With either both outputs on or both outputs off, use one of the 560 // dual modes. 561 std::vector<int64> display_ids; 562 for (size_t i = 0; i < outputs.size(); ++i) { 563 // If display id isn't available, switch to extended mode. 564 if (!outputs[i].has_display_id) 565 return STATE_DUAL_EXTENDED; 566 display_ids.push_back(outputs[i].display_id); 567 } 568 return state_controller_->GetStateForDisplayIds(display_ids); 569 } 570 } 571 default: 572 NOTREACHED(); 573 } 574 return STATE_INVALID; 575 } 576 577 OutputConfigurator::CoordinateTransformation 578 OutputConfigurator::GetMirrorModeCTM( 579 const OutputConfigurator::OutputSnapshot* output) { 580 CoordinateTransformation ctm; // Default to identity 581 int native_mode_width = 0, native_mode_height = 0; 582 int mirror_mode_width = 0, mirror_mode_height = 0; 583 if (!delegate_->GetModeDetails(output->native_mode, 584 &native_mode_width, &native_mode_height, NULL) || 585 !delegate_->GetModeDetails(output->mirror_mode, 586 &mirror_mode_width, &mirror_mode_height, NULL)) 587 return ctm; 588 589 if (native_mode_height == 0 || mirror_mode_height == 0 || 590 native_mode_width == 0 || mirror_mode_width == 0) 591 return ctm; 592 593 float native_mode_ar = static_cast<float>(native_mode_width) / 594 static_cast<float>(native_mode_height); 595 float mirror_mode_ar = static_cast<float>(mirror_mode_width) / 596 static_cast<float>(mirror_mode_height); 597 598 if (mirror_mode_ar > native_mode_ar) { // Letterboxing 599 ctm.x_scale = 1.0; 600 ctm.x_offset = 0.0; 601 ctm.y_scale = mirror_mode_ar / native_mode_ar; 602 ctm.y_offset = (native_mode_ar / mirror_mode_ar - 1.0) * 0.5; 603 return ctm; 604 } 605 if (native_mode_ar > mirror_mode_ar) { // Pillarboxing 606 ctm.y_scale = 1.0; 607 ctm.y_offset = 0.0; 608 ctm.x_scale = native_mode_ar / mirror_mode_ar; 609 ctm.x_offset = (mirror_mode_ar / native_mode_ar - 1.0) * 0.5; 610 return ctm; 611 } 612 613 return ctm; // Same aspect ratio - return identity 614 } 615 616 float OutputConfigurator::GetMirroredDisplayAreaRatio( 617 const OutputConfigurator::OutputSnapshot* output) { 618 float area_ratio = 1.0f; 619 int native_mode_width = 0, native_mode_height = 0; 620 int mirror_mode_width = 0, mirror_mode_height = 0; 621 if (!delegate_->GetModeDetails(output->native_mode, 622 &native_mode_width, &native_mode_height, NULL) || 623 !delegate_->GetModeDetails(output->mirror_mode, 624 &mirror_mode_width, &mirror_mode_height, NULL)) 625 return area_ratio; 626 627 if (native_mode_height == 0 || mirror_mode_height == 0 || 628 native_mode_width == 0 || mirror_mode_width == 0) 629 return area_ratio; 630 631 float width_ratio = static_cast<float>(mirror_mode_width) / 632 static_cast<float>(native_mode_width); 633 float height_ratio = static_cast<float>(mirror_mode_height) / 634 static_cast<float>(native_mode_height); 635 636 area_ratio = width_ratio * height_ratio; 637 return area_ratio; 638 } 639 640 } // namespace chromeos 641