summaryrefslogtreecommitdiffstats
path: root/chromeos
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-14 03:55:04 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-14 03:55:04 +0000
commitedc1c808459bb3c721d0f1f73196b56602ace426 (patch)
tree0af2ea2d7bd327b8a476beb7fbd3ee5dc815769a /chromeos
parent82c91907cc8e62f9681daf7c937578b7033390d0 (diff)
downloadchromium_src-edc1c808459bb3c721d0f1f73196b56602ace426.zip
chromium_src-edc1c808459bb3c721d0f1f73196b56602ace426.tar.gz
chromium_src-edc1c808459bb3c721d0f1f73196b56602ace426.tar.bz2
chromeos: Ignore XRandR events about known changes.
This updates OutputConfigurator to avoid reconfiguring displays in response to RRNotify events describing already-known output states. Doing so avoids an extra XRRGetScreenResources() call that blocks the UI thread for 60 ms during startup. BUG=254667 Review URL: https://chromiumcodereview.appspot.com/22871002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@217461 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos')
-rw-r--r--chromeos/display/output_configurator.cc97
-rw-r--r--chromeos/display/output_configurator.h16
-rw-r--r--chromeos/display/output_configurator_unittest.cc197
3 files changed, 229 insertions, 81 deletions
diff --git a/chromeos/display/output_configurator.cc b/chromeos/display/output_configurator.cc
index a8c1681..be84741 100644
--- a/chromeos/display/output_configurator.cc
+++ b/chromeos/display/output_configurator.cc
@@ -119,28 +119,35 @@ OutputConfigurator::OutputSnapshot::OutputSnapshot()
display_id(0),
has_display_id(false) {}
-bool OutputConfigurator::TestApi::SendOutputChangeEvents(bool connected) {
- XRRScreenChangeNotifyEvent screen_event;
- memset(&screen_event, 0, sizeof(screen_event));
- screen_event.type = xrandr_event_base_ + RRScreenChangeNotify;
- configurator_->Dispatch(
- reinterpret_cast<const base::NativeEvent>(&screen_event));
-
- XRROutputChangeNotifyEvent notify_event;
- memset(&notify_event, 0, sizeof(notify_event));
- notify_event.type = xrandr_event_base_ + RRNotify;
- notify_event.subtype = RRNotify_OutputChange;
- notify_event.connection = connected ? RR_Connected : RR_Disconnected;
- configurator_->Dispatch(
- reinterpret_cast<const base::NativeEvent>(&notify_event));
-
- if (!configurator_->configure_timer_->IsRunning()) {
- LOG(ERROR) << "ConfigureOutputs() timer not running";
+void OutputConfigurator::TestApi::SendScreenChangeEvent() {
+ XRRScreenChangeNotifyEvent event = {0};
+ event.type = xrandr_event_base_ + RRScreenChangeNotify;
+ configurator_->Dispatch(reinterpret_cast<const base::NativeEvent>(&event));
+}
+
+void OutputConfigurator::TestApi::SendOutputChangeEvent(RROutput output,
+ RRCrtc crtc,
+ RRMode mode,
+ bool connected) {
+ XRROutputChangeNotifyEvent event = {0};
+ event.type = xrandr_event_base_ + RRNotify;
+ event.subtype = RRNotify_OutputChange;
+ event.output = output;
+ event.crtc = crtc;
+ event.mode = mode;
+ event.connection = connected ? RR_Connected : RR_Disconnected;
+ configurator_->Dispatch(reinterpret_cast<const base::NativeEvent>(&event));
+}
+
+bool OutputConfigurator::TestApi::TriggerConfigureTimeout() {
+ if (configurator_->configure_timer_.get() &&
+ configurator_->configure_timer_->IsRunning()) {
+ configurator_->configure_timer_.reset();
+ configurator_->ConfigureOutputs();
+ return true;
+ } else {
return false;
}
-
- configurator_->ConfigureOutputs();
- return true;
}
OutputConfigurator::OutputConfigurator()
@@ -265,28 +272,54 @@ bool OutputConfigurator::Dispatch(const base::NativeEvent& event) {
return true;
if (event->type - xrandr_event_base_ == RRScreenChangeNotify) {
+ VLOG(1) << "Received RRScreenChangeNotify event";
delegate_->UpdateXRandRConfiguration(event);
return true;
}
+ // Bail out early for everything except RRNotify_OutputChange events
+ // about an output getting connected or disconnected.
if (event->type - xrandr_event_base_ != RRNotify)
return true;
+ const XRRNotifyEvent* notify_event = reinterpret_cast<XRRNotifyEvent*>(event);
+ if (notify_event->subtype != RRNotify_OutputChange)
+ return true;
+ const XRROutputChangeNotifyEvent* output_change_event =
+ reinterpret_cast<XRROutputChangeNotifyEvent*>(event);
+ const int action = output_change_event->connection;
+ if (action != RR_Connected && action != RR_Disconnected)
+ return true;
- XEvent* xevent = static_cast<XEvent*>(event);
- XRRNotifyEvent* notify_event =
- reinterpret_cast<XRRNotifyEvent*>(xevent);
- if (notify_event->subtype == RRNotify_OutputChange) {
- XRROutputChangeNotifyEvent* output_change_event =
- reinterpret_cast<XRROutputChangeNotifyEvent*>(xevent);
- if ((output_change_event->connection == RR_Connected) ||
- (output_change_event->connection == RR_Disconnected)) {
- // Connecting/Disconnecting display may generate multiple
- // RRNotify. Defer configuring outputs to avoid
- // grabbing X and configuring displays multiple times.
- ScheduleConfigureOutputs();
+ const bool connected = (action == RR_Connected);
+ VLOG(1) << "Received RRNotify_OutputChange event:"
+ << " output=" << output_change_event->output
+ << " crtc=" << output_change_event->crtc
+ << " mode=" << output_change_event->mode
+ << " action=" << (connected ? "connected" : "disconnected");
+
+ bool found_changed_output = false;
+ for (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin();
+ it != cached_outputs_.end(); ++it) {
+ if (it->output == output_change_event->output) {
+ if (connected && it->crtc == output_change_event->crtc &&
+ it->current_mode == output_change_event->mode) {
+ VLOG(1) << "Ignoring event describing already-cached state";
+ return true;
+ }
+ found_changed_output = true;
+ break;
}
}
+ if (!connected && !found_changed_output) {
+ VLOG(1) << "Ignoring event describing already-disconnected output";
+ return true;
+ }
+
+ // Connecting/disconnecting a display may generate multiple events. Defer
+ // configuring outputs to avoid grabbing X and configuring displays
+ // multiple times.
+ ScheduleConfigureOutputs();
return true;
}
diff --git a/chromeos/display/output_configurator.h b/chromeos/display/output_configurator.h
index fbc7804..b9d7faf 100644
--- a/chromeos/display/output_configurator.h
+++ b/chromeos/display/output_configurator.h
@@ -211,10 +211,18 @@ class CHROMEOS_EXPORT OutputConfigurator
xrandr_event_base_(xrandr_event_base) {}
~TestApi() {}
- // Dispatches RRScreenChangeNotify and RRNotify_OutputChange events to
- // |configurator_| and runs ConfigureOutputs(). Returns false if
- // |configure_timer_| wasn't started.
- bool SendOutputChangeEvents(bool connected);
+ // Dispatches an RRScreenChangeNotify event to |configurator_|.
+ void SendScreenChangeEvent();
+
+ // Dispatches an RRNotify_OutputChange event to |configurator_|.
+ void SendOutputChangeEvent(RROutput output,
+ RRCrtc crtc,
+ RRMode mode,
+ bool connected);
+
+ // If |configure_timer_| is started, stops the timer, runs
+ // ConfigureOutputs(), and returns true; returns false otherwise.
+ bool TriggerConfigureTimeout();
private:
OutputConfigurator* configurator_; // not owned
diff --git a/chromeos/display/output_configurator_unittest.cc b/chromeos/display/output_configurator_unittest.cc
index 96386f5..6769e51 100644
--- a/chromeos/display/output_configurator_unittest.cc
+++ b/chromeos/display/output_configurator_unittest.cc
@@ -90,14 +90,21 @@ class TestDelegate : public OutputConfigurator::Delegate {
public:
static const int kXRandREventBase = 10;
- TestDelegate() {}
+ TestDelegate() : configure_crtc_result_(true) {}
virtual ~TestDelegate() {}
+ const std::vector<OutputConfigurator::OutputSnapshot>& outputs() const {
+ return outputs_;
+ }
void set_outputs(
const std::vector<OutputConfigurator::OutputSnapshot>& outputs) {
outputs_ = outputs;
}
+ void set_configure_crtc_result(bool result) {
+ configure_crtc_result_ = result;
+ }
+
// Returns a comma-separated string describing the actions that were
// requested since the previous call to GetActionsAndClear() (i.e.
// results are non-repeatable).
@@ -154,7 +161,7 @@ class TestDelegate : public OutputConfigurator::Delegate {
int x,
int y) OVERRIDE {
AppendAction(GetCrtcAction(crtc, x, y, mode, output));
- return true;
+ return configure_crtc_result_;
}
virtual void CreateFrameBuffer(
int width,
@@ -201,6 +208,9 @@ class TestDelegate : public OutputConfigurator::Delegate {
std::string actions_;
+ // Return value returned by ConfigureCrtc().
+ bool configure_crtc_result_;
+
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
@@ -288,7 +298,7 @@ class OutputConfiguratorTest : public testing::Test {
o->touch_device_id = 0;
o->has_display_id = true;
- UpdateOutputs(2);
+ UpdateOutputs(2, false);
delegate_->AddMode(kSmallModeId, kSmallModeWidth, kSmallModeHeight, false);
delegate_->AddMode(kBigModeId, kBigModeWidth, kBigModeHeight, false);
}
@@ -308,18 +318,31 @@ class OutputConfiguratorTest : public testing::Test {
static const int kBigModeHeight = 1600;
// Configures |delegate_| to return the first |num_outputs| entries from
- // |outputs_|.
- virtual void UpdateOutputs(size_t num_outputs) {
+ // |outputs_|. If |send_events| is true, also sends screen-change and
+ // output-change events to |configurator_| and triggers the configure
+ // timeout if one was scheduled.
+ void UpdateOutputs(size_t num_outputs, bool send_events) {
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);
+
+ if (send_events) {
+ test_api_.SendScreenChangeEvent();
+ for (size_t i = 0; i < arraysize(outputs_); ++i) {
+ const OutputConfigurator::OutputSnapshot output = outputs_[i];
+ bool connected = i < num_outputs;
+ test_api_.SendOutputChangeEvent(
+ output.output, output.crtc, output.current_mode, connected);
+ }
+ test_api_.TriggerConfigureTimeout();
+ }
}
// Initializes |configurator_| with a single internal display.
- virtual void InitWithSingleOutput() {
- UpdateOutputs(1);
+ void InitWithSingleOutput() {
+ UpdateOutputs(1, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
@@ -353,11 +376,10 @@ TEST_F(OutputConfiguratorTest, ConnectSecondOutput) {
// Connect a second output and check that the configurator enters
// extended mode.
- UpdateOutputs(2);
+ state_controller_.set_state(STATE_DUAL_EXTENDED);
+ UpdateOutputs(2, true);
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(),
@@ -383,8 +405,7 @@ TEST_F(OutputConfiguratorTest, ConnectSecondOutput) {
EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
// Disconnect the second output.
- UpdateOutputs(1);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(false));
+ UpdateOutputs(1, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
@@ -396,9 +417,8 @@ TEST_F(OutputConfiguratorTest, ConnectSecondOutput) {
// Software Mirroring
DisableNativeMirroring();
- UpdateOutputs(2);
state_controller_.set_state(STATE_DUAL_EXTENDED);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(true));
+ UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
@@ -430,8 +450,7 @@ TEST_F(OutputConfiguratorTest, ConnectSecondOutput) {
EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
// Disconnect the second output.
- UpdateOutputs(1);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(false));
+ UpdateOutputs(1, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
@@ -445,9 +464,8 @@ TEST_F(OutputConfiguratorTest, ConnectSecondOutput) {
TEST_F(OutputConfiguratorTest, SetDisplayPower) {
InitWithSingleOutput();
- UpdateOutputs(2);
state_controller_.set_state(STATE_DUAL_MIRROR);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(true));
+ UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
@@ -507,14 +525,10 @@ TEST_F(OutputConfiguratorTest, SetDisplayPower) {
// Software Mirroring
DisableNativeMirroring();
- UpdateOutputs(2);
-
+ state_controller_.set_state(STATE_DUAL_MIRROR);
+ UpdateOutputs(2, true);
const int kDualHeight =
kSmallModeHeight + OutputConfigurator::kVerticalGap + kBigModeHeight;
-
- state_controller_.set_state(STATE_DUAL_MIRROR);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(true));
- // Move to extended
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
@@ -628,9 +642,8 @@ TEST_F(OutputConfiguratorTest, SuspendAndResume) {
// 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));
+ UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
@@ -657,9 +670,9 @@ TEST_F(OutputConfiguratorTest, SuspendAndResume) {
EXPECT_EQ(JoinActions(kGrab, kUngrab, kSync, NULL),
delegate_->GetActionsAndClear());
- // If a display is disconnected while resuming, the configurator should
+ // If a display is disconnected while suspended, the configurator should
// pick up the change.
- UpdateOutputs(1);
+ UpdateOutputs(1, false);
configurator_.ResumeDisplays();
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
@@ -671,7 +684,7 @@ TEST_F(OutputConfiguratorTest, SuspendAndResume) {
}
TEST_F(OutputConfiguratorTest, Headless) {
- UpdateOutputs(0);
+ UpdateOutputs(0, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
@@ -694,8 +707,7 @@ TEST_F(OutputConfiguratorTest, Headless) {
outputs_[0].is_internal = false;
outputs_[0].native_mode = kBigModeId;
outputs_[0].selected_mode = kBigModeId;
- UpdateOutputs(1);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(true));
+ UpdateOutputs(1, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kBigModeHeight,
outputs_[0].crtc, 0).c_str(),
@@ -706,7 +718,7 @@ TEST_F(OutputConfiguratorTest, Headless) {
}
TEST_F(OutputConfiguratorTest, StartWithTwoOutputs) {
- UpdateOutputs(2);
+ UpdateOutputs(2, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
@@ -725,7 +737,7 @@ TEST_F(OutputConfiguratorTest, StartWithTwoOutputs) {
}
TEST_F(OutputConfiguratorTest, InvalidOutputStates) {
- UpdateOutputs(0);
+ UpdateOutputs(0, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
configurator_.Start(0);
@@ -734,37 +746,132 @@ TEST_F(OutputConfiguratorTest, InvalidOutputStates) {
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED));
- UpdateOutputs(1);
- EXPECT_TRUE(test_api_.SendOutputChangeEvents(true));
+ UpdateOutputs(1, 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));
+ UpdateOutputs(2, 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));
}
-TEST_F(OutputConfiguratorTest, GetOutputStateForDisplays) {
+TEST_F(OutputConfiguratorTest, GetOutputStateForDisplaysWithoutId) {
outputs_[0].has_display_id = false;
- UpdateOutputs(2);
-
+ UpdateOutputs(2, false);
configurator_.Init(false);
- configurator_.Start(0);
-
state_controller_.set_state(STATE_DUAL_MIRROR);
- test_api_.SendOutputChangeEvents(true);
+ configurator_.Start(0);
EXPECT_EQ(STATE_DUAL_EXTENDED, configurator_.output_state());
+}
+TEST_F(OutputConfiguratorTest, GetOutputStateForDisplaysWithId) {
outputs_[0].has_display_id = true;
- UpdateOutputs(2);
- test_api_.SendOutputChangeEvents(true);
+ UpdateOutputs(2, false);
+ configurator_.Init(false);
+ state_controller_.set_state(STATE_DUAL_MIRROR);
+ configurator_.Start(0);
EXPECT_EQ(STATE_DUAL_MIRROR, configurator_.output_state());
}
+TEST_F(OutputConfiguratorTest, AvoidUnnecessaryProbes) {
+ InitWithSingleOutput();
+
+ // X sends several events just after the configurator starts. Check that
+ // the output change events don't trigger an additional probe, which can
+ // block the UI thread.
+ test_api_.SendScreenChangeEvent();
+ EXPECT_EQ(kUpdateXRandR, delegate_->GetActionsAndClear());
+
+ test_api_.SendOutputChangeEvent(
+ outputs_[0].output, outputs_[0].crtc, outputs_[0].current_mode, true);
+ test_api_.SendOutputChangeEvent(
+ outputs_[1].output, outputs_[1].crtc, outputs_[1].current_mode, false);
+ EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
+ EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
+
+ // Send an event stating that the second output is connected and check
+ // that it gets updated.
+ state_controller_.set_state(STATE_DUAL_MIRROR);
+ UpdateOutputs(2, false);
+ test_api_.SendOutputChangeEvent(
+ outputs_[1].output, outputs_[1].crtc, outputs_[1].current_mode, true);
+ EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
+ 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, kProjectingOn, NULL),
+ delegate_->GetActionsAndClear());
+
+ // An event about the second output changing modes should trigger another
+ // reconfigure.
+ test_api_.SendOutputChangeEvent(
+ outputs_[1].output, outputs_[1].crtc, outputs_[1].native_mode, true);
+ EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
+ 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, kProjectingOn, NULL),
+ delegate_->GetActionsAndClear());
+
+ // Disconnect the second output.
+ UpdateOutputs(1, true);
+ 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());
+
+ // An additional event about the second output being disconnected should
+ // be ignored.
+ test_api_.SendOutputChangeEvent(
+ outputs_[1].output, outputs_[1].crtc, outputs_[1].current_mode, false);
+ EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
+ EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
+
+ // Tell the delegate to report failure, which should result in the
+ // second output sticking with its native mode.
+ delegate_->set_configure_crtc_result(false);
+ UpdateOutputs(2, 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());
+
+ // An change event reporting a mode change on the second output should
+ // trigger another reconfigure.
+ delegate_->set_configure_crtc_result(true);
+ test_api_.SendOutputChangeEvent(
+ outputs_[1].output, outputs_[1].crtc, outputs_[1].mirror_mode, true);
+ EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
+ 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, kProjectingOn, NULL),
+ delegate_->GetActionsAndClear());
+}
+
} // namespace chromeos