// Copyright (c) 2012 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 "chromeos/display/output_configurator.h"
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/XInput2.h>
#include "base/bind.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 "chromeos/display/output_util.h"
#include "chromeos/display/real_output_configurator_delegate.h"
namespace chromeos {
namespace {
// The delay to perform configuration after RRNotify. See the comment
// in |Dispatch()|.
const int64 kConfigureDelayMs = 500;
// Returns a string describing |state|.
std::string DisplayPowerStateToString(DisplayPowerState state) {
switch (state) {
case DISPLAY_POWER_ALL_ON:
return "ALL_ON";
case DISPLAY_POWER_ALL_OFF:
return "ALL_OFF";
case DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON:
return "INTERNAL_OFF_EXTERNAL_ON";
case DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF:
return "INTERNAL_ON_EXTERNAL_OFF";
default:
return "unknown (" + base::IntToString(state) + ")";
}
}
// Returns a string describing |state|.
std::string OutputStateToString(OutputState state) {
switch (state) {
case STATE_INVALID:
return "INVALID";
case STATE_HEADLESS:
return "HEADLESS";
case STATE_SINGLE:
return "SINGLE";
case STATE_DUAL_MIRROR:
return "DUAL_MIRROR";
case STATE_DUAL_EXTENDED:
return "DUAL_EXTENDED";
}
NOTREACHED() << "Unknown state " << state;
return "INVALID";
}
// Returns a string representation of OutputSnapshot.
std::string OutputSnapshotToString(
const OutputConfigurator::OutputSnapshot* output) {
return base::StringPrintf(
"[type=%d, output=%ld, crtc=%ld, mode=%ld, dim=%dx%d]",
output->type,
output->output,
output->crtc,
output->current_mode,
static_cast<int>(output->width_mm),
static_cast<int>(output->height_mm));
}
// Returns a string representation of ModeInfo.
std::string ModeInfoToString(const OutputConfigurator::ModeInfo* mode) {
return base::StringPrintf("[%dx%d %srate=%f]",
mode->width,
mode->height,
mode->interlaced ? "interlaced " : "",
mode->refresh_rate);
}
// 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 num_on_outputs;
}
// Determine if there is an "internal" output and how many outputs are
// connected.
bool IsProjecting(
const std::vector<OutputConfigurator::OutputSnapshot>& outputs) {
bool has_internal_output = false;
int connected_output_count = outputs.size();
for (size_t i = 0; i < outputs.size(); ++i)
has_internal_output |= outputs[i].is_internal;
// "Projecting" is defined as having more than 1 output connected while at
// least one of them is an internal output.
return has_internal_output && (connected_output_count > 1);
}
} // namespace
OutputConfigurator::ModeInfo::ModeInfo()
: width(0),
height(0),
interlaced(false),
refresh_rate(0.0) {}
OutputConfigurator::ModeInfo::ModeInfo(int width,
int height,
bool interlaced,
float refresh_rate)
: width(width),
height(height),
interlaced(interlaced),
refresh_rate(refresh_rate) {}
OutputConfigurator::CoordinateTransformation::CoordinateTransformation()
: x_scale(1.0),
x_offset(0.0),
y_scale(1.0),
y_offset(0.0) {}
OutputConfigurator::OutputSnapshot::OutputSnapshot()
: output(None),
crtc(None),
current_mode(None),
native_mode(None),
mirror_mode(None),
selected_mode(None),
x(0),
y(0),
width_mm(0),
height_mm(0),
is_internal(false),
is_aspect_preserving_scaling(false),
type(OUTPUT_TYPE_UNKNOWN),
touch_device_id(0),
display_id(0),
has_display_id(false),
index(0) {}
OutputConfigurator::OutputSnapshot::~OutputSnapshot() {}
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;
}
}
// static
const OutputConfigurator::ModeInfo* OutputConfigurator::GetModeInfo(
const OutputSnapshot& output,
RRMode mode) {
if (mode == None)
return NULL;
ModeInfoMap::const_iterator it = output.mode_infos.find(mode);
if (it == output.mode_infos.end()) {
LOG(WARNING) << "Unable to find info about mode " << mode
<< " for output " << output.output;
return NULL;
}
return &it->second;
}
// static
RRMode OutputConfigurator::FindOutputModeMatchingSize(
const OutputSnapshot& output,
int width,
int height) {
RRMode found = None;
float best_rate = 0;
bool non_interlaced_found = false;
for (ModeInfoMap::const_iterator it = output.mode_infos.begin();
it != output.mode_infos.end(); ++it) {
RRMode mode = it->first;
const ModeInfo& info = it->second;
if (info.width == width && info.height == height) {
if (info.interlaced) {
if (non_interlaced_found)
continue;
} else {
// Reset the best rate if the non interlaced is
// found the first time.
if (!non_interlaced_found)
best_rate = info.refresh_rate;
non_interlaced_found = true;
}
if (info.refresh_rate < best_rate)
continue;
found = mode;
best_rate = info.refresh_rate;
}
}
return found;
}
OutputConfigurator::OutputConfigurator()
: state_controller_(NULL),
mirroring_controller_(NULL),
is_panel_fitting_enabled_(false),
configure_display_(base::SysInfo::IsRunningOnChromeOS()),
xrandr_event_base_(0),
output_state_(STATE_INVALID),
power_state_(DISPLAY_POWER_ALL_ON),
next_output_protection_client_id_(1) {
}
OutputConfigurator::~OutputConfigurator() {}
void OutputConfigurator::SetDelegateForTesting(scoped_ptr<Delegate> delegate) {
delegate_ = delegate.Pass();
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) {
is_panel_fitting_enabled_ = is_panel_fitting_enabled;
if (!configure_display_)
return;
if (!delegate_)
delegate_.reset(new RealOutputConfiguratorDelegate());
}
void OutputConfigurator::Start(uint32 background_color_argb) {
if (!configure_display_)
return;
delegate_->GrabServer();
delegate_->InitXRandRExtension(&xrandr_event_base_);
UpdateCachedOutputs();
if (cached_outputs_.size() > 1 && background_color_argb)
delegate_->SetBackgroundColor(background_color_argb);
const OutputState new_state = ChooseOutputState(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.
delegate_->ForceDPMSOn();
delegate_->UngrabServer();
delegate_->SendProjectingStateToPowerManager(IsProjecting(cached_outputs_));
NotifyObservers(success, new_state);
}
bool OutputConfigurator::ApplyProtections(const DisplayProtections& requests) {
for (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin();
it != cached_outputs_.end(); ++it) {
RROutput this_id = it->output;
uint32_t all_desired = 0;
DisplayProtections::const_iterator request_it = requests.find(
it->display_id);
if (request_it != requests.end())
all_desired = request_it->second;
switch (it->type) {
case OUTPUT_TYPE_UNKNOWN:
return false;
// DisplayPort, DVI, and HDMI all support HDCP.
case OUTPUT_TYPE_DISPLAYPORT:
case OUTPUT_TYPE_DVI:
case OUTPUT_TYPE_HDMI: {
HDCPState new_desired_state =
(all_desired & OUTPUT_PROTECTION_METHOD_HDCP) ?
HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED;
if (!delegate_->SetHDCPState(this_id, new_desired_state))
return false;
break;
}
case OUTPUT_TYPE_INTERNAL:
case OUTPUT_TYPE_VGA:
case OUTPUT_TYPE_NETWORK:
// No protections for these types. Do nothing.
break;
case OUTPUT_TYPE_NONE:
NOTREACHED();
break;
}
}
return true;
}
OutputConfigurator::OutputProtectionClientId
OutputConfigurator::RegisterOutputProtectionClient() {
if (!configure_display_)
return kInvalidClientId;
return next_output_protection_client_id_++;
}
void OutputConfigurator::UnregisterOutputProtectionClient(
OutputProtectionClientId client_id) {
client_protection_requests_.erase(client_id);
DisplayProtections protections;
for (ProtectionRequests::const_iterator it =
client_protection_requests_.begin();
it != client_protection_requests_.end();
++it) {
for (DisplayProtections::const_iterator it2 = it->second.begin();
it2 != it->second.end(); ++it2) {
protections[it2->first] |= it2->second;
}
}
ApplyProtections(protections);
}
bool OutputConfigurator::QueryOutputProtectionStatus(
OutputProtectionClientId client_id,
int64 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 (std::vector<OutputSnapshot>::const_iterator it = cached_outputs_.begin();
it != cached_outputs_.end(); ++it) {
RROutput this_id = it->output;
if (it->display_id != display_id)
continue;
*link_mask |= it->type;
switch (it->type) {
case OUTPUT_TYPE_UNKNOWN:
return false;
// DisplayPort, DVI, and HDMI all support HDCP.
case OUTPUT_TYPE_DISPLAYPORT:
case OUTPUT_TYPE_DVI:
case OUTPUT_TYPE_HDMI: {
HDCPState state;
if (!delegate_->GetHDCPState(this_id, &state))
return false;
if (state == HDCP_STATE_ENABLED)
enabled |= OUTPUT_PROTECTION_METHOD_HDCP;
else
unfulfilled |= OUTPUT_PROTECTION_METHOD_HDCP;
break;
}
case OUTPUT_TYPE_INTERNAL:
case OUTPUT_TYPE_VGA:
case OUTPUT_TYPE_NETWORK:
// No protections for these types. Do nothing.
break;
case OUTPUT_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 OutputConfigurator::EnableOutputProtection(
OutputProtectionClientId client_id,
int64 display_id,
uint32_t desired_method_mask) {
if (!configure_display_)
return false;
DisplayProtections protections;
for (ProtectionRequests::const_iterator it =
client_protection_requests_.begin();
it != client_protection_requests_.end();
++it) {
for (DisplayProtections::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 == OUTPUT_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;
}
void OutputConfigurator::Stop() {
configure_display_ = false;
}
bool OutputConfigurator::SetDisplayPower(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;
delegate_->GrabServer();
UpdateCachedOutputs();
const OutputState new_state = ChooseOutputState(power_state);
bool attempted_change = false;
bool success = false;
bool only_if_single_internal_display =
flags & kSetDisplayPowerOnlyIfSingleInternalDisplay;
bool single_internal_display =
cached_outputs_.size() == 1 && cached_outputs_[0].is_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 != DISPLAY_POWER_ALL_OFF)
delegate_->ForceDPMSOn();
}
delegate_->UngrabServer();
if (attempted_change)
NotifyObservers(success, new_state);
return true;
}
bool OutputConfigurator::SetDisplayMode(OutputState new_state) {
if (!configure_display_)
return false;
VLOG(1) << "SetDisplayMode: state=" << OutputStateToString(new_state);
if (output_state_ == new_state) {
// Cancel software mirroring if the state is moving from
// STATE_DUAL_EXTENDED to STATE_DUAL_EXTENDED.
if (mirroring_controller_ && new_state == STATE_DUAL_EXTENDED)
mirroring_controller_->SetSoftwareMirroring(false);
NotifyObservers(true, new_state);
return true;
}
delegate_->GrabServer();
UpdateCachedOutputs();
const bool success = EnterStateOrFallBackToSoftwareMirroring(
new_state, power_state_);
delegate_->UngrabServer();
NotifyObservers(success, new_state);
return success;
}
bool OutputConfigurator::Dispatch(const base::NativeEvent& event) {
if (!configure_display_)
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;
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;
}
base::EventStatus OutputConfigurator::WillProcessEvent(
const base::NativeEvent& event) {
// XI_HierarchyChanged events are special. There is no window associated with
// these events. So process them directly from here.
if (configure_display_ && event->type == GenericEvent &&
event->xgeneric.evtype == XI_HierarchyChanged) {
VLOG(1) << "Received XI_HierarchyChanged event";
// Defer configuring outputs to not stall event processing.
// This also takes care of same event being received twice.
ScheduleConfigureOutputs();
}
return base::EVENT_CONTINUE;
}
void OutputConfigurator::DidProcessEvent(const base::NativeEvent& event) {
}
void OutputConfigurator::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void OutputConfigurator::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void OutputConfigurator::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_ == 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();
}
}
void OutputConfigurator::ResumeDisplays() {
// Force probing to ensure that we pick up any changes that were made
// while the system was suspended.
SetDisplayPower(power_state_, kSetDisplayPowerForceProbe);
}
void OutputConfigurator::ScheduleConfigureOutputs() {
if (configure_timer_.get()) {
configure_timer_->Reset();
} else {
configure_timer_.reset(new base::OneShotTimer<OutputConfigurator>());
configure_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
this,
&OutputConfigurator::ConfigureOutputs);
}
}
void OutputConfigurator::UpdateCachedOutputs() {
cached_outputs_ = delegate_->GetOutputs();
// Set |selected_mode| fields.
for (size_t i = 0; i < cached_outputs_.size(); ++i) {
OutputSnapshot* output = &cached_outputs_[i];
if (output->has_display_id) {
int width = 0, height = 0;
if (state_controller_ &&
state_controller_->GetResolutionForDisplayId(
output->display_id, &width, &height)) {
output->selected_mode =
FindOutputModeMatchingSize(*output, width, height);
}
}
// Fall back to native mode.
if (output->selected_mode == None)
output->selected_mode = output->native_mode;
}
// Set |mirror_mode| fields.
if (cached_outputs_.size() == 2) {
bool one_is_internal = cached_outputs_[0].is_internal;
bool two_is_internal = cached_outputs_[1].is_internal;
int internal_outputs = (one_is_internal ? 1 : 0) +
(two_is_internal ? 1 : 0);
DCHECK_LT(internal_outputs, 2);
LOG_IF(WARNING, internal_outputs == 2)
<< "Two internal outputs detected.";
bool can_mirror = false;
for (int attempt = 0; !can_mirror && attempt < 2; ++attempt) {
// Try preserving external output's aspect ratio on the first attempt.
// If that fails, fall back to the highest matching resolution.
bool preserve_aspect = attempt == 0;
if (internal_outputs == 1) {
if (one_is_internal) {
can_mirror = FindMirrorMode(&cached_outputs_[0], &cached_outputs_[1],
is_panel_fitting_enabled_, preserve_aspect);
} else {
DCHECK(two_is_internal);
can_mirror = FindMirrorMode(&cached_outputs_[1], &cached_outputs_[0],
is_panel_fitting_enabled_, preserve_aspect);
}
} else { // if (internal_outputs == 0)
// No panel fitting for external outputs, so fall back to exact match.
can_mirror = FindMirrorMode(&cached_outputs_[0], &cached_outputs_[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_outputs_[1], &cached_outputs_[0],
false, preserve_aspect);
}
}
}
}
}
bool OutputConfigurator::FindMirrorMode(OutputSnapshot* internal_output,
OutputSnapshot* external_output,
bool try_panel_fitting,
bool preserve_aspect) {
const ModeInfo* internal_native_info =
GetModeInfo(*internal_output, internal_output->native_mode);
const ModeInfo* external_native_info =
GetModeInfo(*external_output, external_output->native_mode);
if (!internal_native_info || !external_native_info)
return false;
// Check if some external output resolution can be mirrored on internal.
// Prefer the modes in the order that X sorts them, assuming this is the order
// in which they look better on the monitor.
for (ModeInfoMap::const_iterator external_it =
external_output->mode_infos.begin();
external_it != external_output->mode_infos.end(); ++external_it) {
const ModeInfo& external_info = external_it->second;
bool is_native_aspect_ratio =
external_native_info->width * external_info.height ==
external_native_info->height * external_info.width;
if (preserve_aspect && !is_native_aspect_ratio)
continue; // Allow only aspect ratio preserving modes for mirroring.
// Try finding an exact match.
for (ModeInfoMap::const_iterator internal_it =
internal_output->mode_infos.begin();
internal_it != internal_output->mode_infos.end(); ++internal_it) {
const ModeInfo& internal_info = internal_it->second;
if (internal_info.width == external_info.width &&
internal_info.height == external_info.height &&
internal_info.interlaced == external_info.interlaced) {
internal_output->mirror_mode = internal_it->first;
external_output->mirror_mode = external_it->first;
return true; // Mirror mode found.
}
}
// Try to create a matching internal output 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->width >= external_info.width &&
internal_native_info->height >= external_info.height &&
!external_info.interlaced;
if (can_fit) {
RRMode mode = external_it->first;
delegate_->AddOutputMode(internal_output->output, mode);
internal_output->mode_infos.insert(std::make_pair(mode, external_info));
internal_output->mirror_mode = mode;
external_output->mirror_mode = mode;
return true; // Mirror mode created.
}
}
}
return false;
}
void OutputConfigurator::ConfigureOutputs() {
configure_timer_.reset();
delegate_->GrabServer();
UpdateCachedOutputs();
const OutputState new_state = ChooseOutputState(power_state_);
const bool success = EnterStateOrFallBackToSoftwareMirroring(
new_state, power_state_);
delegate_->UngrabServer();
NotifyObservers(success, new_state);
delegate_->SendProjectingStateToPowerManager(IsProjecting(cached_outputs_));
}
void OutputConfigurator::NotifyObservers(bool success,
OutputState attempted_state) {
if (success) {
FOR_EACH_OBSERVER(Observer, observers_,
OnDisplayModeChanged(cached_outputs_));
} else {
FOR_EACH_OBSERVER(Observer, observers_,
OnDisplayModeChangeFailed(attempted_state));
}
}
bool OutputConfigurator::EnterStateOrFallBackToSoftwareMirroring(
OutputState output_state,
DisplayPowerState power_state) {
bool success = EnterState(output_state, power_state);
if (mirroring_controller_) {
bool enable_software_mirroring = false;
if (!success && output_state == STATE_DUAL_MIRROR) {
if (output_state_ != STATE_DUAL_EXTENDED || power_state_ != power_state)
EnterState(STATE_DUAL_EXTENDED, power_state);
enable_software_mirroring = success =
output_state_ == STATE_DUAL_EXTENDED;
}
mirroring_controller_->SetSoftwareMirroring(enable_software_mirroring);
}
return success;
}
bool OutputConfigurator::EnterState(
OutputState output_state,
DisplayPowerState power_state) {
std::vector<bool> output_power;
int num_on_outputs = GetOutputPower(
cached_outputs_, power_state, &output_power);
VLOG(1) << "EnterState: output=" << OutputStateToString(output_state)
<< " power=" << DisplayPowerStateToString(power_state);
// Framebuffer dimensions.
int width = 0, height = 0;
std::vector<OutputSnapshot> updated_outputs = cached_outputs_;
switch (output_state) {
case STATE_INVALID:
NOTREACHED() << "Ignoring request to enter invalid state with "
<< updated_outputs.size() << " connected output(s)";
return false;
case STATE_HEADLESS:
if (updated_outputs.size() != 0) {
LOG(WARNING) << "Ignoring request to enter headless mode with "
<< updated_outputs.size() << " connected output(s)";
return false;
}
break;
case STATE_SINGLE: {
// If there are multiple outputs connected, only one should be turned on.
if (updated_outputs.size() != 1 && num_on_outputs != 1) {
LOG(WARNING) << "Ignoring request to enter single mode with "
<< updated_outputs.size() << " connected outputs and "
<< num_on_outputs << " turned on";
return false;
}
for (size_t i = 0; i < updated_outputs.size(); ++i) {
OutputSnapshot* output = &updated_outputs[i];
output->x = 0;
output->y = 0;
output->current_mode = output_power[i] ? output->selected_mode : None;
if (output_power[i] || updated_outputs.size() == 1) {
const ModeInfo* mode_info =
GetModeInfo(*output, output->selected_mode);
if (!mode_info)
return false;
if (mode_info->width == 1024 && mode_info->height == 768) {
VLOG(1) << "Potentially misdetecting display(1024x768):"
<< " outputs size=" << updated_outputs.size()
<< ", num_on_outputs=" << num_on_outputs
<< ", current size:" << width << "x" << height
<< ", i=" << i
<< ", output=" << OutputSnapshotToString(output)
<< ", mode_info=" << ModeInfoToString(mode_info);
}
width = mode_info->width;
height = mode_info->height;
}
}
break;
}
case STATE_DUAL_MIRROR: {
if (updated_outputs.size() != 2 ||
(num_on_outputs != 0 && num_on_outputs != 2)) {
LOG(WARNING) << "Ignoring request to enter mirrored mode with "
<< updated_outputs.size() << " connected output(s) and "
<< num_on_outputs << " turned on";
return false;
}
if (!updated_outputs[0].mirror_mode)
return false;
const ModeInfo* mode_info =
GetModeInfo(updated_outputs[0], updated_outputs[0].mirror_mode);
if (!mode_info)
return false;
width = mode_info->width;
height = mode_info->height;
for (size_t i = 0; i < updated_outputs.size(); ++i) {
OutputSnapshot* output = &updated_outputs[i];
output->x = 0;
output->y = 0;
output->current_mode = output_power[i] ? output->mirror_mode : None;
if (output->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 (output->mirror_mode != output->native_mode &&
output->is_aspect_preserving_scaling) {
output->transform = GetMirrorModeCTM(*output);
mirrored_display_area_ratio_map_[output->touch_device_id] =
GetMirroredDisplayAreaRatio(*output);
}
}
}
break;
}
case STATE_DUAL_EXTENDED: {
if (updated_outputs.size() != 2 ||
(num_on_outputs != 0 && num_on_outputs != 2)) {
LOG(WARNING) << "Ignoring request to enter extended mode with "
<< updated_outputs.size() << " connected output(s) and "
<< num_on_outputs << " turned on";
return false;
}
for (size_t i = 0; i < updated_outputs.size(); ++i) {
OutputSnapshot* output = &updated_outputs[i];
output->x = 0;
output->y = height ? height + kVerticalGap : 0;
output->current_mode = output_power[i] ? output->selected_mode : None;
// 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.
const ModeInfo* mode_info =
GetModeInfo(updated_outputs[i], updated_outputs[i].selected_mode);
if (!mode_info)
return false;
width = std::max<int>(width, mode_info->width);
height += (height ? kVerticalGap : 0) + mode_info->height;
}
for (size_t i = 0; i < updated_outputs.size(); ++i) {
OutputSnapshot* output = &updated_outputs[i];
if (output->touch_device_id)
output->transform = GetExtendedModeCTM(*output, width, height);
}
break;
}
}
// Finally, apply the desired changes.
DCHECK_EQ(cached_outputs_.size(), updated_outputs.size());
if (!updated_outputs.empty()) {
delegate_->CreateFrameBuffer(width, height, updated_outputs);
for (size_t i = 0; i < updated_outputs.size(); ++i) {
const OutputSnapshot& output = updated_outputs[i];
if (delegate_->ConfigureCrtc(output.crtc, output.current_mode,
output.output, output.x, output.y)) {
if (output.touch_device_id)
delegate_->ConfigureCTM(output.touch_device_id, output.transform);
cached_outputs_[i] = updated_outputs[i];
} else {
LOG(WARNING) << "Unable to configure CRTC " << output.crtc << ":"
<< " mode=" << output.current_mode
<< " output=" << output.output
<< " x=" << output.x
<< " y=" << output.y;
}
}
}
output_state_ = output_state;
power_state_ = power_state;
return true;
}
OutputState OutputConfigurator::ChooseOutputState(
DisplayPowerState power_state) const {
int num_on_outputs = GetOutputPower(cached_outputs_, power_state, NULL);
switch (cached_outputs_.size()) {
case 0:
return STATE_HEADLESS;
case 1:
return STATE_SINGLE;
case 2: {
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<int64> display_ids;
for (size_t i = 0; i < cached_outputs_.size(); ++i) {
// If display id isn't available, switch to extended mode.
if (!cached_outputs_[i].has_display_id)
return STATE_DUAL_EXTENDED;
display_ids.push_back(cached_outputs_[i].display_id);
}
return state_controller_->GetStateForDisplayIds(display_ids);
}
}
default:
NOTREACHED();
}
return STATE_INVALID;
}
OutputConfigurator::CoordinateTransformation
OutputConfigurator::GetMirrorModeCTM(
const OutputConfigurator::OutputSnapshot& output) {
CoordinateTransformation ctm; // Default to identity
const ModeInfo* native_mode_info = GetModeInfo(output, output.native_mode);
const ModeInfo* mirror_mode_info = GetModeInfo(output, output.mirror_mode);
if (!native_mode_info || !mirror_mode_info ||
native_mode_info->height == 0 || mirror_mode_info->height == 0 ||
native_mode_info->width == 0 || mirror_mode_info->width == 0)
return ctm;
float native_mode_ar = static_cast<float>(native_mode_info->width) /
static_cast<float>(native_mode_info->height);
float mirror_mode_ar = static_cast<float>(mirror_mode_info->width) /
static_cast<float>(mirror_mode_info->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
}
OutputConfigurator::CoordinateTransformation
OutputConfigurator::GetExtendedModeCTM(
const OutputConfigurator::OutputSnapshot& output,
int framebuffer_width,
int framebuffer_height) {
CoordinateTransformation ctm; // Default to identity
const ModeInfo* mode_info = GetModeInfo(output, output.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 OutputConfiguratorTest.CTMForMultiScreens.
ctm.x_scale =
static_cast<float>(mode_info->width - 1) / (framebuffer_width - 1);
ctm.x_offset = static_cast<float>(output.x) / (framebuffer_width - 1);
ctm.y_scale =
static_cast<float>(mode_info->height - 1) / (framebuffer_height - 1);
ctm.y_offset = static_cast<float>(output.y) / (framebuffer_height - 1);
return ctm;
}
float OutputConfigurator::GetMirroredDisplayAreaRatio(
const OutputConfigurator::OutputSnapshot& output) {
float area_ratio = 1.0f;
const ModeInfo* native_mode_info = GetModeInfo(output, output.native_mode);
const ModeInfo* mirror_mode_info = GetModeInfo(output, output.mirror_mode);
if (!native_mode_info || !mirror_mode_info ||
native_mode_info->height == 0 || mirror_mode_info->height == 0 ||
native_mode_info->width == 0 || mirror_mode_info->width == 0)
return area_ratio;
float width_ratio = static_cast<float>(mirror_mode_info->width) /
static_cast<float>(native_mode_info->width);
float height_ratio = static_cast<float>(mirror_mode_info->height) /
static_cast<float>(native_mode_info->height);
area_ratio = width_ratio * height_ratio;
return area_ratio;
}
} // namespace chromeos