summaryrefslogtreecommitdiffstats
path: root/chromeos/display
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-09 21:52:36 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-09 21:52:36 +0000
commitacb217edc035228253515cbc49f3f7134739e4aa (patch)
tree9d6b9db01a8c605d425432429f2eb67d77151b40 /chromeos/display
parent1e056b976eb8035c4c18fec1e2e35e27eb448e15 (diff)
downloadchromium_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.cc376
-rw-r--r--chromeos/display/output_configurator.h46
-rw-r--r--chromeos/display/output_configurator_unittest.cc569
-rw-r--r--chromeos/display/real_output_configurator_delegate.cc25
-rw-r--r--chromeos/display/real_output_configurator_delegate.h7
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);