Home | History | Annotate | Download | only in display
      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(&notify_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>(&notify_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