blob: 1283b58a2b108b1180e020e83abc1169055d3e35 [file] [log] [blame]
// Copyright 2014 The Chromium 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 "ui/display/chromeos/display_configurator.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "ui/display/display_switches.h"
#include "ui/display/types/chromeos/display_mode.h"
#include "ui/display/types/chromeos/display_snapshot.h"
#include "ui/display/types/chromeos/native_display_delegate.h"
namespace ui {
namespace {
typedef std::vector<const DisplayMode*> DisplayModeList;
// The delay to perform configuration after RRNotify. See the comment
// in |Dispatch()|.
const int kConfigureDelayMs = 500;
// Returns a string describing |state|.
std::string DisplayPowerStateToString(chromeos::DisplayPowerState state) {
switch (state) {
case chromeos::DISPLAY_POWER_ALL_ON:
return "ALL_ON";
case chromeos::DISPLAY_POWER_ALL_OFF:
return "ALL_OFF";
case chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON:
return "INTERNAL_OFF_EXTERNAL_ON";
case chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF:
return "INTERNAL_ON_EXTERNAL_OFF";
default:
return "unknown (" + base::IntToString(state) + ")";
}
}
// Returns a string describing |state|.
std::string DisplayStateToString(MultipleDisplayState state) {
switch (state) {
case MULTIPLE_DISPLAY_STATE_INVALID:
return "INVALID";
case MULTIPLE_DISPLAY_STATE_HEADLESS:
return "HEADLESS";
case MULTIPLE_DISPLAY_STATE_SINGLE:
return "SINGLE";
case MULTIPLE_DISPLAY_STATE_DUAL_MIRROR:
return "DUAL_MIRROR";
case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED:
return "DUAL_EXTENDED";
}
NOTREACHED() << "Unknown state " << state;
return "INVALID";
}
// Returns the number of displays in |displays| that should be turned on, per
// |state|. If |display_power| is non-NULL, it is updated to contain the
// on/off state of each corresponding entry in |displays|.
int GetDisplayPower(
const std::vector<DisplayConfigurator::DisplayState>& display_states,
chromeos::DisplayPowerState state,
std::vector<bool>* display_power) {
int num_on_displays = 0;
if (display_power)
display_power->resize(display_states.size());
for (size_t i = 0; i < display_states.size(); ++i) {
bool internal =
display_states[i].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
bool on =
state == chromeos::DISPLAY_POWER_ALL_ON ||
(state == chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON &&
!internal) ||
(state == chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal);
if (display_power)
(*display_power)[i] = on;
if (on)
num_on_displays++;
}
return num_on_displays;
}
} // namespace
DisplayConfigurator::CoordinateTransformation::CoordinateTransformation()
: x_scale(1.0),
x_offset(0.0),
y_scale(1.0),
y_offset(0.0) {}
DisplayConfigurator::DisplayState::DisplayState()
: display(NULL),
touch_device_id(0),
selected_mode(NULL),
mirror_mode(NULL) {}
bool DisplayConfigurator::TestApi::TriggerConfigureTimeout() {
if (configurator_->configure_timer_.get() &&
configurator_->configure_timer_->IsRunning()) {
configurator_->configure_timer_.reset();
configurator_->ConfigureDisplays();
return true;
} else {
return false;
}
}
// static
const DisplayMode* DisplayConfigurator::FindDisplayModeMatchingSize(
const DisplaySnapshot& display,
const gfx::Size& size) {
const DisplayMode* best_mode = NULL;
for (DisplayModeList::const_iterator it = display.modes().begin();
it != display.modes().end();
++it) {
const DisplayMode* mode = *it;
if (mode->size() != size)
continue;
if (!best_mode) {
best_mode = mode;
continue;
}
if (mode->is_interlaced()) {
if (!best_mode->is_interlaced())
continue;
} else {
// Reset the best rate if the non interlaced is
// found the first time.
if (best_mode->is_interlaced()) {
best_mode = mode;
continue;
}
}
if (mode->refresh_rate() < best_mode->refresh_rate())
continue;
best_mode = mode;
}
return best_mode;
}
DisplayConfigurator::DisplayConfigurator()
: state_controller_(NULL),
mirroring_controller_(NULL),
is_panel_fitting_enabled_(false),
configure_display_(base::SysInfo::IsRunningOnChromeOS()),
display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
power_state_(chromeos::DISPLAY_POWER_ALL_ON),
next_display_protection_client_id_(1) {}
DisplayConfigurator::~DisplayConfigurator() {
if (native_display_delegate_)
native_display_delegate_->RemoveObserver(this);
}
void DisplayConfigurator::SetDelegatesForTesting(
scoped_ptr<NativeDisplayDelegate> display_delegate,
scoped_ptr<TouchscreenDelegate> touchscreen_delegate) {
DCHECK(!native_display_delegate_);
DCHECK(!touchscreen_delegate_);
InitializeDelegates(display_delegate.Pass(), touchscreen_delegate.Pass());
configure_display_ = true;
}
void DisplayConfigurator::SetInitialDisplayPower(
chromeos::DisplayPowerState power_state) {
DCHECK_EQ(display_state_, MULTIPLE_DISPLAY_STATE_INVALID);
power_state_ = power_state;
}
void DisplayConfigurator::Init(bool is_panel_fitting_enabled) {
is_panel_fitting_enabled_ = is_panel_fitting_enabled;
if (!configure_display_)
return;
PlatformInitialize();
}
void DisplayConfigurator::InitializeDelegates(
scoped_ptr<NativeDisplayDelegate> display_delegate,
scoped_ptr<TouchscreenDelegate> touchscreen_delegate) {
if (!native_display_delegate_ && !touchscreen_delegate_) {
native_display_delegate_ = display_delegate.Pass();
touchscreen_delegate_ = touchscreen_delegate.Pass();
native_display_delegate_->AddObserver(this);
}
}
void DisplayConfigurator::ForceInitialConfigure(
uint32_t background_color_argb) {
if (!configure_display_)
return;
native_display_delegate_->GrabServer();
native_display_delegate_->Initialize();
UpdateCachedDisplays();
if (cached_displays_.size() > 1 && background_color_argb)
native_display_delegate_->SetBackgroundColor(background_color_argb);
const MultipleDisplayState new_state = ChooseDisplayState(power_state_);
const bool success =
EnterStateOrFallBackToSoftwareMirroring(new_state, power_state_);
// Force the DPMS on chrome startup as the driver doesn't always detect
// that all displays are on when signing out.
native_display_delegate_->ForceDPMSOn();
native_display_delegate_->UngrabServer();
NotifyObservers(success, new_state);
}
bool DisplayConfigurator::ApplyProtections(const ContentProtections& requests) {
for (DisplayStateList::const_iterator it = cached_displays_.begin();
it != cached_displays_.end();
++it) {
uint32_t all_desired = 0;
ContentProtections::const_iterator request_it =
requests.find(it->display->display_id());
if (request_it != requests.end())
all_desired = request_it->second;
switch (it->display->type()) {
case DISPLAY_CONNECTION_TYPE_UNKNOWN:
return false;
// DisplayPort, DVI, and HDMI all support HDCP.
case DISPLAY_CONNECTION_TYPE_DISPLAYPORT:
case DISPLAY_CONNECTION_TYPE_DVI:
case DISPLAY_CONNECTION_TYPE_HDMI: {
HDCPState new_desired_state =
(all_desired & CONTENT_PROTECTION_METHOD_HDCP) ?
HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED;
if (!native_display_delegate_->SetHDCPState(*it->display,
new_desired_state))
return false;
break;
}
case DISPLAY_CONNECTION_TYPE_INTERNAL:
case DISPLAY_CONNECTION_TYPE_VGA:
case DISPLAY_CONNECTION_TYPE_NETWORK:
// No protections for these types. Do nothing.
break;
case DISPLAY_CONNECTION_TYPE_NONE:
NOTREACHED();
break;
}
}
return true;
}
DisplayConfigurator::ContentProtectionClientId
DisplayConfigurator::RegisterContentProtectionClient() {
if (!configure_display_)
return kInvalidClientId;
return next_display_protection_client_id_++;
}
void DisplayConfigurator::UnregisterContentProtectionClient(
ContentProtectionClientId client_id) {
client_protection_requests_.erase(client_id);
ContentProtections protections;
for (ProtectionRequests::const_iterator it =
client_protection_requests_.begin();
it != client_protection_requests_.end();
++it) {
for (ContentProtections::const_iterator it2 = it->second.begin();
it2 != it->second.end();
++it2) {
protections[it2->first] |= it2->second;
}
}
ApplyProtections(protections);
}
bool DisplayConfigurator::QueryContentProtectionStatus(
ContentProtectionClientId client_id,
int64_t display_id,
uint32_t* link_mask,
uint32_t* protection_mask) {
if (!configure_display_)
return false;
uint32_t enabled = 0;
uint32_t unfulfilled = 0;
*link_mask = 0;
for (DisplayStateList::const_iterator it = cached_displays_.begin();
it != cached_displays_.end();
++it) {
if (it->display->display_id() != display_id)
continue;
*link_mask |= it->display->type();
switch (it->display->type()) {
case DISPLAY_CONNECTION_TYPE_UNKNOWN:
return false;
// DisplayPort, DVI, and HDMI all support HDCP.
case DISPLAY_CONNECTION_TYPE_DISPLAYPORT:
case DISPLAY_CONNECTION_TYPE_DVI:
case DISPLAY_CONNECTION_TYPE_HDMI: {
HDCPState state;
if (!native_display_delegate_->GetHDCPState(*it->display, &state))
return false;
if (state == HDCP_STATE_ENABLED)
enabled |= CONTENT_PROTECTION_METHOD_HDCP;
else
unfulfilled |= CONTENT_PROTECTION_METHOD_HDCP;
break;
}
case DISPLAY_CONNECTION_TYPE_INTERNAL:
case DISPLAY_CONNECTION_TYPE_VGA:
case DISPLAY_CONNECTION_TYPE_NETWORK:
// No protections for these types. Do nothing.
break;
case DISPLAY_CONNECTION_TYPE_NONE:
NOTREACHED();
break;
}
}
// Don't reveal protections requested by other clients.
ProtectionRequests::iterator it = client_protection_requests_.find(client_id);
if (it != client_protection_requests_.end()) {
uint32_t requested_mask = 0;
if (it->second.find(display_id) != it->second.end())
requested_mask = it->second[display_id];
*protection_mask = enabled & ~unfulfilled & requested_mask;
} else {
*protection_mask = 0;
}
return true;
}
bool DisplayConfigurator::EnableContentProtection(
ContentProtectionClientId client_id,
int64_t display_id,
uint32_t desired_method_mask) {
if (!configure_display_)
return false;
ContentProtections protections;
for (ProtectionRequests::const_iterator it =
client_protection_requests_.begin();
it != client_protection_requests_.end();
++it) {
for (ContentProtections::const_iterator it2 = it->second.begin();
it2 != it->second.end();
++it2) {
if (it->first == client_id && it2->first == display_id)
continue;
protections[it2->first] |= it2->second;
}
}
protections[display_id] |= desired_method_mask;
if (!ApplyProtections(protections))
return false;
if (desired_method_mask == CONTENT_PROTECTION_METHOD_NONE) {
if (client_protection_requests_.find(client_id) !=
client_protection_requests_.end()) {
client_protection_requests_[client_id].erase(display_id);
if (client_protection_requests_[client_id].size() == 0)
client_protection_requests_.erase(client_id);
}
} else {
client_protection_requests_[client_id][display_id] = desired_method_mask;
}
return true;
}
std::vector<ui::ColorCalibrationProfile>
DisplayConfigurator::GetAvailableColorCalibrationProfiles(int64_t display_id) {
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableDisplayColorCalibration)) {
for (size_t i = 0; i < cached_displays_.size(); ++i) {
if (cached_displays_[i].display &&
cached_displays_[i].display->display_id() == display_id) {
return native_display_delegate_->GetAvailableColorCalibrationProfiles(
*cached_displays_[i].display);
}
}
}
return std::vector<ui::ColorCalibrationProfile>();
}
bool DisplayConfigurator::SetColorCalibrationProfile(
int64_t display_id,
ui::ColorCalibrationProfile new_profile) {
for (size_t i = 0; i < cached_displays_.size(); ++i) {
if (cached_displays_[i].display &&
cached_displays_[i].display->display_id() == display_id) {
return native_display_delegate_->SetColorCalibrationProfile(
*cached_displays_[i].display, new_profile);
}
}
return false;
}
void DisplayConfigurator::PrepareForExit() {
configure_display_ = false;
}
bool DisplayConfigurator::SetDisplayPower(
chromeos::DisplayPowerState power_state,
int flags) {
if (!configure_display_)
return false;
VLOG(1) << "SetDisplayPower: power_state="
<< DisplayPowerStateToString(power_state) << " flags=" << flags
<< ", configure timer="
<< ((configure_timer_.get() && configure_timer_->IsRunning()) ?
"Running" : "Stopped");
if (power_state == power_state_ && !(flags & kSetDisplayPowerForceProbe))
return true;
native_display_delegate_->GrabServer();
UpdateCachedDisplays();
const MultipleDisplayState new_state = ChooseDisplayState(power_state);
bool attempted_change = false;
bool success = false;
bool only_if_single_internal_display =
flags & kSetDisplayPowerOnlyIfSingleInternalDisplay;
bool single_internal_display =
cached_displays_.size() == 1 &&
cached_displays_[0].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
if (single_internal_display || !only_if_single_internal_display) {
success = EnterStateOrFallBackToSoftwareMirroring(new_state, power_state);
attempted_change = true;
// 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.
if (success && power_state != chromeos::DISPLAY_POWER_ALL_OFF)
native_display_delegate_->ForceDPMSOn();
}
native_display_delegate_->UngrabServer();
if (attempted_change)
NotifyObservers(success, new_state);
return true;
}
bool DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) {
if (!configure_display_)
return false;
VLOG(1) << "SetDisplayMode: state=" << DisplayStateToString(new_state);
if (display_state_ == new_state) {
// Cancel software mirroring if the state is moving from
// MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED to
// MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED.
if (mirroring_controller_ &&
new_state == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED)
mirroring_controller_->SetSoftwareMirroring(false);
NotifyObservers(true, new_state);
return true;
}
native_display_delegate_->GrabServer();
UpdateCachedDisplays();
const bool success =
EnterStateOrFallBackToSoftwareMirroring(new_state, power_state_);
native_display_delegate_->UngrabServer();
NotifyObservers(success, new_state);
return success;
}
void DisplayConfigurator::OnConfigurationChanged() {
// Configure displays with |kConfigureDelayMs| delay,
// so that time-consuming ConfigureDisplays() won't be called multiple times.
if (configure_timer_.get()) {
configure_timer_->Reset();
} else {
configure_timer_.reset(new base::OneShotTimer<DisplayConfigurator>());
configure_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
this,
&DisplayConfigurator::ConfigureDisplays);
}
}
void DisplayConfigurator::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DisplayConfigurator::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void DisplayConfigurator::SuspendDisplays() {
// If the display is off due to user inactivity and there's only a single
// internal display connected, switch to the all-on state before
// suspending. This shouldn't be very noticeable to the user since the
// backlight is off at this point, and doing this lets us resume directly
// into the "on" state, which greatly reduces resume times.
if (power_state_ == chromeos::DISPLAY_POWER_ALL_OFF) {
SetDisplayPower(chromeos::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.
native_display_delegate_->SyncWithServer();
}
}
void DisplayConfigurator::ResumeDisplays() {
// Force probing to ensure that we pick up any changes that were made
// while the system was suspended.
SetDisplayPower(power_state_, kSetDisplayPowerForceProbe);
}
void DisplayConfigurator::UpdateCachedDisplays() {
std::vector<DisplaySnapshot*> snapshots =
native_display_delegate_->GetDisplays();
cached_displays_.clear();
for (size_t i = 0; i < snapshots.size(); ++i) {
DisplayState display_state;
display_state.display = snapshots[i];
cached_displays_.push_back(display_state);
}
touchscreen_delegate_->AssociateTouchscreens(&cached_displays_);
// Set |selected_mode| fields.
for (size_t i = 0; i < cached_displays_.size(); ++i) {
DisplayState* display_state = &cached_displays_[i];
if (display_state->display->has_proper_display_id()) {
gfx::Size size;
if (state_controller_ &&
state_controller_->GetResolutionForDisplayId(
display_state->display->display_id(), &size)) {
display_state->selected_mode =
FindDisplayModeMatchingSize(*display_state->display, size);
}
}
// Fall back to native mode.
if (!display_state->selected_mode)
display_state->selected_mode = display_state->display->native_mode();
}
// Set |mirror_mode| fields.
if (cached_displays_.size() == 2) {
bool one_is_internal =
cached_displays_[0].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
bool two_is_internal =
cached_displays_[1].display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
int internal_displays =
(one_is_internal ? 1 : 0) + (two_is_internal ? 1 : 0);
DCHECK_LT(internal_displays, 2);
LOG_IF(WARNING, internal_displays == 2)
<< "Two internal displays detected.";
bool can_mirror = false;
for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) {
// Try preserving external display's aspect ratio on the first attempt.
// If that fails, fall back to the highest matching resolution.
bool preserve_aspect = attempt == 0;
if (internal_displays == 1) {
if (one_is_internal) {
can_mirror = FindMirrorMode(&cached_displays_[0],
&cached_displays_[1],
is_panel_fitting_enabled_,
preserve_aspect);
} else {
DCHECK(two_is_internal);
can_mirror = FindMirrorMode(&cached_displays_[1],
&cached_displays_[0],
is_panel_fitting_enabled_,
preserve_aspect);
}
} else { // if (internal_displays == 0)
// No panel fitting for external displays, so fall back to exact match.
can_mirror = FindMirrorMode(
&cached_displays_[0], &cached_displays_[1], false, preserve_aspect);
if (!can_mirror && preserve_aspect) {
// FindMirrorMode() will try to preserve aspect ratio of what it
// thinks is external display, so if it didn't succeed with one, maybe
// it will succeed with the other. This way we will have the correct
// aspect ratio on at least one of them.
can_mirror = FindMirrorMode(&cached_displays_[1],
&cached_displays_[0],
false,
preserve_aspect);
}
}
}
}
}
bool DisplayConfigurator::FindMirrorMode(DisplayState* internal_display,
DisplayState* external_display,
bool try_panel_fitting,
bool preserve_aspect) {
const DisplayMode* internal_native_info =
internal_display->display->native_mode();
const DisplayMode* external_native_info =
external_display->display->native_mode();
if (!internal_native_info || !external_native_info)
return false;
// Check if some external display resolution can be mirrored on internal.
// Prefer the modes in the order they're present in DisplaySnapshot, assuming
// this is the order in which they look better on the monitor.
for (DisplayModeList::const_iterator external_it =
external_display->display->modes().begin();
external_it != external_display->display->modes().end();
++external_it) {
const DisplayMode& external_info = **external_it;
bool is_native_aspect_ratio =
external_native_info->size().width() * external_info.size().height() ==
external_native_info->size().height() * external_info.size().width();
if (preserve_aspect && !is_native_aspect_ratio)
continue; // Allow only aspect ratio preserving modes for mirroring.
// Try finding an exact match.
for (DisplayModeList::const_iterator internal_it =
internal_display->display->modes().begin();
internal_it != internal_display->display->modes().end();
++internal_it) {
const DisplayMode& internal_info = **internal_it;
if (internal_info.size().width() == external_info.size().width() &&
internal_info.size().height() == external_info.size().height() &&
internal_info.is_interlaced() == external_info.is_interlaced()) {
internal_display->mirror_mode = *internal_it;
external_display->mirror_mode = *external_it;
return true; // Mirror mode found.
}
}
// Try to create a matching internal display mode by panel fitting.
if (try_panel_fitting) {
// We can downscale by 1.125, and upscale indefinitely. Downscaling looks
// ugly, so, can fit == can upscale. Also, internal panels don't support
// fitting interlaced modes.
bool can_fit = internal_native_info->size().width() >=
external_info.size().width() &&
internal_native_info->size().height() >=
external_info.size().height() &&
!external_info.is_interlaced();
if (can_fit) {
native_display_delegate_->AddMode(*internal_display->display,
*external_it);
internal_display->display->add_mode(*external_it);
internal_display->mirror_mode = *external_it;
external_display->mirror_mode = *external_it;
return true; // Mirror mode created.
}
}
}
return false;
}
void DisplayConfigurator::ConfigureDisplays() {
configure_timer_.reset();
if (!configure_display_)
return;
native_display_delegate_->GrabServer();
UpdateCachedDisplays();
const MultipleDisplayState new_state = ChooseDisplayState(power_state_);
const bool success =
EnterStateOrFallBackToSoftwareMirroring(new_state, power_state_);
native_display_delegate_->UngrabServer();
NotifyObservers(success, new_state);
}
void DisplayConfigurator::NotifyObservers(
bool success,
MultipleDisplayState attempted_state) {
if (success) {
FOR_EACH_OBSERVER(
Observer, observers_, OnDisplayModeChanged(cached_displays_));
} else {
FOR_EACH_OBSERVER(
Observer, observers_, OnDisplayModeChangeFailed(attempted_state));
}
}
bool DisplayConfigurator::EnterStateOrFallBackToSoftwareMirroring(
MultipleDisplayState display_state,
chromeos::DisplayPowerState power_state) {
bool success = EnterState(display_state, power_state);
if (mirroring_controller_) {
bool enable_software_mirroring = false;
if (!success && display_state == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR) {
if (display_state_ != MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED ||
power_state_ != power_state)
EnterState(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED, power_state);
enable_software_mirroring = success =
display_state_ == MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED;
}
mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring);
}
return success;
}
bool DisplayConfigurator::EnterState(MultipleDisplayState display_state,
chromeos::DisplayPowerState power_state) {
std::vector<bool> display_power;
int num_on_displays =
GetDisplayPower(cached_displays_, power_state, &display_power);
VLOG(1) << "EnterState: display=" << DisplayStateToString(display_state)
<< " power=" << DisplayPowerStateToString(power_state);
// Framebuffer dimensions.
gfx::Size size;
std::vector<gfx::Point> new_origins(cached_displays_.size(), gfx::Point());
std::vector<const DisplayMode*> new_mode;
for (size_t i = 0; i < cached_displays_.size(); ++i)
new_mode.push_back(cached_displays_[i].display->current_mode());
switch (display_state) {
case MULTIPLE_DISPLAY_STATE_INVALID:
NOTREACHED() << "Ignoring request to enter invalid state with "
<< cached_displays_.size() << " connected display(s)";
return false;
case MULTIPLE_DISPLAY_STATE_HEADLESS:
if (cached_displays_.size() != 0) {
LOG(WARNING) << "Ignoring request to enter headless mode with "
<< cached_displays_.size() << " connected display(s)";
return false;
}
break;
case MULTIPLE_DISPLAY_STATE_SINGLE: {
// If there are multiple displays connected, only one should be turned on.
if (cached_displays_.size() != 1 && num_on_displays != 1) {
LOG(WARNING) << "Ignoring request to enter single mode with "
<< cached_displays_.size() << " connected displays and "
<< num_on_displays << " turned on";
return false;
}
for (size_t i = 0; i < cached_displays_.size(); ++i) {
DisplayState* state = &cached_displays_[i];
new_mode[i] = display_power[i] ? state->selected_mode : NULL;
if (display_power[i] || cached_displays_.size() == 1) {
const DisplayMode* mode_info = state->selected_mode;
if (!mode_info)
return false;
if (mode_info->size() == gfx::Size(1024, 768)) {
VLOG(1) << "Potentially misdetecting display(1024x768):"
<< " displays size=" << cached_displays_.size()
<< ", num_on_displays=" << num_on_displays
<< ", current size:" << size.width() << "x" << size.height()
<< ", i=" << i << ", display=" << state->display->ToString()
<< ", display_mode=" << mode_info->ToString();
}
size = mode_info->size();
}
}
break;
}
case MULTIPLE_DISPLAY_STATE_DUAL_MIRROR: {
if (cached_displays_.size() != 2 ||
(num_on_displays != 0 && num_on_displays != 2)) {
LOG(WARNING) << "Ignoring request to enter mirrored mode with "
<< cached_displays_.size() << " connected display(s) and "
<< num_on_displays << " turned on";
return false;
}
const DisplayMode* mode_info = cached_displays_[0].mirror_mode;
if (!mode_info)
return false;
size = mode_info->size();
for (size_t i = 0; i < cached_displays_.size(); ++i) {
DisplayState* state = &cached_displays_[i];
new_mode[i] = display_power[i] ? state->mirror_mode : NULL;
if (state->touch_device_id) {
// CTM needs to be calculated if aspect preserving scaling is used.
// Otherwise, assume it is full screen, and use identity CTM.
if (state->mirror_mode != state->display->native_mode() &&
state->display->is_aspect_preserving_scaling()) {
state->transform = GetMirrorModeCTM(*state);
mirrored_display_area_ratio_map_[state->touch_device_id] =
GetMirroredDisplayAreaRatio(*state);
}
}
}
break;
}
case MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED: {
if (cached_displays_.size() != 2 ||
(num_on_displays != 0 && num_on_displays != 2)) {
LOG(WARNING) << "Ignoring request to enter extended mode with "
<< cached_displays_.size() << " connected display(s) and "
<< num_on_displays << " turned on";
return false;
}
for (size_t i = 0; i < cached_displays_.size(); ++i) {
DisplayState* state = &cached_displays_[i];
new_origins[i].set_y(size.height() ? size.height() + kVerticalGap : 0);
new_mode[i] = display_power[i] ? state->selected_mode : NULL;
// Retain the full screen size even if all displays are off so the
// same desktop configuration can be restored when the displays are
// turned back on.
const DisplayMode* mode_info = cached_displays_[i].selected_mode;
if (!mode_info)
return false;
size.set_width(std::max<int>(size.width(), mode_info->size().width()));
size.set_height(size.height() + (size.height() ? kVerticalGap : 0) +
mode_info->size().height());
}
for (size_t i = 0; i < cached_displays_.size(); ++i) {
DisplayState* state = &cached_displays_[i];
if (state->touch_device_id)
state->transform = GetExtendedModeCTM(*state, new_origins[i], size);
}
break;
}
}
// Finally, apply the desired changes.
bool all_succeeded = true;
if (!cached_displays_.empty()) {
native_display_delegate_->CreateFrameBuffer(size);
for (size_t i = 0; i < cached_displays_.size(); ++i) {
const DisplayState& state = cached_displays_[i];
bool configure_succeeded = false;
while (true) {
if (native_display_delegate_->Configure(
*state.display, new_mode[i], new_origins[i])) {
state.display->set_current_mode(new_mode[i]);
state.display->set_origin(new_origins[i]);
configure_succeeded = true;
break;
}
const DisplayMode* mode_info = new_mode[i];
if (!mode_info)
break;
// Find the mode with the next-best resolution and see if that can
// be set.
int best_mode_pixels = 0;
int current_mode_pixels = mode_info->size().GetArea();
for (DisplayModeList::const_iterator it =
state.display->modes().begin();
it != state.display->modes().end();
it++) {
int pixel_count = (*it)->size().GetArea();
if ((pixel_count < current_mode_pixels) &&
(pixel_count > best_mode_pixels)) {
new_mode[i] = *it;
best_mode_pixels = pixel_count;
}
}
if (best_mode_pixels == 0)
break;
}
if (configure_succeeded) {
if (state.touch_device_id)
touchscreen_delegate_->ConfigureCTM(state.touch_device_id,
state.transform);
} else {
all_succeeded = false;
}
// If we are trying to set mirror mode and one of the modesets fails,
// then the two monitors will be mis-matched. In this case, return
// false to let the observers be aware.
if (display_state == MULTIPLE_DISPLAY_STATE_DUAL_MIRROR &&
display_power[i] &&
state.display->current_mode() != state.mirror_mode)
all_succeeded = false;
}
}
if (all_succeeded) {
display_state_ = display_state;
power_state_ = power_state;
}
return all_succeeded;
}
MultipleDisplayState DisplayConfigurator::ChooseDisplayState(
chromeos::DisplayPowerState power_state) const {
int num_on_displays = GetDisplayPower(cached_displays_, power_state, NULL);
switch (cached_displays_.size()) {
case 0:
return MULTIPLE_DISPLAY_STATE_HEADLESS;
case 1:
return MULTIPLE_DISPLAY_STATE_SINGLE;
case 2: {
if (num_on_displays == 1) {
// If only one display is currently turned on, return the "single"
// state so that its native mode will be used.
return MULTIPLE_DISPLAY_STATE_SINGLE;
} else {
if (!state_controller_)
return MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED;
// With either both displays on or both displays off, use one of the
// dual modes.
std::vector<int64_t> display_ids;
for (size_t i = 0; i < cached_displays_.size(); ++i) {
// If display id isn't available, switch to extended mode.
if (!cached_displays_[i].display->has_proper_display_id())
return MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED;
display_ids.push_back(cached_displays_[i].display->display_id());
}
return state_controller_->GetStateForDisplayIds(display_ids);
}
}
default:
NOTREACHED();
}
return MULTIPLE_DISPLAY_STATE_INVALID;
}
DisplayConfigurator::CoordinateTransformation
DisplayConfigurator::GetMirrorModeCTM(const DisplayState& display_state) {
CoordinateTransformation ctm; // Default to identity
const DisplayMode* native_mode_info = display_state.display->native_mode();
const DisplayMode* mirror_mode_info = display_state.mirror_mode;
if (!native_mode_info || !mirror_mode_info ||
native_mode_info->size().height() == 0 ||
mirror_mode_info->size().height() == 0 ||
native_mode_info->size().width() == 0 ||
mirror_mode_info->size().width() == 0)
return ctm;
float native_mode_ar = static_cast<float>(native_mode_info->size().width()) /
static_cast<float>(native_mode_info->size().height());
float mirror_mode_ar = static_cast<float>(mirror_mode_info->size().width()) /
static_cast<float>(mirror_mode_info->size().height());
if (mirror_mode_ar > native_mode_ar) { // Letterboxing
ctm.x_scale = 1.0;
ctm.x_offset = 0.0;
ctm.y_scale = mirror_mode_ar / native_mode_ar;
ctm.y_offset = (native_mode_ar / mirror_mode_ar - 1.0) * 0.5;
return ctm;
}
if (native_mode_ar > mirror_mode_ar) { // Pillarboxing
ctm.y_scale = 1.0;
ctm.y_offset = 0.0;
ctm.x_scale = native_mode_ar / mirror_mode_ar;
ctm.x_offset = (mirror_mode_ar / native_mode_ar - 1.0) * 0.5;
return ctm;
}
return ctm; // Same aspect ratio - return identity
}
DisplayConfigurator::CoordinateTransformation
DisplayConfigurator::GetExtendedModeCTM(const DisplayState& display_state,
const gfx::Point& new_origin,
const gfx::Size& framebuffer_size) {
CoordinateTransformation ctm; // Default to identity
const DisplayMode* mode_info = display_state.selected_mode;
DCHECK(mode_info);
if (!mode_info)
return ctm;
// An example of how to calculate the CTM.
// Suppose we have 2 monitors, the first one has size 1366 x 768.
// The second one has size 2560 x 1600
// The total size of framebuffer is 2560 x 2428
// where 2428 = 768 + 60 (hidden gap) + 1600
// and the sceond monitor is translated to Point (0, 828) in the
// framebuffer.
// X will first map input event location to [0, 2560) x [0, 2428),
// then apply CTM on it.
// So to compute CTM, for monitor1, we have
// x_scale = (1366 - 1) / (2560 - 1)
// x_offset = 0 / (2560 - 1)
// y_scale = (768 - 1) / (2428 - 1)
// y_offset = 0 / (2428 -1)
// For Monitor 2, we have
// x_scale = (2560 - 1) / (2560 - 1)
// x_offset = 0 / (2560 - 1)
// y_scale = (1600 - 1) / (2428 - 1)
// y_offset = 828 / (2428 -1)
// See the unittest DisplayConfiguratorTest.CTMForMultiScreens.
ctm.x_scale = static_cast<float>(mode_info->size().width() - 1) /
(framebuffer_size.width() - 1);
ctm.x_offset =
static_cast<float>(new_origin.x()) / (framebuffer_size.width() - 1);
ctm.y_scale = static_cast<float>(mode_info->size().height() - 1) /
(framebuffer_size.height() - 1);
ctm.y_offset =
static_cast<float>(new_origin.y()) / (framebuffer_size.height() - 1);
return ctm;
}
float DisplayConfigurator::GetMirroredDisplayAreaRatio(
const DisplayState& display_state) {
float area_ratio = 1.0f;
const DisplayMode* native_mode_info = display_state.display->native_mode();
const DisplayMode* mirror_mode_info = display_state.mirror_mode;
if (!native_mode_info || !mirror_mode_info ||
native_mode_info->size().height() == 0 ||
mirror_mode_info->size().height() == 0 ||
native_mode_info->size().width() == 0 ||
mirror_mode_info->size().width() == 0)
return area_ratio;
float width_ratio = static_cast<float>(mirror_mode_info->size().width()) /
static_cast<float>(native_mode_info->size().width());
float height_ratio = static_cast<float>(mirror_mode_info->size().height()) /
static_cast<float>(native_mode_info->size().height());
area_ratio = width_ratio * height_ratio;
return area_ratio;
}
} // namespace ui