// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/display/manager/display_configurator.h"
#include <cstddef>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/syslog_logging.h"
#include "base/system/sys_info.h"
#include "base/time/time.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/display.h"
#include "ui/display/display_features.h"
#include "ui/display/display_switches.h"
#include "ui/display/manager/content_protection_manager.h"
#include "ui/display/manager/display_layout_manager.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/display/manager/update_display_configuration_task.h"
#include "ui/display/manager/util/display_manager_util.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/gamma_ramp_rgb_entry.h"
#include "ui/display/types/native_display_delegate.h"
#include "ui/display/util/display_util.h"
namespace display {
namespace {
typedef std::vector<const DisplayMode*> DisplayModeList;
using RefreshRateOverrideMap = DisplayConfigurator::RefreshRateOverrideMap;
struct DisplayState {
raw_ptr<DisplaySnapshot> display = nullptr; // Not owned.
// User-selected mode for the display.
raw_ptr<const DisplayMode> selected_mode = nullptr;
// Mode used when displaying the same desktop on multiple displays.
raw_ptr<const DisplayMode> mirror_mode = nullptr;
};
// Returns whether |display_id| can be found in |display_list|,
bool IsDisplayIdInDisplayStateList(
int64_t display_id,
const DisplayConfigurator::DisplayStateList& display_list) {
return base::Contains(display_list, display_id, &DisplaySnapshot::display_id);
}
// Returns true if a platform native |mode| is equal to a |managed_mode|.
bool AreModesEqual(const DisplayMode& mode,
const ManagedDisplayMode& managed_mode) {
return mode.size() == managed_mode.size() &&
mode.refresh_rate() == managed_mode.refresh_rate() &&
mode.is_interlaced() == managed_mode.is_interlaced();
}
// Finds and returns a pointer to a platform native mode in the given |display|
// snapshot's modes which exactly matches the given |managed_mode|. Returns
// nullptr if nothing was found.
const DisplayMode* FindExactMatchingMode(
const DisplaySnapshot& display,
const ManagedDisplayMode& managed_mode) {
if (managed_mode.native()) {
return display.native_mode() &&
AreModesEqual(*display.native_mode(), managed_mode)
? display.native_mode()
: nullptr;
}
for (const std::unique_ptr<const DisplayMode>& mode : display.modes()) {
if (AreModesEqual(*mode, managed_mode))
return mode.get();
}
return nullptr;
}
} // namespace
const int DisplayConfigurator::kSetDisplayPowerNoFlags = 0;
const int DisplayConfigurator::kSetDisplayPowerForceProbe = 1 << 0;
const int DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay =
1 << 1;
////////////////////////////////////////////////////////////////////////////////
// DisplayConfigurator::DisplayLayoutManagerImpl implementation
class DisplayConfigurator::DisplayLayoutManagerImpl
: public DisplayLayoutManager {
public:
explicit DisplayLayoutManagerImpl(DisplayConfigurator* configurator);
DisplayLayoutManagerImpl(const DisplayLayoutManagerImpl&) = delete;
DisplayLayoutManagerImpl& operator=(const DisplayLayoutManagerImpl&) = delete;
~DisplayLayoutManagerImpl() override;
// DisplayLayoutManager:
SoftwareMirroringController* GetSoftwareMirroringController() const override;
StateController* GetStateController() const override;
MultipleDisplayState GetDisplayState() const override;
chromeos::DisplayPowerState GetPowerState() const override;
bool GetDisplayLayout(
const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
MultipleDisplayState new_display_state,
chromeos::DisplayPowerState new_power_state,
const base::flat_set<int64_t>& new_vrr_enabled_state,
std::vector<DisplayConfigureRequest>* requests) const override;
DisplayStateList GetDisplayStates() const override;
bool IsMirroring() const override;
void set_configure_displays(bool configure_displays) {
configure_displays_ = configure_displays;
}
private:
// Parses the |displays| into a list of DisplayStates. This effectively adds
// |mirror_mode| and |selected_mode| to the returned results.
// TODO(dnicoara): Break this into GetSelectedMode() and GetMirrorMode() and
// remove DisplayState.
std::vector<DisplayState> ParseDisplays(
const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays)
const;
const DisplayMode* GetUserSelectedMode(const DisplaySnapshot& display) const;
// Return true if all displays are on the same device.
bool AllDisplaysOnSameDevice(
const std::vector<DisplayState*>& displays) const;
// Return true if all displays have display mode.
bool AllDisplaysHaveDisplayMode(
const std::vector<DisplayState*>& displays) const;
// Return true if |mode| has the same aspect ratio as the native mode of
// |display|.
bool HasSameAspectRatioAsNativeMode(const DisplaySnapshot* display,
const DisplayMode* mode) const;
// Helper method for ParseDisplays() that initializes the passed-in displays'
// |mirror_mode| fields by looking for a matching mode among these displays'
// mode list. |preserve_native_aspect_ratio| limits the search only to the
// modes having the native aspect ratio of each external display.
bool FindExactMatchingMirrorMode(const std::vector<DisplayState*>& displays,
bool preserve_native_aspect_ratio) const;
raw_ptr<DisplayConfigurator> configurator_; // Not owned.
bool configure_displays_ = false;
};
DisplayConfigurator::DisplayLayoutManagerImpl::DisplayLayoutManagerImpl(
DisplayConfigurator* configurator)
: configurator_(configurator) {}
DisplayConfigurator::DisplayLayoutManagerImpl::~DisplayLayoutManagerImpl() {}
DisplayConfigurator::SoftwareMirroringController*
DisplayConfigurator::DisplayLayoutManagerImpl::GetSoftwareMirroringController()
const {
return configurator_->mirroring_controller_;
}
DisplayConfigurator::StateController*
DisplayConfigurator::DisplayLayoutManagerImpl::GetStateController() const {
return configurator_->state_controller_;
}
MultipleDisplayState
DisplayConfigurator::DisplayLayoutManagerImpl::GetDisplayState() const {
return configurator_->current_display_state_;
}
chromeos::DisplayPowerState
DisplayConfigurator::DisplayLayoutManagerImpl::GetPowerState() const {
return configurator_->current_power_state_;
}
std::vector<DisplayState>
DisplayConfigurator::DisplayLayoutManagerImpl::ParseDisplays(
const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& snapshots)
const {
std::vector<DisplayState> cached_displays;
for (display::DisplaySnapshot* snapshot : snapshots) {
DisplayState display_state;
display_state.display = snapshot;
display_state.selected_mode = GetUserSelectedMode(*snapshot);
cached_displays.push_back(display_state);
}
// Hardware mirroring is now disabled by default until it is decided whether
// to permanently remove hardware mirroring support. See crbug.com/1161556 for
// details.
if (!features::IsHardwareMirrorModeEnabled())
return cached_displays;
// Hardware mirroring doesn't work on desktop-linux Chrome OS's fake displays.
// Skip mirror mode setup in that case to fall back on software mirroring.
if (!configure_displays_) {
return cached_displays;
}
if (cached_displays.size() <= 1)
return cached_displays;
std::vector<DisplayState*> displays;
int num_internal_displays = 0;
for (auto& display : cached_displays) {
if (display.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
++num_internal_displays;
displays.emplace_back(&display);
}
CHECK_LT(num_internal_displays, 2);
LOG_IF(WARNING, num_internal_displays >= 2)
<< "At least two internal displays detected.";
// Hardware mirroring doesn't work among displays on different devices. In
// this case we revert to software mirroring.
if (!AllDisplaysOnSameDevice(displays))
return cached_displays;
// Hardware mirroring doesn't work for displays that do not have display
// mode. In this case we revert to software mirroring.
if (!AllDisplaysHaveDisplayMode(displays))
return cached_displays;
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;
can_mirror = FindExactMatchingMirrorMode(displays, preserve_aspect);
}
return cached_displays;
}
bool DisplayConfigurator::DisplayLayoutManagerImpl::GetDisplayLayout(
const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
MultipleDisplayState new_display_state,
chromeos::DisplayPowerState new_power_state,
const base::flat_set<int64_t>& new_vrr_enabled_state,
std::vector<DisplayConfigureRequest>* requests) const {
std::vector<DisplayState> states = ParseDisplays(displays);
std::vector<bool> display_power;
int num_on_displays =
GetDisplayPower(displays, new_power_state, &display_power);
// TODO(aswolfers): Log vrr state.
VLOG(1) << "EnterState: display="
<< MultipleDisplayStateToString(new_display_state)
<< " power=" << DisplayPowerStateToString(new_power_state);
// Framebuffer dimensions.
gfx::Size size;
for (display::DisplaySnapshot* display : displays) {
const bool enable_vrr =
display->IsVrrCapable() &&
(::features::IsVariableRefreshRateAlwaysOn() ||
new_vrr_enabled_state.contains(display->display_id()));
requests->emplace_back(display, display->current_mode(), gfx::Point(),
enable_vrr);
}
switch (new_display_state) {
case MULTIPLE_DISPLAY_STATE_INVALID:
NOTREACHED_IN_MIGRATION()
<< "Ignoring request to enter invalid state with " << displays.size()
<< " connected display(s)";
return false;
case MULTIPLE_DISPLAY_STATE_HEADLESS:
if (displays.size() != 0) {
LOG(WARNING) << "Ignoring request to enter headless mode with "
<< 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 (displays.size() != 1 && num_on_displays != 1) {
LOG(WARNING) << "Ignoring request to enter single mode with "
<< displays.size() << " connected displays and "
<< num_on_displays << " turned on";
return false;
}
for (size_t i = 0; i < states.size(); ++i) {
const DisplayState* state = &states[i];
(*requests)[i].mode = display_power[i] && state->selected_mode
? state->selected_mode->Clone()
: nullptr;
if (display_power[i] || states.size() == 1) {
const DisplayMode* mode_info = state->selected_mode;
if (!mode_info) {
LOG(WARNING) << "No selected mode when configuring display: "
<< state->display->ToString();
return false;
}
if (mode_info->size() == gfx::Size(1024, 768)) {
VLOG(1) << "Potentially misdetecting display(1024x768):"
<< " displays size=" << states.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_MULTI_MIRROR: {
if (configurator_->mirroring_controller_->IsSoftwareMirroringEnforced()) {
LOG(WARNING) << "Ignoring request to enter hardware mirror mode "
"because software mirroring is enforced";
return false;
}
const bool can_set_mirror_mode =
states.size() > 1 && num_on_displays != 1;
if (!can_set_mirror_mode) {
LOG(WARNING) << "Ignoring request to enter mirrored mode with "
<< states.size() << " connected display(s) and "
<< num_on_displays << " turned on";
return false;
}
const DisplayMode* mode_info = states[0].mirror_mode;
if (!mode_info) {
SYSLOG(INFO) << "Either hardware mirroring was disabled or no common "
"mode between the available displays was found to "
"support it. Using software mirroring instead.";
return false;
}
size = mode_info->size();
for (size_t i = 0; i < states.size(); ++i) {
const DisplayState* state = &states[i];
(*requests)[i].mode = display_power[i] && state->mirror_mode
? state->mirror_mode->Clone()
: nullptr;
}
break;
}
case MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED: {
if (states.size() < 2) {
LOG(WARNING) << "Ignoring request to enter extended mode with "
<< states.size() << " connected display(s) and "
<< num_on_displays << " turned on";
return false;
}
for (size_t i = 0; i < states.size(); ++i) {
const DisplayState* state = &states[i];
(*requests)[i].origin.set_y(size.height() ? size.height() + kVerticalGap
: 0);
(*requests)[i].mode = display_power[i] && state->selected_mode
? state->selected_mode->Clone()
: nullptr;
// 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 = states[i].selected_mode;
if (!mode_info) {
LOG(WARNING) << "No selected mode when configuring display: "
<< state->display->ToString();
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());
}
break;
}
}
DCHECK(new_display_state == MULTIPLE_DISPLAY_STATE_HEADLESS ||
!size.IsEmpty());
return true;
}
DisplayConfigurator::DisplayStateList
DisplayConfigurator::DisplayLayoutManagerImpl::GetDisplayStates() const {
return configurator_->cached_displays();
}
bool DisplayConfigurator::DisplayLayoutManagerImpl::IsMirroring() const {
if (GetDisplayState() == MULTIPLE_DISPLAY_STATE_MULTI_MIRROR)
return true;
return GetSoftwareMirroringController() &&
GetSoftwareMirroringController()->SoftwareMirroringEnabled();
}
const DisplayMode*
DisplayConfigurator::DisplayLayoutManagerImpl::GetUserSelectedMode(
const DisplaySnapshot& display) const {
const DisplayMode* selected_mode = nullptr;
auto* state_controller = GetStateController();
if (state_controller) {
ManagedDisplayMode mode;
const bool mode_found = state_controller->GetSelectedModeForDisplayId(
display.display_id(), &mode);
if (display::features::IsListAllDisplayModesEnabled()) {
// When selecting any arbitrary display mode is enabled, we don't try to
// be smart about finding the best mode matching the user-selected display
// size, rather we find an exact match to the selected display mode.
selected_mode =
mode_found ? FindExactMatchingMode(display, mode) : nullptr;
} else {
selected_mode = mode_found
? FindDisplayModeMatchingSize(display, mode.size())
: nullptr;
}
}
// Fall back to native mode.
return selected_mode ? selected_mode : display.native_mode();
}
bool DisplayConfigurator::DisplayLayoutManagerImpl::AllDisplaysOnSameDevice(
const std::vector<DisplayState*>& displays) const {
DisplayState* first_display = displays.front();
for (auto it = displays.begin() + 1; it != displays.end(); ++it) {
if (first_display->display->sys_path() != (*it)->display->sys_path())
return false;
}
return true;
}
bool DisplayConfigurator::DisplayLayoutManagerImpl::AllDisplaysHaveDisplayMode(
const std::vector<DisplayState*>& displays) const {
for (const auto* display : displays) {
if (display->display->modes().empty())
return false;
}
return true;
}
bool DisplayConfigurator::DisplayLayoutManagerImpl::
HasSameAspectRatioAsNativeMode(const DisplaySnapshot* display,
const DisplayMode* mode) const {
return display->native_mode()->size().width() * mode->size().height() ==
display->native_mode()->size().height() * mode->size().width();
}
bool DisplayConfigurator::DisplayLayoutManagerImpl::FindExactMatchingMirrorMode(
const std::vector<DisplayState*>& displays,
bool preserve_native_aspect_ratio) const {
DCHECK(displays.size() > 0);
// Put each display's display modes in |mode_lists| and sort the display modes
// for each display by size area and refresh rate.
std::vector<std::vector<const DisplayMode*>> mode_lists;
for (auto* d : displays) {
std::vector<const DisplayMode*> mode_list;
for (auto& mode : d->display->modes()) {
if (d->display->type() != DISPLAY_CONNECTION_TYPE_INTERNAL &&
preserve_native_aspect_ratio &&
!HasSameAspectRatioAsNativeMode(d->display, mode.get())) {
// Only preserve aspect ratio for external displays.
continue;
}
mode_list.emplace_back(mode.get());
}
std::sort(
mode_list.begin(), mode_list.end(),
[](const DisplayMode* const& a, const DisplayMode* const& b) -> bool {
if (a->size().GetArea() > b->size().GetArea())
return true;
if (a->size().GetArea() < b->size().GetArea())
return false;
return a->refresh_rate() > b->refresh_rate();
});
mode_lists.emplace_back(mode_list);
}
std::vector<std::vector<const DisplayMode*>::iterator> it_list;
for (auto& mode_list : mode_lists)
it_list.emplace_back(mode_list.begin());
// Find matching display modes among all displays and use them as mirror
// mirror modes.
for (; it_list[0] != mode_lists[0].end(); ++it_list[0]) {
bool found = true;
for (size_t i = 1; i < mode_lists.size(); ++i) {
while (it_list[i] != mode_lists[i].end() &&
(*it_list[i])->size().GetArea() >=
(*it_list[0])->size().GetArea()) {
if ((*it_list[i])->size() == (*it_list[0])->size() &&
(*it_list[i])->is_interlaced() == (*it_list[0])->is_interlaced()) {
displays[i]->mirror_mode = *it_list[i];
break;
}
++it_list[i];
}
if (!displays[i]->mirror_mode) {
found = false;
break;
}
}
if (found) {
displays[0]->mirror_mode = *it_list[0];
return true;
}
for (auto* d : displays)
d->mirror_mode = nullptr;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// DisplayConfigurator implementation
// static
const DisplayMode* DisplayConfigurator::FindDisplayModeMatchingSize(
const DisplaySnapshot& display,
const gfx::Size& size) {
const DisplayMode* best_mode = NULL;
for (const std::unique_ptr<const DisplayMode>& mode : display.modes()) {
if (mode->size() != size)
continue;
if (mode.get() == display.native_mode()) {
best_mode = mode.get();
break;
}
if (!best_mode) {
best_mode = mode.get();
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.get();
continue;
}
}
if (mode->refresh_rate() < best_mode->refresh_rate())
continue;
best_mode = mode.get();
}
return best_mode;
}
DisplayConfigurator::DisplayConfigurator()
: state_controller_(nullptr),
mirroring_controller_(nullptr),
is_panel_fitting_enabled_(false),
configure_displays_(base::SysInfo::IsRunningOnChromeOS()),
current_display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
current_power_state_(chromeos::DISPLAY_POWER_ALL_ON),
requested_display_state_(MULTIPLE_DISPLAY_STATE_INVALID),
pending_power_state_(chromeos::DISPLAY_POWER_ALL_ON),
has_pending_power_state_(false),
pending_power_flags_(kSetDisplayPowerNoFlags),
force_configure_(false),
display_externally_controlled_(false),
display_control_changing_(false),
displays_suspended_(false),
layout_manager_(new DisplayLayoutManagerImpl(this)),
content_protection_manager_(new ContentProtectionManager(
layout_manager_.get(),
base::BindRepeating(&DisplayConfigurator::configurator_disabled,
base::Unretained(this)))),
has_unassociated_display_(false) {
AddObserver(content_protection_manager_.get());
}
DisplayConfigurator::~DisplayConfigurator() {
RemoveObserver(content_protection_manager_.get());
if (native_display_delegate_)
native_display_delegate_->RemoveObserver(this);
CallAndClearInProgressCallbacks(false);
CallAndClearQueuedCallbacks(false);
}
void DisplayConfigurator::SetDelegateForTesting(
std::unique_ptr<NativeDisplayDelegate> display_delegate) {
DCHECK(!native_display_delegate_);
native_display_delegate_ = std::move(display_delegate);
SetConfigureDisplays(true);
}
void DisplayConfigurator::SetInitialDisplayPower(
chromeos::DisplayPowerState power_state) {
if (requested_power_state_) {
// A new power state has already been requested so ignore the initial state.
return;
}
// Set the initial requested power state.
requested_power_state_ = power_state;
if (current_display_state_ == MULTIPLE_DISPLAY_STATE_INVALID) {
// DisplayConfigurator::OnConfigured has not been called yet so just set
// the current state and notify observers.
current_power_state_ = power_state;
NotifyPowerStateObservers();
return;
}
// DisplayConfigurator::OnConfigured has been called so update the current
// and pending states.
UpdatePowerState(power_state);
}
void DisplayConfigurator::InitializeDisplayPowerState() {
SetInitialDisplayPower(chromeos::DISPLAY_POWER_ALL_ON);
}
void DisplayConfigurator::Init(
std::unique_ptr<NativeDisplayDelegate> display_delegate,
bool is_panel_fitting_enabled) {
is_panel_fitting_enabled_ = is_panel_fitting_enabled;
if (configurator_disabled())
return;
// If the delegate is already initialized don't update it (For example, tests
// set their own delegates).
if (!native_display_delegate_)
native_display_delegate_ = std::move(display_delegate);
native_display_delegate_->AddObserver(this);
content_protection_manager_->set_native_display_delegate(
native_display_delegate_.get());
}
void DisplayConfigurator::SetConfigureDisplays(bool configure_displays) {
configure_displays_ = configure_displays;
layout_manager_->set_configure_displays(configure_displays);
}
void DisplayConfigurator::TakeControl(DisplayControlCallback callback) {
if (display_control_changing_) {
LOG(ERROR) << __func__
<< " failed. There is another RelinquishControl() or "
"TakeControl() call in progress.";
std::move(callback).Run(false);
return;
}
if (!display_externally_controlled_) {
LOG(ERROR) << __func__
<< " failed. Displays are not controlled externally.";
std::move(callback).Run(true);
return;
}
display_control_changing_ = true;
native_display_delegate_->TakeDisplayControl(
base::BindOnce(&DisplayConfigurator::OnDisplayControlTaken,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DisplayConfigurator::OnDisplayControlTaken(DisplayControlCallback callback,
bool success) {
display_control_changing_ = false;
display_externally_controlled_ = !success;
if (success) {
// Force a configuration since the display configuration may have changed.
force_configure_ = true;
if (requested_power_state_) {
// Restore the requested power state before releasing control.
SetDisplayPower(*requested_power_state_, kSetDisplayPowerNoFlags,
base::DoNothing());
}
}
std::move(callback).Run(success);
}
void DisplayConfigurator::RelinquishControl(DisplayControlCallback callback) {
if (display_control_changing_) {
LOG(ERROR) << __func__
<< " failed. There is another RelinquishControl() or "
"TakeControl() call in progress.";
std::move(callback).Run(false);
return;
}
if (display_externally_controlled_) {
LOG(ERROR) << __func__
<< " failed. Displays are already controlled externally.";
std::move(callback).Run(true);
return;
}
// For simplicity, just fail if in the middle of a display configuration.
if (configuration_task_) {
LOG(ERROR) << __func__
<< " failed. There is a display configuration in progress.";
std::move(callback).Run(false);
return;
}
display_control_changing_ = true;
// Turn off the displays before releasing control since we're no longer using
// them for output.
SetDisplayPowerInternal(
chromeos::DISPLAY_POWER_ALL_OFF, kSetDisplayPowerNoFlags,
base::BindOnce(&DisplayConfigurator::SendRelinquishDisplayControl,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DisplayConfigurator::GetSeamlessRefreshRates(
int64_t display_id,
GetSeamlessRefreshRatesCallback callback) {
native_display_delegate_->GetSeamlessRefreshRates(display_id,
std::move(callback));
}
void DisplayConfigurator::SendRelinquishDisplayControl(
DisplayControlCallback callback,
bool success) {
if (success) {
// Set the flag early such that an incoming configuration event won't start
// while we're releasing control of the displays.
display_externally_controlled_ = true;
native_display_delegate_->RelinquishDisplayControl(
base::BindOnce(&DisplayConfigurator::OnDisplayControlRelinquished,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
} else {
LOG(ERROR) << __func__
<< "failed. Failed to turn off all displays before "
"relinquishing control.";
display_control_changing_ = false;
std::move(callback).Run(false);
}
}
void DisplayConfigurator::OnDisplayControlRelinquished(
DisplayControlCallback callback,
bool success) {
display_control_changing_ = false;
display_externally_controlled_ = success;
if (!success) {
force_configure_ = true;
RunPendingConfiguration();
}
std::move(callback).Run(success);
}
void DisplayConfigurator::ForceInitialConfigure() {
if (configurator_disabled())
return;
DCHECK(native_display_delegate_);
native_display_delegate_->Initialize();
// ForceInitialConfigure should be the first configuration so there shouldn't
// be anything scheduled.
DCHECK(!configuration_task_);
configuration_task_ = std::make_unique<UpdateDisplayConfigurationTask>(
native_display_delegate_.get(), layout_manager_.get(),
requested_display_state_, GetRequestedPowerState(),
kSetDisplayPowerForceProbe, GetRequestedVrrState(),
GetRequestedRefreshRateOverrides(),
/*force_configure=*/true, kConfigurationTypeFull,
base::BindOnce(&DisplayConfigurator::OnConfigured,
weak_ptr_factory_.GetWeakPtr()));
configuration_task_->Run();
}
void DisplayConfigurator::SetColorTemperatureAdjustment(
int64_t display_id,
const ColorTemperatureAdjustment& cta) {
if (!IsDisplayIdInDisplayStateList(display_id, cached_displays_)) {
return;
}
native_display_delegate_->SetColorTemperatureAdjustment(display_id, cta);
}
void DisplayConfigurator::SetColorCalibration(
int64_t display_id,
const ColorCalibration& calibration) {
if (!IsDisplayIdInDisplayStateList(display_id, cached_displays_)) {
return;
}
native_display_delegate_->SetColorCalibration(display_id, calibration);
}
void DisplayConfigurator::SetPrivacyScreen(int64_t display_id,
bool enabled,
ConfigurationCallback callback) {
#if DCHECK_IS_ON()
DisplaySnapshot* internal_display = nullptr;
for (DisplaySnapshot* display : cached_displays_) {
if (display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL) {
internal_display = display;
break;
}
}
DCHECK(internal_display);
DCHECK_EQ(internal_display->display_id(), display_id);
DCHECK_NE(internal_display->privacy_screen_state(), kNotSupported);
DCHECK(internal_display->current_mode());
#endif
native_display_delegate_->SetPrivacyScreen(display_id, enabled,
std::move(callback));
}
chromeos::DisplayPowerState DisplayConfigurator::GetRequestedPowerState()
const {
return requested_power_state_.value_or(chromeos::DISPLAY_POWER_ALL_ON);
}
void DisplayConfigurator::PrepareForExit() {
configure_displays_ = false;
}
void DisplayConfigurator::SetDisplayPowerInternal(
chromeos::DisplayPowerState power_state,
int flags,
ConfigurationCallback callback) {
// Only skip if the current power state is the same and the latest requested
// power state is the same. If |pending_power_state_ != current_power_state_|
// then there is a current task pending or the last configuration failed. In
// either case request a new configuration to make sure the state is
// consistent with the expectations.
if (power_state == current_power_state_ &&
power_state == pending_power_state_ &&
!(flags & kSetDisplayPowerForceProbe)) {
std::move(callback).Run(true);
return;
}
pending_power_state_ = power_state;
has_pending_power_state_ = true;
pending_power_flags_ = flags;
queued_configuration_callbacks_.push_back(std::move(callback));
if (configure_timer_.IsRunning()) {
// If there is a configuration task scheduled, avoid performing
// configuration immediately. Instead reset the timer to wait for things to
// settle.
configure_timer_.Reset();
return;
}
RunPendingConfiguration();
}
void DisplayConfigurator::SetDisplayPower(
chromeos::DisplayPowerState power_state,
int flags,
ConfigurationCallback callback) {
if (configurator_disabled()) {
std::move(callback).Run(false);
return;
}
VLOG(1) << "SetDisplayPower: power_state="
<< DisplayPowerStateToString(power_state) << " flags=" << flags
<< ", configure timer="
<< (configure_timer_.IsRunning() ? "Running" : "Stopped");
requested_power_state_ = power_state;
SetDisplayPowerInternal(*requested_power_state_, flags, std::move(callback));
}
void DisplayConfigurator::SetMultipleDisplayState(
MultipleDisplayState new_state) {
if (configurator_disabled())
return;
VLOG(1) << "SetMultipleDisplayState: state="
<< MultipleDisplayStateToString(new_state);
if (current_display_state_ == new_state) {
// Cancel software mirroring if the state is moving from
// MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED to
// MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED.
if (mirroring_controller_ &&
new_state == MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED)
mirroring_controller_->SetSoftwareMirroring(false);
NotifyDisplayStateObservers(true, new_state);
return;
}
requested_display_state_ = new_state;
RunPendingConfiguration();
}
void DisplayConfigurator::OnConfigurationChanged() {
// Don't do anything if the displays are currently suspended. Instead we will
// probe and reconfigure the displays if necessary in ResumeDisplays().
if (displays_suspended_) {
VLOG(1) << "Displays are currently suspended. Not attempting to "
<< "reconfigure them.";
return;
}
// Configure displays with |kConfigureDelayMs| delay,
// so that time-consuming ConfigureDisplays() won't be called multiple times.
configure_timer_.Start(FROM_HERE, base::Milliseconds(kConfigureDelayMs), this,
&DisplayConfigurator::ConfigureDisplays);
}
void DisplayConfigurator::OnDisplaySnapshotsInvalidated() {
VLOG(1) << "Display snapshots invalidated.";
cached_displays_.clear();
for (Observer& observer : observers_) {
observer.OnDisplaySnapshotsInvalidated();
}
}
void DisplayConfigurator::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DisplayConfigurator::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool DisplayConfigurator::HasObserverForTesting(Observer* observer) const {
return observers_.HasObserver(observer);
}
void DisplayConfigurator::SuspendDisplays(ConfigurationCallback callback) {
if (configurator_disabled()) {
std::move(callback).Run(false);
return;
}
displays_suspended_ = true;
// Stop |configure_timer_| because we will force probe and configure all the
// displays at resume time anyway.
configure_timer_.Stop();
// Turn off the displays for suspend. This way, if we wake up for lucid sleep,
// the displays will not turn on (all displays should be off for lucid sleep
// unless explicitly requested by lucid sleep code). Use
// SetDisplayPowerInternal so requested_power_state_ is maintained.
SetDisplayPowerInternal(chromeos::DISPLAY_POWER_ALL_OFF,
kSetDisplayPowerNoFlags, std::move(callback));
}
void DisplayConfigurator::ResumeDisplays() {
if (configurator_disabled())
return;
displays_suspended_ = false;
if (current_display_state_ == MULTIPLE_DISPLAY_STATE_MULTI_MIRROR ||
current_display_state_ == MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED) {
// When waking up from suspend while being in a multi display mode, we
// schedule a delayed forced configuration, which will make
// SetDisplayPowerInternal() avoid performing the configuration immediately.
// This gives a chance to wait for all displays to be added and detected
// before configuration is performed, so we won't immediately resize the
// desktops and the windows on it to fit on a single display.
configure_timer_.Start(
FROM_HERE, base::Milliseconds(kResumeConfigureMultiDisplayDelayMs),
this, &DisplayConfigurator::ConfigureDisplays);
}
// TODO(crbug.com/41360858): Solve the issue of mirror mode on display resume.
// If requested_power_state_ is ALL_OFF due to idle suspend, powerd will turn
// the display power on when it enables the backlight.
if (requested_power_state_) {
SetDisplayPower(*requested_power_state_, kSetDisplayPowerNoFlags,
base::DoNothing());
}
}
void DisplayConfigurator::ConfigureDisplays() {
if (configurator_disabled())
return;
force_configure_ = true;
RunPendingConfiguration();
}
void DisplayConfigurator::RunPendingConfiguration() {
// Configuration task is currently running. Do not start a second
// configuration.
if (configuration_task_)
return;
if (!ShouldRunConfigurationTask()) {
LOG(ERROR) << "Called RunPendingConfiguration without any changes"
" requested";
CallAndClearQueuedCallbacks(true);
return;
}
ConfigurationType configuration_type = kConfigurationTypeFull;
if (!HasPendingFullConfiguration()) {
DCHECK(HasPendingSeamlessConfiguration());
configuration_type = kConfigurationTypeSeamless;
}
configuration_task_ = std::make_unique<UpdateDisplayConfigurationTask>(
native_display_delegate_.get(), layout_manager_.get(),
requested_display_state_, pending_power_state_, pending_power_flags_,
GetRequestedVrrState(), GetRequestedRefreshRateOverrides(),
force_configure_, configuration_type,
base::BindOnce(&DisplayConfigurator::OnConfigured,
weak_ptr_factory_.GetWeakPtr()));
// Reset the flags before running the task; otherwise it may end up scheduling
// another configuration.
force_configure_ = false;
pending_power_flags_ = kSetDisplayPowerNoFlags;
has_pending_power_state_ = false;
requested_display_state_ = MULTIPLE_DISPLAY_STATE_INVALID;
pending_refresh_rate_overrides_ = std::nullopt;
pending_vrr_state_ = std::nullopt;
DCHECK(in_progress_configuration_callbacks_.empty());
in_progress_configuration_callbacks_.swap(queued_configuration_callbacks_);
configuration_task_->Run();
}
void DisplayConfigurator::OnConfigured(
bool success,
const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
unassociated_displays,
MultipleDisplayState new_display_state,
chromeos::DisplayPowerState new_power_state) {
VLOG(1) << "OnConfigured: success=" << success << " new_display_state="
<< MultipleDisplayStateToString(new_display_state)
<< " new_power_state=" << DisplayPowerStateToString(new_power_state);
cached_displays_ = displays;
has_unassociated_display_ = unassociated_displays.size();
if (success) {
current_display_state_ = new_display_state;
UpdatePowerState(new_power_state);
}
configuration_task_.reset();
NotifyDisplayStateObservers(success, new_display_state);
CallAndClearInProgressCallbacks(success);
if (success && !configure_timer_.IsRunning() &&
ShouldRunConfigurationTask()) {
configure_timer_.Start(FROM_HERE, base::Milliseconds(kConfigureDelayMs),
this, &DisplayConfigurator::RunPendingConfiguration);
} else {
// If a new configuration task isn't scheduled respond to all queued
// callbacks (for example if requested state is current state).
if (!configure_timer_.IsRunning())
CallAndClearQueuedCallbacks(success);
}
}
void DisplayConfigurator::UpdatePowerState(
chromeos::DisplayPowerState new_power_state) {
chromeos::DisplayPowerState old_power_state = current_power_state_;
current_power_state_ = new_power_state;
// Don't notify observers of |current_power_state_| when there is a pending
// power state. Notifying the observers may confuse them because they may
// already know the up-to-date state via PowerManagerClient. Please refer to
// b/134459602 for details.
if (has_pending_power_state_)
return;
// If the pending power state hasn't changed then make sure that value gets
// updated as well since the last requested value may have been dependent on
// certain conditions (ie: if only the internal monitor was present).
pending_power_state_ = new_power_state;
if (old_power_state != current_power_state_)
NotifyPowerStateObservers();
}
bool DisplayConfigurator::ShouldRunConfigurationTask() const {
return HasPendingSeamlessConfiguration() || HasPendingFullConfiguration();
}
bool DisplayConfigurator::HasPendingFullConfiguration() const {
if (force_configure_) {
return true;
}
// Schedule if there is a request to change the display state.
if (requested_display_state_ != current_display_state_ &&
requested_display_state_ != MULTIPLE_DISPLAY_STATE_INVALID)
return true;
// Schedule if there is a request to change the power state.
if (has_pending_power_state_)
return true;
return false;
}
bool DisplayConfigurator::HasPendingSeamlessConfiguration() const {
if (pending_refresh_rate_overrides_) {
return true;
}
// Schedule if there is a request to change the VRR enabled state.
if (ShouldConfigureVrr()) {
return true;
}
return false;
}
void DisplayConfigurator::CallAndClearInProgressCallbacks(bool success) {
for (auto& callback : in_progress_configuration_callbacks_)
std::move(callback).Run(success);
in_progress_configuration_callbacks_.clear();
}
void DisplayConfigurator::CallAndClearQueuedCallbacks(bool success) {
for (auto& callback : queued_configuration_callbacks_)
std::move(callback).Run(success);
queued_configuration_callbacks_.clear();
}
void DisplayConfigurator::NotifyDisplayStateObservers(
bool success,
MultipleDisplayState attempted_state) {
if (success) {
for (Observer& observer : observers_)
observer.OnDisplayConfigurationChanged(cached_displays_);
} else {
for (Observer& observer : observers_)
observer.OnDisplayConfigurationChangeFailed(cached_displays_,
attempted_state);
}
}
void DisplayConfigurator::NotifyPowerStateObservers() {
for (Observer& observer : observers_)
observer.OnPowerStateChanged(current_power_state_);
}
bool DisplayConfigurator::IsDisplayOn() const {
return current_power_state_ != chromeos::DISPLAY_POWER_ALL_OFF;
}
void DisplayConfigurator::SetRefreshRateOverrides(
const RefreshRateOverrideMap& requested_overrides) {
if (HasPendingFullConfiguration()) {
// If there is a full config pending, skip the refresh override.
VLOG(3) << "Skip refresh rate overrides because a full configuration is "
"pending.";
return;
}
// Filter out requested overrides to ensure that they only target
// existing displays, and ensure that requests for the native refresh rate
// are represented by having no entry in the override map.
RefreshRateOverrideMap effective_overrides;
for (auto& snapshot : cached_displays_) {
auto it = requested_overrides.find(snapshot->display_id());
if (it != requested_overrides.end() &&
it->second != snapshot->native_mode()->refresh_rate()) {
effective_overrides[snapshot->display_id()] = it->second;
}
}
LOG_IF(WARNING, requested_overrides != effective_overrides)
<< "Some requested overrides are invalid and have been removed.\n"
<< "\tRequested: " << RefreshRateOverrideToString(requested_overrides)
<< "\n"
<< "\tActual: " << RefreshRateOverrideToString(effective_overrides);
const RefreshRateOverrideMap current_overrides =
GetCurrentRefreshRateOverrideState();
if (current_overrides == effective_overrides) {
VLOG(3) << "Skip refresh rate overrides because the requested overrides "
"are a no-op.";
return;
}
pending_refresh_rate_overrides_.emplace(effective_overrides);
if (!configure_timer_.IsRunning()) {
RunPendingConfiguration();
}
}
void DisplayConfigurator::SetVrrEnabled(
const base::flat_set<int64_t>& display_ids) {
// Filter the provided set for VRR-capable displays only, and determine
// whether a configuration is required given the current state.
base::flat_set<int64_t> filtered_display_ids;
bool requires_configuration = false;
for (const display::DisplaySnapshot* display : cached_displays_) {
if (!display->IsVrrCapable()) {
continue;
}
const bool vrr_should_be_enabled =
display_ids.contains(display->display_id());
if (vrr_should_be_enabled) {
filtered_display_ids.emplace(display->display_id());
}
requires_configuration |= vrr_should_be_enabled != display->IsVrrEnabled();
}
if (requires_configuration) {
pending_vrr_state_.emplace(filtered_display_ids);
if (!configure_timer_.IsRunning()) {
RunPendingConfiguration();
}
}
}
const base::flat_set<int64_t> DisplayConfigurator::GetRequestedVrrState()
const {
if (pending_vrr_state_.has_value()) {
return pending_vrr_state_.value();
}
base::flat_set<int64_t> requested_vrr_state;
for (const display::DisplaySnapshot* display : cached_displays_) {
if (display->IsVrrEnabled()) {
requested_vrr_state.emplace(display->display_id());
}
}
return requested_vrr_state;
}
bool DisplayConfigurator::ShouldConfigureVrr() const {
return pending_vrr_state_.has_value();
}
RefreshRateOverrideMap DisplayConfigurator::GetRequestedRefreshRateOverrides()
const {
// Do not request overrides for a full configuration.
if (HasPendingFullConfiguration()) {
return {};
}
if (pending_refresh_rate_overrides_.has_value()) {
return pending_refresh_rate_overrides_.value();
}
return GetCurrentRefreshRateOverrideState();
}
RefreshRateOverrideMap DisplayConfigurator::GetCurrentRefreshRateOverrideState()
const {
RefreshRateOverrideMap overrides;
for (DisplaySnapshot* cached_display : cached_displays_) {
// TODO(b/334104991): Refresh rate override is only enabled for internal
// displays.
if (cached_display->type() != DISPLAY_CONNECTION_TYPE_INTERNAL) {
continue;
}
// External displays may not be configured to their native modes.
const DisplayMode* native_mode = cached_display->native_mode();
const DisplayMode* current_mode = cached_display->current_mode();
// Display is not enabled, so there is no override.
if (!native_mode || !current_mode) {
continue;
}
if (native_mode->size() != current_mode->size()) {
// This should not happen for internal displays.
LOG(WARNING) << "Current mode size does not match native mode size.";
continue;
}
if (native_mode->refresh_rate() != current_mode->refresh_rate()) {
VLOG(3) << "Current override state for display with id ("
<< cached_display->display_id()
<< "): " << current_mode->refresh_rate();
overrides[cached_display->display_id()] = current_mode->refresh_rate();
}
}
return overrides;
}
////////////////////////////////////////////////////////////////////////////////
// DisplayConfigurator::TestApi implementation
bool DisplayConfigurator::TestApi::TriggerConfigureTimeout() {
if (configurator_->configure_timer_.IsRunning()) {
configurator_->configure_timer_.FireNow();
return true;
} else {
return false;
}
}
base::TimeDelta DisplayConfigurator::TestApi::GetConfigureDelay() const {
return configurator_->configure_timer_.IsRunning()
? configurator_->configure_timer_.GetCurrentDelay()
: base::TimeDelta();
}
DisplayLayoutManager* DisplayConfigurator::TestApi::GetDisplayLayoutManager()
const {
return configurator_->layout_manager_.get();
}
} // namespace display