monitor_reconfig: Handle external monitors and add tests.
This splits the resolution-choosing logic out into its own
class to make it easier to test. It also adds some
probably-unreliable heuristics to try to guess when the user
would rather that we just use the external output's maximum
resolution instead of trying to find a resolution that'll
also work on the built-in output.
BUG=chromium-os:2933
TEST=added a bunch
Review URL: http://codereview.chromium.org/3304011
diff --git a/Makefile b/Makefile
index d62f0f6..3ef81f1 100644
--- a/Makefile
+++ b/Makefile
@@ -7,21 +7,28 @@
PKG_CONFIG ?= pkg-config
LIBS = -lbase -lpthread -lrt
+TEST_LIBS = -lgtest -lgflags
INCLUDE_DIRS = -I.. $(shell $(PKG_CONFIG) --cflags x11 xrandr)
LIB_DIRS = $(shell $(PKG_CONFIG) --libs x11 xrandr)
BIN=monitor_reconfigure
-OBJECTS=monitor_reconfigure_main.o
+OBJECTS=monitor_reconfigure_main.o resolution_selector.o
.cc.o:
$(CXX) $(CXXFLAGS) $(INCLUDE_DIRS) -c $< -o $@
$(BIN): $(OBJECTS)
$(CXX) $(CXXFLAGS) $(INCLUDE_DIRS) $(LIB_DIRS) $^ $(LIBS) $(LDFLAGS) \
- -o $@
+ -o $@
all: $(BIN)
+test: resolution_selector_test
+
+resolution_selector_test: resolution_selector_test.cc resolution_selector.o
+ $(CXX) $(CXXFLAGS) $(INCLUDE_DIRS) $(LIB_DIRS) $^ \
+ $(LIBS) $(TEST_LIBS) $(LDFLAGS) -o $@
+
clean:
- @rm -f $(BIN) $(OBJECTS)
+ @rm -f $(BIN) $(OBJECTS) resolution_selector_test
diff --git a/monitor_reconfigure_main.cc b/monitor_reconfigure_main.cc
index 4a8b011..7d96cfb 100644
--- a/monitor_reconfigure_main.cc
+++ b/monitor_reconfigure_main.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -18,6 +18,8 @@
namespace monitor_reconfig {
+static const char kLcdOutputName[] = "LVDS1";
+
MonitorReconfigureMain::MonitorReconfigureMain(Display* display,
XRRScreenResources* screen_info)
: display_(display),
@@ -31,13 +33,38 @@
DetermineOutputs();
}
+void MonitorReconfigureMain::Run() {
+ vector<ResolutionSelector::Mode> lcd_modes;
+ SortModesByResolution(*lcd_output_, &lcd_modes);
+ DCHECK(!lcd_modes.empty());
+
+ vector<ResolutionSelector::Mode> external_modes;
+ if (IsExternalMonitorConnected()) {
+ SortModesByResolution(*external_output_, &external_modes);
+ DCHECK(!external_modes.empty());
+ }
+
+ ResolutionSelector selector;
+ string lcd_resolution, external_resolution, screen_resolution;
+ CHECK(selector.FindBestResolutions(lcd_modes,
+ external_modes,
+ &lcd_resolution,
+ &external_resolution,
+ &screen_resolution));
+
+ SetDeviceResolution(lcd_output_->name, lcd_resolution);
+ if (IsExternalMonitorConnected())
+ SetDeviceResolution(external_output_->name, external_resolution);
+ SetScreenResolution(screen_resolution);
+}
+
void MonitorReconfigureMain::DetermineOutputs() {
+ CHECK(screen_info_->noutput > 1) << "Expected at least two outputs";
XRROutputInfo* first_output =
XRRGetOutputInfo(display_, screen_info_, screen_info_->outputs[0]);
XRROutputInfo* second_output =
XRRGetOutputInfo(display_, screen_info_, screen_info_->outputs[1]);
- static const char* kLcdOutputName = "LVDS1";
if (strcmp(first_output->name, kLcdOutputName) == 0) {
lcd_output_ = first_output;
external_output_ = second_output;
@@ -64,67 +91,19 @@
}
void MonitorReconfigureMain::SortModesByResolution(
- const XRROutputInfo& output_info, vector<XRRModeInfo*>* modes_out) {
+ const XRROutputInfo& output_info,
+ vector<ResolutionSelector::Mode>* modes_out) {
modes_out->clear();
- for (int i = 0; i < output_info.nmode; ++i)
- modes_out->push_back(mode_map_[output_info.modes[i]]);
- sort(modes_out->begin(), modes_out->end(), ModeResolutionComparator());
-}
-bool MonitorReconfigureMain::FindBestResolutions(
- string* lcd_resolution,
- string* external_resolution,
- string* screen_resolution) {
- DCHECK(lcd_resolution);
- DCHECK(external_resolution);
- DCHECK(screen_resolution);
-
- vector<XRRModeInfo*> lcd_modes, external_modes;
- SortModesByResolution(*lcd_output_, &lcd_modes);
- SortModesByResolution(*external_output_, &external_modes);
- DCHECK(!lcd_modes.empty());
- DCHECK(!external_modes.empty());
-
- if ((lcd_modes[0]->width * lcd_modes[0]->height) >=
- (external_modes[0]->width * external_modes[0]->height)) {
- return FindNearestResolutions(
- lcd_modes, external_modes,
- lcd_resolution, external_resolution, screen_resolution);
- } else {
- return FindNearestResolutions(
- external_modes, lcd_modes,
- external_resolution, lcd_resolution, screen_resolution);
- }
-}
-
-bool MonitorReconfigureMain::FindNearestResolutions(
- const vector<XRRModeInfo*>& larger_device_modes,
- const vector<XRRModeInfo*>& smaller_device_modes,
- string* larger_resolution,
- string* smaller_resolution,
- string* screen_resolution) {
- DCHECK(larger_resolution);
- DCHECK(smaller_resolution);
-
- // Start with the best that the smaller device has to offer.
- smaller_resolution->assign(smaller_device_modes[0]->name);
- *screen_resolution = *smaller_resolution;
- int smaller_width = smaller_device_modes[0]->width;
- int smaller_height = smaller_device_modes[0]->height;
-
- for (vector<XRRModeInfo*>::const_reverse_iterator it =
- larger_device_modes.rbegin();
- it != larger_device_modes.rend(); ++it) {
- if ((*it)->width >= smaller_width && (*it)->height >= smaller_height) {
- larger_resolution->assign((*it)->name);
- return true;
- }
+ for (int i = 0; i < output_info.nmode; ++i) {
+ const XRRModeInfo* mode = mode_map_[output_info.modes[i]];
+ DCHECK(mode);
+ modes_out->push_back(
+ ResolutionSelector::Mode(mode->width, mode->height, mode->name));
}
- LOG(WARNING) << "Failed to find a resolution from larger device "
- << "exceeding chosen resolution from smaller device ("
- << *smaller_resolution << ")";
- return false;
+ sort(modes_out->begin(), modes_out->end(),
+ ResolutionSelector::ModeResolutionComparator());
}
bool MonitorReconfigureMain::SetDeviceResolution(
@@ -142,28 +121,6 @@
return system(command.c_str()) == 0;
}
-void MonitorReconfigureMain::Run() {
- // If there's no external monitor connected, just use the highest resolution
- // supported by the LCD.
- if (!IsExternalMonitorConnected()) {
- LOG(INFO) << "No external monitor connected; using max LCD resolution";
- vector<XRRModeInfo*> lcd_modes;
- SortModesByResolution(*lcd_output_, &lcd_modes);
- CHECK(!lcd_modes.empty());
- SetDeviceResolution(lcd_output_->name, lcd_modes[0]->name);
- SetScreenResolution(lcd_modes[0]->name);
- return;
- }
-
- string lcd_resolution, external_resolution, screen_resolution;
- CHECK(FindBestResolutions(&lcd_resolution,
- &external_resolution,
- &screen_resolution));
- SetDeviceResolution(lcd_output_->name, lcd_resolution);
- SetDeviceResolution(external_output_->name, external_resolution);
- SetScreenResolution(screen_resolution);
-}
-
} // end namespace monitor_reconfig
int main(int argc, char** argv) {
@@ -177,7 +134,6 @@
Window window = RootWindow(display, DefaultScreen(display));
XRRScreenResources* screen_info = XRRGetScreenResources(display, window);
monitor_reconfig::MonitorReconfigureMain main_app(display, screen_info);
-
main_app.Run();
return 0;
}
diff --git a/monitor_reconfigure_main.h b/monitor_reconfigure_main.h
index 3aa17d0..aeac10f 100644
--- a/monitor_reconfigure_main.h
+++ b/monitor_reconfigure_main.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -12,6 +12,8 @@
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
+#include "monitor_reconfig/resolution_selector.h"
+
namespace monitor_reconfig {
// MonitorReconfigureMain is the class responsible for setting the external
@@ -32,38 +34,10 @@
// Returns whether an external monitor is connected
bool IsExternalMonitorConnected();
- // Comparator used by SortModeByResolution().
- // Returns true if |mode_a| has more pixels than |mode_b| and false otherwise.
- class ModeResolutionComparator {
- public:
- bool operator()(XRRModeInfo* mode_a, XRRModeInfo* mode_b) const {
- return mode_a->width * mode_a->height > mode_b->width * mode_b->height;
- }
- };
-
// Sorts |output_info|'s modes by decreasing number of pixels, storing the
// results in |modes_out|.
void SortModesByResolution(const XRROutputInfo& output_info,
- std::vector<XRRModeInfo*>* modes_out);
-
- // Find resolutions to use.
- bool FindBestResolutions(
- std::string* lcd_resolution,
- std::string* external_resolution,
- std::string* screen_resolution);
-
- // Find resolutions to use that are reasonably close together.
- // |larger_device_modes| and |smaller_device_modes| should be sorted by
- // descending resolution. We choose the highest resolution from
- // |smaller_device_modes| and the lowest resolution from |larger_device_modes|
- // that's at least as high as the resolution from the smaller device.
- // |screen_resolution| gets set to |smaller_resolution| to avoid clipping.
- bool FindNearestResolutions(
- const std::vector<XRRModeInfo*>& larger_device_modes,
- const std::vector<XRRModeInfo*>& smaller_device_modes,
- std::string* larger_resolution,
- std::string* smaller_resolution,
- std::string* screen_resolution);
+ std::vector<ResolutionSelector::Mode>* modes_out);
// Set the resolution for a particular display or for the screen.
bool SetDeviceResolution(const std::string& device_name,
diff --git a/resolution_selector.cc b/resolution_selector.cc
new file mode 100644
index 0000000..ac4e31b
--- /dev/null
+++ b/resolution_selector.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "monitor_reconfig/resolution_selector.h"
+
+#include "base/logging.h"
+
+namespace monitor_reconfig {
+
+using std::string;
+using std::vector;
+
+
+const int ResolutionSelector::kMaxProjectorPixels = 1280 * 720;
+
+
+bool ResolutionSelector::FindBestResolutions(
+ const vector<Mode>& lcd_modes,
+ const vector<Mode>& external_modes,
+ string* lcd_resolution,
+ string* external_resolution,
+ string* screen_resolution) {
+ DCHECK(!lcd_modes.empty());
+
+ // If there's no external display, just use the highest resolution
+ // available from the LCD.
+ if (external_modes.empty()) {
+ *lcd_resolution = *screen_resolution = lcd_modes[0].name;
+ external_resolution->clear();
+ return true;
+ }
+
+ const int max_lcd_size = lcd_modes[0].width * lcd_modes[0].height;
+ const int max_external_size =
+ external_modes[0].width * external_modes[0].height;
+
+ if (max_lcd_size >= max_external_size) {
+ return FindNearestResolutions(
+ lcd_modes, external_modes,
+ lcd_resolution, external_resolution, screen_resolution);
+ } else {
+ // If the external output is large enough that we think it's a monitor
+ // (as opposed to a projector), then we just use its max resolution and
+ // forget about trying to choose a screen size that'll fit on the
+ // built-in display.
+ if (max_external_size > kMaxProjectorPixels) {
+ *external_resolution = *screen_resolution = external_modes[0].name;
+ *lcd_resolution = lcd_modes[0].name;
+ return true;
+ }
+ return FindNearestResolutions(
+ external_modes, lcd_modes,
+ external_resolution, lcd_resolution, screen_resolution);
+ }
+}
+
+bool ResolutionSelector::FindNearestResolutions(
+ const vector<Mode>& larger_device_modes,
+ const vector<Mode>& smaller_device_modes,
+ std::string* larger_resolution,
+ std::string* smaller_resolution,
+ std::string* screen_resolution) {
+ DCHECK(!larger_device_modes.empty());
+ DCHECK(!smaller_device_modes.empty());
+ DCHECK(larger_resolution);
+ DCHECK(smaller_resolution);
+ DCHECK(screen_resolution);
+
+ // Start with the best that the smaller device has to offer.
+ *smaller_resolution = smaller_device_modes[0].name;
+ *screen_resolution = *smaller_resolution;
+ int smaller_width = smaller_device_modes[0].width;
+ int smaller_height = smaller_device_modes[0].height;
+
+ for (vector<Mode>::const_reverse_iterator it =
+ larger_device_modes.rbegin();
+ it != larger_device_modes.rend(); ++it) {
+ if (it->width >= smaller_width && it->height >= smaller_height) {
+ *larger_resolution = it->name;
+ return true;
+ }
+ }
+
+ LOG(WARNING) << "Failed to find a resolution from larger device "
+ << "exceeding chosen resolution from smaller device ("
+ << *smaller_resolution << ")";
+ return false;
+}
+
+} // namespace monitor_reconfig
diff --git a/resolution_selector.h b/resolution_selector.h
new file mode 100644
index 0000000..e292090
--- /dev/null
+++ b/resolution_selector.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MONITOR_RECONFIGURE_RESOLUTION_SELECTOR_H_
+#define MONITOR_RECONFIGURE_RESOLUTION_SELECTOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace monitor_reconfig {
+
+// ResolutionSelector takes the sets of resolutions supported by the
+// built-in and external displays as input and attempts to choose a shared
+// resolution that will work well on both devices.
+class ResolutionSelector {
+ public:
+ // A single mode supported by a device, equivalent to the XRRModeInfo
+ // struct.
+ struct Mode {
+ Mode(int width, int height, std::string name)
+ : width(width),
+ height(height),
+ name(name) {
+ }
+
+ // Mode's dimensions.
+ int width;
+ int height;
+
+ // Mode's name from XRandR. This uniquely describes the mode and can
+ // be used to set the devices's resolution later.
+ std::string name;
+ };
+
+ // Maximum screen size for the external output at which we assume that
+ // it's a projector (as opposed to a monitor) and try to find a size that
+ // will also fit on the LCD display. Above this, we just use the
+ // external output's maximum resolution, even if it doesn't fit on the
+ // LCD.
+ static const int kMaxProjectorPixels;
+
+ ResolutionSelector() {}
+ ~ResolutionSelector() {}
+
+ // Comparator used to sort Mode objects.
+ // Returns true if |mode_a| has more pixels than |mode_b| and false otherwise.
+ class ModeResolutionComparator {
+ public:
+ bool operator()(const Mode& mode_a, const Mode& mode_b) const {
+ return mode_a.width * mode_a.height > mode_b.width * mode_b.height;
+ }
+ };
+
+ // Find the "best" resolutions for various outputs.
+ // The returned strings contain |name| values from the passed-in modes.
+ bool FindBestResolutions(
+ const std::vector<Mode>& lcd_modes,
+ const std::vector<Mode>& external_modes,
+ std::string* lcd_resolution,
+ std::string* external_resolution,
+ std::string* screen_resolution);
+
+ private:
+ // Find resolutions to use that are reasonably close together.
+ // |larger_device_modes| and |smaller_device_modes| should be sorted by
+ // descending resolution. We choose the highest resolution from
+ // |smaller_device_modes| and the lowest resolution from |larger_device_modes|
+ // that's at least as high as the resolution from the smaller device.
+ // |screen_resolution| gets set to |smaller_resolution| to avoid clipping.
+ bool FindNearestResolutions(
+ const std::vector<Mode>& larger_device_modes,
+ const std::vector<Mode>& smaller_device_modes,
+ std::string* larger_resolution,
+ std::string* smaller_resolution,
+ std::string* screen_resolution);
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionSelector);
+};
+
+} // namespace monitor_reconfig
+
+#endif // MONITOR_RECONFIGURE_RESOLUTION_SELECTOR_H_
diff --git a/resolution_selector_test.cc b/resolution_selector_test.cc
new file mode 100644
index 0000000..7cbfb12
--- /dev/null
+++ b/resolution_selector_test.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include <gflags/gflags.h>
+#include <gtest/gtest.h>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "monitor_reconfig/resolution_selector.h"
+
+DEFINE_bool(logtostderr, false,
+ "Print debugging messages to stderr (suppressed otherwise)");
+
+namespace monitor_reconfig {
+
+using std::string;
+using std::vector;
+
+class ResolutionSelectorTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {}
+
+ // Add a mode to |lcd_modes_|.
+ void AddLcdMode(int width, int height) {
+ lcd_modes_.push_back(
+ ResolutionSelector::Mode(
+ width, height, StringPrintf("%dx%d", width, height)));
+ }
+
+ // Add a mode to |external_modes_|.
+ void AddExternalMode(int width, int height) {
+ external_modes_.push_back(
+ ResolutionSelector::Mode(
+ width, height, StringPrintf("%dx%d", width, height)));
+ }
+
+ // Ask |selector_| for the best resolutions and store them in
+ // |lcd_resolution_|, |external_resolution_|, and |screen_resolution_|.
+ // The return value from |FindBestResolutions()| is returned.
+ bool GetResolutions() {
+ return selector_.FindBestResolutions(lcd_modes_,
+ external_modes_,
+ &lcd_resolution_,
+ &external_resolution_,
+ &screen_resolution_);
+ }
+
+ ResolutionSelector selector_;
+
+ vector<ResolutionSelector::Mode> lcd_modes_;
+ vector<ResolutionSelector::Mode> external_modes_;
+
+ string lcd_resolution_;
+ string external_resolution_;
+ string screen_resolution_;
+};
+
+// We should use the LCD's max resolution when there's no external output
+// connected.
+TEST_F(ResolutionSelectorTest, NoExternalOutput) {
+ AddLcdMode(1024, 768);
+ AddLcdMode(800, 600);
+ ASSERT_TRUE(GetResolutions());
+ EXPECT_EQ("1024x768", lcd_resolution_);
+ EXPECT_EQ("", external_resolution_);
+ EXPECT_EQ("1024x768", screen_resolution_);
+}
+
+// When both outputs have the same max resolution, we should use it.
+TEST_F(ResolutionSelectorTest, MatchingTopResolutions) {
+ AddLcdMode(1024, 768);
+ AddLcdMode(800, 600);
+ AddExternalMode(1024, 768);
+ AddExternalMode(800, 600);
+ ASSERT_TRUE(GetResolutions());
+ EXPECT_EQ("1024x768", lcd_resolution_);
+ EXPECT_EQ("1024x768", external_resolution_);
+ EXPECT_EQ("1024x768", screen_resolution_);
+}
+
+// We should use the highest shared resolution when the LCD has the higher
+// max resolution.
+TEST_F(ResolutionSelectorTest, LcdHasHigherResolution) {
+ AddLcdMode(1024, 768);
+ AddLcdMode(800, 600);
+ AddLcdMode(640, 480);
+ AddExternalMode(800, 600);
+ AddExternalMode(640, 480);
+ ASSERT_TRUE(GetResolutions());
+ EXPECT_EQ("800x600", lcd_resolution_);
+ EXPECT_EQ("800x600", external_resolution_);
+ EXPECT_EQ("800x600", screen_resolution_);
+}
+
+// We should use the highest shared resolution when the external output has
+// the higher max resolution.
+TEST_F(ResolutionSelectorTest, ExternalHasHigherResolution) {
+ AddLcdMode(800, 600);
+ AddLcdMode(640, 480);
+ AddExternalMode(1024, 768);
+ AddExternalMode(800, 600);
+ AddExternalMode(640, 480);
+ ASSERT_TRUE(GetResolutions());
+ EXPECT_EQ("800x600", lcd_resolution_);
+ EXPECT_EQ("800x600", external_resolution_);
+ EXPECT_EQ("800x600", screen_resolution_);
+}
+
+// When the maximum resolution offered by the two outputs is different, we
+// should use the max resolution from the lower-res output.
+TEST_F(ResolutionSelectorTest, MismatchedMaxResolution) {
+ AddLcdMode(1024, 600);
+ AddLcdMode(800, 600);
+ AddExternalMode(1280, 720);
+ AddExternalMode(1024, 768);
+ AddExternalMode(800, 600);
+ ASSERT_TRUE(GetResolutions());
+ EXPECT_EQ("1024x600", lcd_resolution_);
+ EXPECT_EQ("1024x768", external_resolution_);
+ EXPECT_EQ("1024x600", screen_resolution_);
+}
+
+// When the external output is large enough that we think it's a monitor,
+// we should just use its maximum resolution instead of trying to find a
+// size that'll also work for the LCD output.
+TEST_F(ResolutionSelectorTest, ExternalOutputIsMonitor) {
+ AddLcdMode(1024, 768);
+ AddLcdMode(800, 600);
+ AddExternalMode(1600, 1200);
+ AddExternalMode(1280, 960);
+ AddExternalMode(1024, 768);
+ ASSERT_GE(external_modes_[0].width * external_modes_[0].height,
+ ResolutionSelector::kMaxProjectorPixels);
+ ASSERT_TRUE(GetResolutions());
+ EXPECT_EQ("1024x768", lcd_resolution_);
+ EXPECT_EQ("1600x1200", external_resolution_);
+ EXPECT_EQ("1600x1200", screen_resolution_);
+}
+
+// We should just fail if there's no common resolution between the two
+// outputs.
+TEST_F(ResolutionSelectorTest, FailIfNoCommonResolution) {
+ AddLcdMode(1024, 768);
+ AddExternalMode(1280, 600);
+ EXPECT_FALSE(GetResolutions());
+}
+
+} // namespace monitor_reconfig
+
+int main(int argc, char** argv) {
+ google::ParseCommandLineFlags(&argc, &argv, true);
+ CommandLine::Init(argc, argv);
+ logging::InitLogging(NULL,
+ FLAGS_logtostderr ?
+ logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG :
+ logging::LOG_NONE,
+ logging::DONT_LOCK_LOG_FILE,
+ logging::APPEND_TO_OLD_LOG_FILE);
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}