diff options
author | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-09 21:52:36 +0000 |
---|---|---|
committer | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-09 21:52:36 +0000 |
commit | acb217edc035228253515cbc49f3f7134739e4aa (patch) | |
tree | 9d6b9db01a8c605d425432429f2eb67d77151b40 /chromeos/display | |
parent | 1e056b976eb8035c4c18fec1e2e35e27eb448e15 (diff) | |
download | chromium_src-acb217edc035228253515cbc49f3f7134739e4aa.zip chromium_src-acb217edc035228253515cbc49f3f7134739e4aa.tar.gz chromium_src-acb217edc035228253515cbc49f3f7134739e4aa.tar.bz2 |
chromeos: Add unit tests for OutputConfigurator.
This adds tests for display configuration. It also does
some further cleanup in the OutputConfigurator class (e.g.
single-monitor mode is used when two displays are connected
but one is turned off).
BUG=225242,225536
Review URL: https://chromiumcodereview.appspot.com/13947011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@193224 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos/display')
-rw-r--r-- | chromeos/display/output_configurator.cc | 376 | ||||
-rw-r--r-- | chromeos/display/output_configurator.h | 46 | ||||
-rw-r--r-- | chromeos/display/output_configurator_unittest.cc | 569 | ||||
-rw-r--r-- | chromeos/display/real_output_configurator_delegate.cc | 25 | ||||
-rw-r--r-- | chromeos/display/real_output_configurator_delegate.h | 7 |
5 files changed, 774 insertions, 249 deletions
diff --git a/chromeos/display/output_configurator.cc b/chromeos/display/output_configurator.cc index 4556442..94b603f 100644 --- a/chromeos/display/output_configurator.cc +++ b/chromeos/display/output_configurator.cc @@ -42,67 +42,28 @@ std::string DisplayPowerStateToString(DisplayPowerState state) { } } -OutputState InferCurrentState( - const std::vector<OutputConfigurator::OutputSnapshot>& outputs) { - OutputState state = STATE_INVALID; - switch (outputs.size()) { - case 0: - state = STATE_HEADLESS; - break; - case 1: - state = STATE_SINGLE; - break; - case 2: { - RRMode primary_mode = outputs[0].current_mode; - RRMode secondary_mode = outputs[1].current_mode; - - if (outputs[0].y == 0 && outputs[1].y == 0) { - // Displays in the same spot so this is either mirror or unknown. - // Note that we should handle no configured CRTC as a "wildcard" since - // that allows us to preserve mirror mode state while power is switched - // off on one display. - bool primary_mirror = (outputs[0].mirror_mode == primary_mode) || - (primary_mode == None); - bool secondary_mirror = (outputs[1].mirror_mode == secondary_mode) || - (secondary_mode == None); - if (primary_mirror && secondary_mirror) { - state = STATE_DUAL_MIRROR; - } else { - // We should never normally get into this state but it can help us - // make sense of situations where the configuration may have been - // changed for testing, etc. - state = STATE_DUAL_UNKNOWN; - } - } else { - // At this point, we expect both displays to be in native mode and tiled - // such that one is primary and another is correctly positioned as - // secondary. If any of these assumptions are false, this is an unknown - // configuration. - bool primary_native = (primary_mode == outputs[0].native_mode) || - (primary_mode == None); - bool secondary_native = (secondary_mode == outputs[1].native_mode) || - (secondary_mode == None); - if (primary_native && secondary_native) { - // Just check the relative locations. - int secondary_offset = outputs[0].height + - OutputConfigurator::kVerticalGap; - if (outputs[0].y == 0 && outputs[1].y == secondary_offset) { - state = STATE_DUAL_EXTENDED; - } else { - // Unexpected locations. - state = STATE_DUAL_UNKNOWN; - } - } else { - // Mode assumptions don't hold. - state = STATE_DUAL_UNKNOWN; - } - } - break; - } - default: - CHECK(false); +// Returns the number of outputs in |outputs| that should be turned on, per +// |state|. If |output_power| is non-NULL, it is updated to contain the +// on/off state of each corresponding entry in |outputs|. +int GetOutputPower( + const std::vector<OutputConfigurator::OutputSnapshot>& outputs, + DisplayPowerState state, + std::vector<bool>* output_power) { + int num_on_outputs = 0; + if (output_power) + output_power->resize(outputs.size()); + + for (size_t i = 0; i < outputs.size(); ++i) { + bool internal = outputs[i].is_internal; + bool on = state == DISPLAY_POWER_ALL_ON || + (state == DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && !internal) || + (state == DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal); + if (output_power) + (*output_power)[i] = on; + if (on) + num_on_outputs++; } - return state; + return num_on_outputs; } // Determine if there is an "internal" output and how many outputs are @@ -188,7 +149,6 @@ bool OutputConfigurator::IsInternalOutputName(const std::string& name) { OutputConfigurator::OutputConfigurator() : state_controller_(NULL), configure_display_(base::chromeos::IsRunningOnChromeOS()), - connected_output_count_(0), xrandr_event_base_(0), output_state_(STATE_INVALID), power_state_(DISPLAY_POWER_ALL_ON) { @@ -202,6 +162,11 @@ void OutputConfigurator::SetDelegateForTesting( configure_display_ = true; } +void OutputConfigurator::SetInitialDisplayPower(DisplayPowerState power_state) { + DCHECK_EQ(output_state_, STATE_INVALID); + power_state_ = power_state; +} + void OutputConfigurator::Init(bool is_panel_fitting_enabled, uint32 background_color_argb) { if (!configure_display_) @@ -224,26 +189,16 @@ void OutputConfigurator::Start() { return; delegate_->GrabServer(); - std::vector<OutputSnapshot> outputs = delegate_->GetOutputs(); - connected_output_count_ = outputs.size(); - - output_state_ = InferCurrentState(outputs); - // Ensure that we are in a supported state with all connected displays powered - // on. - OutputState starting_state = GetNextState(outputs); - if (output_state_ != starting_state && - EnterState(starting_state, power_state_, outputs)) { - output_state_ = starting_state; - } - bool is_projecting = IsProjecting(outputs); - delegate_->InitXRandRExtension(&xrandr_event_base_); + std::vector<OutputSnapshot> outputs = delegate_->GetOutputs(); + EnterState(GetOutputState(outputs, power_state_), power_state_, outputs); + // Force the DPMS on chrome startup as the driver doesn't always detect // that all displays are on when signing out. delegate_->ForceDPMSOn(); delegate_->UngrabServer(); - delegate_->SendProjectingStateToPowerManager(is_projecting); + delegate_->SendProjectingStateToPowerManager(IsProjecting(outputs)); } void OutputConfigurator::Stop() { @@ -262,14 +217,12 @@ bool OutputConfigurator::SetDisplayPower(DisplayPowerState power_state, delegate_->GrabServer(); std::vector<OutputSnapshot> outputs = delegate_->GetOutputs(); - connected_output_count_ = outputs.size(); bool only_if_single_internal_display = flags & kSetDisplayPowerOnlyIfSingleInternalDisplay; bool single_internal_display = outputs.size() == 1 && outputs[0].is_internal; if ((single_internal_display || !only_if_single_internal_display) && - EnterState(output_state_, power_state, outputs)) { - power_state_ = power_state; + EnterState(GetOutputState(outputs, power_state), power_state, outputs)) { if (power_state != DISPLAY_POWER_ALL_OFF) { // Force the DPMS on since the driver doesn't always detect that it // should turn on. This is needed when coming back from idle suspend. @@ -285,28 +238,21 @@ bool OutputConfigurator::SetDisplayMode(OutputState new_state) { if (!configure_display_) return false; - if (output_state_ == STATE_INVALID || - output_state_ == STATE_HEADLESS || - output_state_ == STATE_SINGLE) - return false; - if (output_state_ == new_state) return true; delegate_->GrabServer(); std::vector<OutputSnapshot> outputs = delegate_->GetOutputs(); - connected_output_count_ = outputs.size(); - if (EnterState(new_state, power_state_, outputs)) - output_state_ = new_state; + bool success = EnterState(new_state, power_state_, outputs); delegate_->UngrabServer(); - if (output_state_ == new_state) { + if (success) { NotifyOnDisplayChanged(); } else { FOR_EACH_OBSERVER( Observer, observers_, OnDisplayModeChangeFailed(new_state)); } - return true; + return success; } bool OutputConfigurator::Dispatch(const base::NativeEvent& event) { @@ -365,12 +311,12 @@ void OutputConfigurator::SuspendDisplays() { if (power_state_ == DISPLAY_POWER_ALL_OFF) { SetDisplayPower(DISPLAY_POWER_ALL_ON, kSetDisplayPowerOnlyIfSingleInternalDisplay); - } - // We need to make sure that the monitor configuration we just did actually - // completes before we return, because otherwise the X message could be - // racing with the HandleSuspendReadiness message. - delegate_->SyncWithServer(); + // We need to make sure that the monitor configuration we just did actually + // completes before we return, because otherwise the X message could be + // racing with the HandleSuspendReadiness message. + delegate_->SyncWithServer(); + } } void OutputConfigurator::ResumeDisplays() { @@ -384,26 +330,17 @@ void OutputConfigurator::ConfigureOutputs() { delegate_->GrabServer(); std::vector<OutputSnapshot> outputs = delegate_->GetOutputs(); - int new_output_count = outputs.size(); - // Don't skip even if the output counts didn't change because - // a display might have been swapped during the suspend. - connected_output_count_ = new_output_count; - OutputState new_state = GetNextState(outputs); - // When a display was swapped, the state moves from - // STATE_DUAL_EXTENDED to STATE_DUAL_EXTENDED, so don't rely on - // the state chagne to tell if it was successful. + OutputState new_state = GetOutputState(outputs, power_state_); bool success = EnterState(new_state, power_state_, outputs); - bool is_projecting = IsProjecting(outputs); delegate_->UngrabServer(); if (success) { - output_state_ = new_state; NotifyOnDisplayChanged(); } else { FOR_EACH_OBSERVER( Observer, observers_, OnDisplayModeChangeFailed(new_state)); } - delegate_->SendProjectingStateToPowerManager(is_projecting); + delegate_->SendProjectingStateToPowerManager(IsProjecting(outputs)); } void OutputConfigurator::NotifyOnDisplayChanged() { @@ -414,138 +351,171 @@ bool OutputConfigurator::EnterState( OutputState output_state, DisplayPowerState power_state, const std::vector<OutputSnapshot>& outputs) { - std::vector<bool> output_power(outputs.size()); - bool all_outputs_off = true; - - for (size_t i = 0; i < outputs.size(); ++i) { - output_power[i] = power_state == DISPLAY_POWER_ALL_ON || - (power_state == DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && - !outputs[i].is_internal) || - (power_state == DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && - outputs[i].is_internal); - if (output_power[i]) - all_outputs_off = false; - } - - switch (outputs.size()) { - case 0: - // Do nothing as no 0-display states are supported. + std::vector<bool> output_power; + int num_on_outputs = GetOutputPower(outputs, power_state, &output_power); + + switch (output_state) { + case STATE_HEADLESS: + if (outputs.size() != 0) { + LOG(WARNING) << "Ignoring request to enter headless mode with " + << outputs.size() << " connected output(s)"; + return false; + } break; - case 1: { - // Re-allocate the framebuffer to fit. + case STATE_SINGLE: { + // If there are multiple outputs connected, only one should be turned on. + if (outputs.size() != 1 && num_on_outputs != 1) { + LOG(WARNING) << "Ignoring request to enter single mode with " + << outputs.size() << " connected outputs and " + << num_on_outputs << " turned on"; + return false; + } + + // Determine which output to use. + const OutputSnapshot& output = outputs.size() == 1 ? outputs[0] : + (output_power[0] ? outputs[0] : outputs[1]); int width = 0, height = 0; - if (!delegate_->GetModeDetails( - outputs[0].native_mode, &width, &height, NULL)) { + if (!delegate_->GetModeDetails(output.native_mode, &width, &height, NULL)) return false; + + std::vector<CrtcConfig> configs(outputs.size()); + for (size_t i = 0; i < outputs.size(); ++i) { + configs[i] = CrtcConfig( + outputs[i].crtc, 0, 0, + output_power[i] ? outputs[i].native_mode : None, + outputs[i].output); } + delegate_->CreateFrameBuffer(width, height, configs); - CrtcConfig config(outputs[0].crtc, 0, 0, - output_power[0] ? outputs[0].native_mode : None, - outputs[0].output); - delegate_->CreateFrameBuffer(width, height, &config, NULL); - delegate_->ConfigureCrtc(&config); - if (outputs[0].touch_device_id) { - // Restore identity transformation for single monitor in native mode. - delegate_->ConfigureCTM(outputs[0].touch_device_id, - CoordinateTransformation()); + for (size_t i = 0; i < outputs.size(); ++i) { + delegate_->ConfigureCrtc(&configs[i]); + if (outputs[i].touch_device_id) { + delegate_->ConfigureCTM(outputs[i].touch_device_id, + CoordinateTransformation()); + } } break; } - case 2: { - if (output_state == STATE_DUAL_MIRROR) { - int width = 0, height = 0; - if (!delegate_->GetModeDetails( - outputs[0].mirror_mode, &width, &height, NULL)) { - return false; - } - - std::vector<CrtcConfig> configs(outputs.size()); - for (size_t i = 0; i < outputs.size(); ++i) { - configs[i] = CrtcConfig( - outputs[i].crtc, 0, 0, - output_power[i] ? outputs[i].mirror_mode : None, - outputs[i].output); - } + case STATE_DUAL_MIRROR: { + if (outputs.size() != 2 || (num_on_outputs != 0 && num_on_outputs != 2)) { + LOG(WARNING) << "Ignoring request to enter mirrored mode with " + << outputs.size() << " connected output(s) and " + << num_on_outputs << " turned on"; + return false; + } - delegate_->CreateFrameBuffer(width, height, &configs[0], &configs[1]); + int width = 0, height = 0; + if (!delegate_->GetModeDetails( + outputs[0].mirror_mode, &width, &height, NULL)) { + return false; + } - for (size_t i = 0; i < outputs.size(); ++i) { - delegate_->ConfigureCrtc(&configs[i]); - if (outputs[i].touch_device_id) { - CoordinateTransformation ctm; - // CTM needs to be calculated if aspect preserving scaling is used. - // Otherwise, assume it is full screen, and use identity CTM. - if (outputs[i].mirror_mode != outputs[i].native_mode && - outputs[i].is_aspect_preserving_scaling) { - ctm = GetMirrorModeCTM(&outputs[i]); - } - delegate_->ConfigureCTM(outputs[i].touch_device_id, ctm); + std::vector<CrtcConfig> configs(outputs.size()); + for (size_t i = 0; i < outputs.size(); ++i) { + configs[i] = CrtcConfig( + outputs[i].crtc, 0, 0, + output_power[i] ? outputs[i].mirror_mode : None, + outputs[i].output); + } + delegate_->CreateFrameBuffer(width, height, configs); + + for (size_t i = 0; i < outputs.size(); ++i) { + delegate_->ConfigureCrtc(&configs[i]); + if (outputs[i].touch_device_id) { + CoordinateTransformation ctm; + // CTM needs to be calculated if aspect preserving scaling is used. + // Otherwise, assume it is full screen, and use identity CTM. + if (outputs[i].mirror_mode != outputs[i].native_mode && + outputs[i].is_aspect_preserving_scaling) { + ctm = GetMirrorModeCTM(&outputs[i]); } + delegate_->ConfigureCTM(outputs[i].touch_device_id, ctm); } - } else { // STATE_DUAL_EXTENDED - // Pairs are [width, height] corresponding to the given output's mode. - std::vector<std::pair<int, int> > mode_sizes(outputs.size()); - std::vector<CrtcConfig> configs(outputs.size()); - int width = 0, height = 0; + } + break; + } + case STATE_DUAL_EXTENDED: { + if (outputs.size() != 2 || (num_on_outputs != 0 && num_on_outputs != 2)) { + LOG(WARNING) << "Ignoring request to enter extended mode with " + << outputs.size() << " connected output(s) and " + << num_on_outputs << " turned on"; + return false; + } - for (size_t i = 0; i < outputs.size(); ++i) { - if (!delegate_->GetModeDetails(outputs[i].native_mode, - &(mode_sizes[i].first), &(mode_sizes[i].second), NULL)) { - return false; - } + // Pairs are [width, height] corresponding to the given output's mode. + std::vector<std::pair<int, int> > mode_sizes(outputs.size()); + std::vector<CrtcConfig> configs(outputs.size()); + int width = 0, height = 0; - configs[i] = CrtcConfig( - outputs[i].crtc, 0, (height ? height + kVerticalGap : 0), - output_power[i] ? outputs[i].native_mode : None, - outputs[i].output); - - // Retain the full screen size if all outputs are off so the same - // desktop configuration can be restored when the outputs are - // turned back on. - if (output_power[i] || all_outputs_off) { - width = std::max<int>(width, mode_sizes[i].first); - height += (height ? kVerticalGap : 0) + mode_sizes[i].second; - } + for (size_t i = 0; i < outputs.size(); ++i) { + if (!delegate_->GetModeDetails(outputs[i].native_mode, + &(mode_sizes[i].first), &(mode_sizes[i].second), NULL)) { + return false; } - delegate_->CreateFrameBuffer(width, height, &configs[0], &configs[1]); + configs[i] = CrtcConfig( + outputs[i].crtc, 0, (height ? height + kVerticalGap : 0), + output_power[i] ? outputs[i].native_mode : None, + outputs[i].output); - for (size_t i = 0; i < outputs.size(); ++i) { - delegate_->ConfigureCrtc(&configs[i]); - if (outputs[i].touch_device_id) { - CoordinateTransformation ctm; - ctm.x_scale = static_cast<float>(mode_sizes[i].first) / width; - ctm.x_offset = static_cast<float>(configs[i].x) / width; - ctm.y_scale = static_cast<float>(mode_sizes[i].second) / height; - ctm.y_offset = static_cast<float>(configs[i].y) / height; - delegate_->ConfigureCTM(outputs[i].touch_device_id, ctm); - } + // Retain the full screen size even if all outputs are off so the + // same desktop configuration can be restored when the outputs are + // turned back on. + width = std::max<int>(width, mode_sizes[i].first); + height += (height ? kVerticalGap : 0) + mode_sizes[i].second; + } + + delegate_->CreateFrameBuffer(width, height, configs); + + for (size_t i = 0; i < outputs.size(); ++i) { + delegate_->ConfigureCrtc(&configs[i]); + if (outputs[i].touch_device_id) { + CoordinateTransformation ctm; + ctm.x_scale = static_cast<float>(mode_sizes[i].first) / width; + ctm.x_offset = static_cast<float>(configs[i].x) / width; + ctm.y_scale = static_cast<float>(mode_sizes[i].second) / height; + ctm.y_offset = static_cast<float>(configs[i].y) / height; + delegate_->ConfigureCTM(outputs[i].touch_device_id, ctm); } } break; } default: - NOTREACHED() << "Got " << outputs.size() << " outputs"; + NOTREACHED() << "Got request to enter output state " << output_state + << " with " << outputs.size() << " output(s)"; + return false; } + output_state_ = output_state; + power_state_ = power_state; return true; } -OutputState OutputConfigurator::GetNextState( - const std::vector<OutputSnapshot>& output_snapshots) const { - switch (output_snapshots.size()) { +OutputState OutputConfigurator::GetOutputState( + const std::vector<OutputSnapshot>& outputs, + DisplayPowerState power_state) const { + int num_on_outputs = GetOutputPower(outputs, power_state, NULL); + switch (outputs.size()) { case 0: return STATE_HEADLESS; case 1: return STATE_SINGLE; case 2: { - std::vector<OutputInfo> outputs; - for (size_t i = 0; i < output_snapshots.size(); ++i) { - outputs.push_back(OutputInfo()); - outputs[i].output = output_snapshots[i].output; - outputs[i].output_index = i; + if (num_on_outputs == 1) { + // If only one output is currently turned on, return the "single" + // state so that its native mode will be used. + return STATE_SINGLE; + } else { + // With either both outputs on or both outputs off, use one of the + // dual modes. + std::vector<OutputInfo> output_infos; + for (size_t i = 0; i < outputs.size(); ++i) { + output_infos.push_back(OutputInfo()); + output_infos[i].output = outputs[i].output; + output_infos[i].output_index = i; + } + return state_controller_->GetStateForOutputs(output_infos); } - return state_controller_->GetStateForOutputs(outputs); } default: NOTREACHED(); diff --git a/chromeos/display/output_configurator.h b/chromeos/display/output_configurator.h index bac694a..34e9384 100644 --- a/chromeos/display/output_configurator.h +++ b/chromeos/display/output_configurator.h @@ -32,7 +32,6 @@ enum OutputState { STATE_SINGLE, STATE_DUAL_MIRROR, STATE_DUAL_EXTENDED, - STATE_DUAL_UNKNOWN, }; // Information that is necessary to construct display id @@ -165,10 +164,10 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { // Called to set the frame buffer (underlying XRR "screen") size. Has // a side-effect of disabling all CRTCs. - virtual void CreateFrameBuffer(int width, - int height, - CrtcConfig* config1, - CrtcConfig* config2) = 0; + virtual void CreateFrameBuffer( + int width, + int height, + const std::vector<OutputConfigurator::CrtcConfig>& configs) = 0; // Configures XInput's Coordinate Transformation Matrix property. // |touch_device_id| the ID of the touchscreen device to configure. @@ -227,14 +226,8 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { OutputConfigurator(); virtual ~OutputConfigurator(); - int connected_output_count() const { return connected_output_count_; } - OutputState output_state() const { return output_state_; } - - void set_display_power_state(DisplayPowerState power_state) { - power_state_ = power_state; - } - DisplayPowerState display_power_state() const { return power_state_; } + DisplayPowerState power_state() const { return power_state_; } void set_state_controller(StateController* controller) { state_controller_ = controller; @@ -244,6 +237,9 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { // true. Should be called before Init(). void SetDelegateForTesting(scoped_ptr<Delegate> delegate); + // Sets the initial value of |power_state_|. Must be called before Start(). + void SetInitialDisplayPower(DisplayPowerState power_state); + // Initialization, must be called right after constructor. // |is_panel_fitting_enabled| indicates hardware panel fitting support. // If |background_color_argb| is non zero and there are multiple displays, @@ -264,7 +260,8 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { bool SetDisplayPower(DisplayPowerState power_state, int flags); // Force switching the display mode to |new_state|. Returns false if - // it was called in a single-head or headless mode. + // switching failed (possibly because |new_state| is invalid for the + // current set of connected outputs). bool SetDisplayMode(OutputState new_state); // Called when an RRNotify event is received. The implementation is @@ -293,19 +290,20 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { // Fires OnDisplayModeChanged() event to the observers. void NotifyOnDisplayChanged(); - // Configures X to the state specified in |output_state| and - // |power_state|. |outputs| contains information on the currently - // configured state, as well as how to apply the new state. + // Switches to the state specified in |output_state| and |power_state|. + // On success, updates |output_state_| and |power_state_| and returns true. bool EnterState(OutputState output_state, DisplayPowerState power_state, const std::vector<OutputSnapshot>& outputs); - // Returns next state. - OutputState GetNextState(const std::vector<OutputSnapshot>& outputs) const; + // Returns the output state that should be used with |outputs| connected + // while in |power_state|. + OutputState GetOutputState(const std::vector<OutputSnapshot>& outputs, + DisplayPowerState power_state) const; // Computes the relevant transformation for mirror mode. // |output| is the output on which mirror mode is being applied. - // Returns the transformation, which would be identity if computations fail. + // Returns the transformation or identity if computations fail. CoordinateTransformation GetMirrorModeCTM( const OutputConfigurator::OutputSnapshot* output); @@ -319,24 +317,20 @@ class CHROMEOS_EXPORT OutputConfigurator : public MessageLoop::Dispatcher { // configuration to immediately fail without changing the state. bool configure_display_; - // The number of outputs that are connected. - int connected_output_count_; - // The base of the event numbers used to represent XRandr events used in // decoding events regarding output add/remove. int xrandr_event_base_; - // The display state as derived from the outputs observed in |output_cache_|. - // This is used for rotating display modes. + // The current display state. OutputState output_state_; - // The current power state as set via SetDisplayPower(). + // The current power state. DisplayPowerState power_state_; ObserverList<Observer> observers_; // The timer to delay configuring outputs. See also the comments in - // |Dispatch()|. + // Dispatch(). scoped_ptr<base::OneShotTimer<OutputConfigurator> > configure_timer_; DISALLOW_COPY_AND_ASSIGN(OutputConfigurator); diff --git a/chromeos/display/output_configurator_unittest.cc b/chromeos/display/output_configurator_unittest.cc index f8d9e12..3dec5ca 100644 --- a/chromeos/display/output_configurator_unittest.cc +++ b/chromeos/display/output_configurator_unittest.cc @@ -4,13 +4,308 @@ #include "chromeos/display/output_configurator.h" +#include <cstdarg> +#include <map> #include <string> +#include <vector> +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/stringprintf.h" #include "testing/gtest/include/gtest/gtest.h" namespace chromeos { -typedef testing::Test OutputConfiguratorTest; +namespace { + +// Strings returned by TestDelegate::GetActionsAndClear() to describe various +// actions that were performed. +const char kInitXRandR[] = "init"; +const char kUpdateXRandR[] = "update"; +const char kGrab[] = "grab"; +const char kUngrab[] = "ungrab"; +const char kSync[] = "sync"; +const char kForceDPMS[] = "dpms"; +const char kProjectingOn[] = "projecting"; +const char kProjectingOff[] = "not_projecting"; + +// String returned by TestDelegate::GetActionsAndClear() if no actions were +// requested. +const char kNoActions[] = ""; + +// Returns a string describing a TestDelegate::SetBackgroundColor() call. +std::string GetBackgroundAction(uint32 color_argb) { + return base::StringPrintf("background(0x%x)", color_argb); +} + +// Returns a string describing a TestDelegate::ConfigureCrtc() call. +std::string GetCrtcAction(RRCrtc crtc, + int x, + int y, + RRMode mode, + RROutput output) { + return base::StringPrintf("crtc(crtc=%lu,x=%d,y=%d,mode=%lu,output=%lu)", + crtc, x, y, mode, output); +} + +// Returns a string describing a TestDelegate::CreateFramebuffer() call. +std::string GetFramebufferAction(int width, + int height, + RRCrtc crtc1, + RRCrtc crtc2) { + return base::StringPrintf( + "framebuffer(width=%d,height=%d,crtc1=%lu,crtc2=%lu)", + width, height, crtc1, crtc2); +} + +// Returns a string describing a TestDelegate::ConfigureCTM() call. +std::string GetCTMAction( + int device_id, + const OutputConfigurator::CoordinateTransformation& ctm) { + return base::StringPrintf("ctm(id=%d,transform=(%f,%f,%f,%f))", device_id, + ctm.x_scale, ctm.x_offset, ctm.y_scale, ctm.y_offset); +} + +// Joins a sequence of strings describing actions (e.g. kScreenDim) such +// that they can be compared against a string returned by +// TestDelegate::GetActionsAndClear(). The list of actions must be +// terminated by a NULL pointer. +std::string JoinActions(const char* action, ...) { + std::string actions; + + va_list arg_list; + va_start(arg_list, action); + while (action) { + if (!actions.empty()) + actions += ","; + actions += action; + action = va_arg(arg_list, const char*); + } + va_end(arg_list); + return actions; +} + +class TestDelegate : public OutputConfigurator::Delegate { + public: + static const int kXRandREventBase = 10; + + TestDelegate() {} + virtual ~TestDelegate() {} + + void set_outputs( + const std::vector<OutputConfigurator::OutputSnapshot>& outputs) { + outputs_ = outputs; + } + + // Returns a comma-separated string describing the actions that were + // requested since the previous call to GetActionsAndClear() (i.e. + // results are non-repeatable). + std::string GetActionsAndClear() { + std::string actions = actions_; + actions_.clear(); + return actions; + } + + // Adds a mode to be returned by GetModeDetails(). + void AddMode(RRMode mode, int width, int height, bool interlaced) { + modes_[mode] = ModeDetails(width, height, interlaced); + } + + // OutputConfigurator::Delegate overrides: + virtual void SetPanelFittingEnabled(bool enabled) OVERRIDE {} + virtual void InitXRandRExtension(int* event_base) OVERRIDE { + AppendAction(kInitXRandR); + *event_base = kXRandREventBase; + } + virtual void UpdateXRandRConfiguration( + const base::NativeEvent& event) OVERRIDE { AppendAction(kUpdateXRandR); } + virtual void GrabServer() OVERRIDE { AppendAction(kGrab); } + virtual void UngrabServer() OVERRIDE { AppendAction(kUngrab); } + virtual void SyncWithServer() OVERRIDE { AppendAction(kSync); } + virtual void SetBackgroundColor(uint32 color_argb) OVERRIDE { + AppendAction(GetBackgroundAction(color_argb)); + } + virtual void ForceDPMSOn() OVERRIDE { AppendAction(kForceDPMS); } + virtual std::vector<OutputConfigurator::OutputSnapshot> GetOutputs() + OVERRIDE { + return outputs_; + } + virtual bool GetModeDetails( + RRMode mode, + int* width, + int* height, + bool* interlaced) OVERRIDE { + std::map<RRMode, ModeDetails>::const_iterator it = modes_.find(mode); + if (it == modes_.end()) + return false; + + if (width) + *width = it->second.width; + if (height) + *height = it->second.height; + if (interlaced) + *interlaced = it->second.interlaced; + return true; + } + virtual void ConfigureCrtc(OutputConfigurator::CrtcConfig* config) OVERRIDE { + AppendAction(GetCrtcAction(config->crtc, config->x, config->y, config->mode, + config->output)); + } + virtual void CreateFrameBuffer( + int width, + int height, + const std::vector<OutputConfigurator::CrtcConfig>& configs) { + AppendAction(GetFramebufferAction(width, height, + configs.size() >= 1 ? configs[0].crtc : 0, + configs.size() >= 2 ? configs[1].crtc : 0)); + } + virtual void ConfigureCTM( + int touch_device_id, + const OutputConfigurator::CoordinateTransformation& ctm) OVERRIDE { + AppendAction(GetCTMAction(touch_device_id, ctm)); + } + virtual void SendProjectingStateToPowerManager(bool projecting) OVERRIDE { + AppendAction(projecting ? kProjectingOn : kProjectingOff); + } + + private: + struct ModeDetails { + ModeDetails() : width(0), height(0), interlaced(false) {} + ModeDetails(int width, int height, bool interlaced) + : width(width), + height(height), + interlaced(interlaced) {} + + int width; + int height; + bool interlaced; + }; + + void AppendAction(const std::string& action) { + if (!actions_.empty()) + actions_ += ","; + actions_ += action; + } + + std::map<RRMode, ModeDetails> modes_; + + // Outputs to be returned by GetOutputs(). + std::vector<OutputConfigurator::OutputSnapshot> outputs_; + + std::string actions_; + + DISALLOW_COPY_AND_ASSIGN(TestDelegate); +}; + +class TestStateController : public OutputConfigurator::StateController { + public: + TestStateController() : state_(STATE_DUAL_EXTENDED) {} + ~TestStateController() {} + + void set_state(OutputState state) { state_ = state; } + + // OutputConfigurator::StateController overrides: + virtual OutputState GetStateForOutputs( + const std::vector<OutputInfo>& outputs) const OVERRIDE { return state_; } + + private: + OutputState state_; + + DISALLOW_COPY_AND_ASSIGN(TestStateController); +}; + +class OutputConfiguratorTest : public testing::Test { + public: + OutputConfiguratorTest() + : test_api_(&configurator_, TestDelegate::kXRandREventBase) {} + virtual ~OutputConfiguratorTest() {} + + virtual void SetUp() OVERRIDE { + delegate_ = new TestDelegate(); + configurator_.SetDelegateForTesting( + scoped_ptr<OutputConfigurator::Delegate>(delegate_)); + configurator_.set_state_controller(&state_controller_); + + OutputConfigurator::OutputSnapshot* o = &outputs_[0]; + o->output = 1; + o->crtc = 10; + o->current_mode = kSmallModeId; + o->native_mode = kSmallModeId; + o->mirror_mode = kSmallModeId; + o->y = 0; + o->height = kSmallModeHeight; + o->is_internal = true; + o->is_aspect_preserving_scaling = true; + o->touch_device_id = 0; + + o = &outputs_[1]; + o->output = 2; + o->crtc = 11; + o->current_mode = kBigModeId; + o->native_mode = kBigModeId; + o->mirror_mode = kSmallModeId; + o->y = 0; + o->height = kBigModeHeight; + o->is_internal = false; + o->is_aspect_preserving_scaling = true; + o->touch_device_id = 0; + + UpdateOutputs(2); + delegate_->AddMode(kSmallModeId, kSmallModeWidth, kSmallModeHeight, false); + delegate_->AddMode(kBigModeId, kBigModeWidth, kBigModeHeight, false); + } + + protected: + // Predefined modes that can be used by outputs. + static const int kSmallModeId = 20; + static const int kSmallModeWidth = 1366; + static const int kSmallModeHeight = 768; + + static const int kBigModeId = 21; + static const int kBigModeWidth = 2560; + static const int kBigModeHeight = 1600; + + // Configures |delegate_| to return the first |num_outputs| entries from + // |outputs_|. + virtual void UpdateOutputs(size_t num_outputs) { + ASSERT_LE(num_outputs, arraysize(outputs_)); + std::vector<OutputConfigurator::OutputSnapshot> outputs; + for (size_t i = 0; i < num_outputs; ++i) + outputs.push_back(outputs_[i]); + delegate_->set_outputs(outputs); + } + + // Initializes |configurator_| with a single internal display. + virtual void InitWithSingleOutput() { + UpdateOutputs(1); + EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear()); + configurator_.Init(false, 0); + EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), + delegate_->GetActionsAndClear()); + configurator_.Start(); + EXPECT_EQ(JoinActions(kGrab, kInitXRandR, + GetFramebufferAction(kSmallModeWidth, + kSmallModeHeight, outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + kForceDPMS, kUngrab, kProjectingOff, NULL), + delegate_->GetActionsAndClear()); + } + + base::MessageLoop message_loop_; + TestStateController state_controller_; + OutputConfigurator configurator_; + TestDelegate* delegate_; // not owned + OutputConfigurator::TestApi test_api_; + + OutputConfigurator::OutputSnapshot outputs_[2]; + + private: + DISALLOW_COPY_AND_ASSIGN(OutputConfiguratorTest); +}; + +} // namespace TEST_F(OutputConfiguratorTest, IsInternalOutputName) { EXPECT_TRUE(OutputConfigurator::IsInternalOutputName("LVDS")); @@ -25,4 +320,276 @@ TEST_F(OutputConfiguratorTest, IsInternalOutputName) { EXPECT_FALSE(OutputConfigurator::IsInternalOutputName("eD")); } +TEST_F(OutputConfiguratorTest, ConnectSecondOutput) { + InitWithSingleOutput(); + + // Connect a second output and check that the configurator enters + // extended mode. + UpdateOutputs(2); + const int kDualHeight = + kSmallModeHeight + OutputConfigurator::kVerticalGap + kBigModeHeight; + state_controller_.set_state(STATE_DUAL_EXTENDED); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(true)); + EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab, + GetFramebufferAction(kBigModeWidth, kDualHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, + kSmallModeHeight + OutputConfigurator::kVerticalGap, + kBigModeId, outputs_[1].output).c_str(), + kUngrab, kProjectingOn, NULL), + delegate_->GetActionsAndClear()); + + EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR)); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId, + outputs_[1].output).c_str(), + kUngrab, NULL), + delegate_->GetActionsAndClear()); + + // Disconnect the second output. + UpdateOutputs(1); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(false)); + EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + kUngrab, kProjectingOff, NULL), + delegate_->GetActionsAndClear()); +} + +TEST_F(OutputConfiguratorTest, SetDisplayPower) { + InitWithSingleOutput(); + + UpdateOutputs(2); + state_controller_.set_state(STATE_DUAL_MIRROR); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(true)); + EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId, + outputs_[1].output).c_str(), + kUngrab, kProjectingOn, NULL), + delegate_->GetActionsAndClear()); + + // Turning off the internal display should switch the external display to + // its native mode. + configurator_.SetDisplayPower(DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kBigModeWidth, kBigModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, 0, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, kBigModeId, + outputs_[1].output).c_str(), + kForceDPMS, kUngrab, NULL), + delegate_->GetActionsAndClear()); + + // When all displays are turned off, the framebuffer should switch back + // to the mirrored size. + configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, 0, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, 0, + outputs_[1].output).c_str(), + kUngrab, NULL), + delegate_->GetActionsAndClear()); + + // Turn all displays on and check that mirroring is still used. + configurator_.SetDisplayPower(DISPLAY_POWER_ALL_ON, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId, + outputs_[1].output).c_str(), + kForceDPMS, kUngrab, NULL), + delegate_->GetActionsAndClear()); +} + +TEST_F(OutputConfiguratorTest, SuspendAndResume) { + InitWithSingleOutput(); + + // No preparation is needed before suspending when the display is already + // on. The configurator should still reprobe on resume in case a display + // was connected while suspended. + configurator_.SuspendDisplays(); + EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear()); + configurator_.ResumeDisplays(); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + kForceDPMS, kUngrab, NULL), + delegate_->GetActionsAndClear()); + + // Now turn the display off before suspending and check that the + // configurator turns it back on and syncs with the server. + configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, 0, + outputs_[0].output).c_str(), + kUngrab, NULL), + delegate_->GetActionsAndClear()); + + configurator_.SuspendDisplays(); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + kForceDPMS, kUngrab, kSync, NULL), + delegate_->GetActionsAndClear()); + + configurator_.ResumeDisplays(); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + kForceDPMS, kUngrab, NULL), + delegate_->GetActionsAndClear()); + + // If a second, external display is connected, the displays shouldn't be + // powered back on before suspending. + UpdateOutputs(2); + state_controller_.set_state(STATE_DUAL_MIRROR); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(true)); + EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId, + outputs_[1].output).c_str(), + kUngrab, kProjectingOn, NULL), + delegate_->GetActionsAndClear()); + + configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, 0, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, 0, + outputs_[1].output).c_str(), + kUngrab, NULL), + delegate_->GetActionsAndClear()); + + configurator_.SuspendDisplays(); + EXPECT_EQ(JoinActions(kGrab, kUngrab, kSync, NULL), + delegate_->GetActionsAndClear()); + + // If a display is disconnected while resuming, the configurator should + // pick up the change. + UpdateOutputs(1); + configurator_.ResumeDisplays(); + EXPECT_EQ(JoinActions(kGrab, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, 0, + outputs_[0].output).c_str(), + kUngrab, NULL), + delegate_->GetActionsAndClear()); +} + +TEST_F(OutputConfiguratorTest, Headless) { + UpdateOutputs(0); + EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear()); + configurator_.Init(false, 0); + EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), delegate_->GetActionsAndClear()); + configurator_.Start(); + EXPECT_EQ(JoinActions(kGrab, kInitXRandR, kForceDPMS, kUngrab, + kProjectingOff, NULL), + delegate_->GetActionsAndClear()); + + // Not much should happen when the display power state is changed while + // no displays are connected. + configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), delegate_->GetActionsAndClear()); + configurator_.SetDisplayPower(DISPLAY_POWER_ALL_ON, + OutputConfigurator::kSetDisplayPowerNoFlags); + EXPECT_EQ(JoinActions(kGrab, kForceDPMS, kUngrab, NULL), + delegate_->GetActionsAndClear()); + + // Connect an external display and check that it's configured correctly. + outputs_[0].is_internal = false; + outputs_[0].native_mode = kBigModeId; + UpdateOutputs(1); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(true)); + EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab, + GetFramebufferAction(kBigModeWidth, kBigModeHeight, + outputs_[0].crtc, 0).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kBigModeId, + outputs_[0].output).c_str(), + kUngrab, kProjectingOff, NULL), + delegate_->GetActionsAndClear()); +} + +TEST_F(OutputConfiguratorTest, StartWithTwoOutputs) { + UpdateOutputs(2); + EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear()); + configurator_.Init(false, 0); + EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), delegate_->GetActionsAndClear()); + + state_controller_.set_state(STATE_DUAL_MIRROR); + configurator_.Start(); + EXPECT_EQ(JoinActions(kGrab, kInitXRandR, + GetFramebufferAction(kSmallModeWidth, kSmallModeHeight, + outputs_[0].crtc, outputs_[1].crtc).c_str(), + GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId, + outputs_[0].output).c_str(), + GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId, + outputs_[1].output).c_str(), + kForceDPMS, kUngrab, kProjectingOn, NULL), + delegate_->GetActionsAndClear()); +} + +TEST_F(OutputConfiguratorTest, InvalidOutputStates) { + UpdateOutputs(0); + EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear()); + configurator_.Init(false, 0); + configurator_.Start(); + EXPECT_TRUE(configurator_.SetDisplayMode(STATE_HEADLESS)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_SINGLE)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED)); + + UpdateOutputs(1); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(true)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_HEADLESS)); + EXPECT_TRUE(configurator_.SetDisplayMode(STATE_SINGLE)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED)); + + UpdateOutputs(2); + state_controller_.set_state(STATE_DUAL_EXTENDED); + EXPECT_TRUE(test_api_.SendOutputChangeEvents(true)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_HEADLESS)); + EXPECT_FALSE(configurator_.SetDisplayMode(STATE_SINGLE)); + EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR)); + EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED)); +} + } // namespace chromeos diff --git a/chromeos/display/real_output_configurator_delegate.cc b/chromeos/display/real_output_configurator_delegate.cc index c94a262..4a6167c 100644 --- a/chromeos/display/real_output_configurator_delegate.cc +++ b/chromeos/display/real_output_configurator_delegate.cc @@ -262,8 +262,7 @@ void RealOutputConfiguratorDelegate::ConfigureCrtc( void RealOutputConfiguratorDelegate::CreateFrameBuffer( int width, int height, - OutputConfigurator::CrtcConfig* config1, - OutputConfigurator::CrtcConfig* config2) { + const std::vector<OutputConfigurator::CrtcConfig>& configs) { CHECK(screen_) << "Server not grabbed"; int current_width = DisplayWidth(display_, DefaultScreen(display_)); int current_height = DisplayHeight(display_, DefaultScreen(display_)); @@ -272,7 +271,7 @@ void RealOutputConfiguratorDelegate::CreateFrameBuffer( if (width == current_width && height == current_height) return; - DestroyUnusedCrtcs(config1, config2); + DestroyUnusedCrtcs(configs); int mm_width = width * kPixelsToMmScale; int mm_height = height * kPixelsToMmScale; XRRSetScreenSize(display_, window_, width, height, mm_width, mm_height); @@ -323,8 +322,7 @@ void RealOutputConfiguratorDelegate::SendProjectingStateToPowerManager( } void RealOutputConfiguratorDelegate::DestroyUnusedCrtcs( - OutputConfigurator::CrtcConfig* config1, - OutputConfigurator::CrtcConfig* config2) { + const std::vector<OutputConfigurator::CrtcConfig>& configs) { CHECK(screen_) << "Server not grabbed"; // Setting the screen size will fail if any CRTC doesn't fit afterwards. // At the same time, turning CRTCs off and back on uses up a lot of time. @@ -341,16 +339,13 @@ void RealOutputConfiguratorDelegate::DestroyUnusedCrtcs( // Default config is to disable the crtcs. OutputConfigurator::CrtcConfig config( screen_->crtcs[i], 0, 0, None, None); - - // If we are going to use that CRTC later, prepare it now. - if (config1 && screen_->crtcs[i] == config1->crtc) { - config = *config1; - config.x = 0; - config.y = 0; - } else if (config2 && screen_->crtcs[i] == config2->crtc) { - config = *config2; - config.x = 0; - config.y = 0; + for (std::vector<OutputConfigurator::CrtcConfig>::const_iterator it = + configs.begin(); it != configs.end(); ++it) { + if (config.crtc == it->crtc) { + config.mode = it->mode; + config.output = it->output; + break; + } } if (config.mode != None) { diff --git a/chromeos/display/real_output_configurator_delegate.h b/chromeos/display/real_output_configurator_delegate.h index b06f77d..ba0b248 100644 --- a/chromeos/display/real_output_configurator_delegate.h +++ b/chromeos/display/real_output_configurator_delegate.h @@ -47,8 +47,7 @@ class RealOutputConfiguratorDelegate : public OutputConfigurator::Delegate { virtual void CreateFrameBuffer( int width, int height, - OutputConfigurator::CrtcConfig* config1, - OutputConfigurator::CrtcConfig* config2) OVERRIDE; + const std::vector<OutputConfigurator::CrtcConfig>& configs) OVERRIDE; virtual void ConfigureCTM( int touch_device_id, const OutputConfigurator::CoordinateTransformation& ctm) OVERRIDE; @@ -58,8 +57,8 @@ class RealOutputConfiguratorDelegate : public OutputConfigurator::Delegate { // Destroys unused CRTCs and parks used CRTCs in a way which allows a // framebuffer resize. This is faster than turning them off, resizing, // then turning them back on. - void DestroyUnusedCrtcs(OutputConfigurator::CrtcConfig* config1, - OutputConfigurator::CrtcConfig* config2); + void DestroyUnusedCrtcs( + const std::vector<OutputConfigurator::CrtcConfig>& configs); // Returns whether |id| is configured to preserve aspect when scaling. bool IsOutputAspectPreservingScaling(RROutput id); |