// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/browser/cast_display_configurator.h"
#include <math.h>
#include <algorithm>
#include <string>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "chromecast/base/cast_features.h"
#include "chromecast/browser/cast_touch_device_manager.h"
#include "chromecast/chromecast_buildflags.h"
#include "chromecast/graphics/cast_display_util.h"
#include "chromecast/graphics/cast_screen.h"
#include "chromecast/public/graphics_properties_shlib.h"
#include "ui/display/types/display_configuration_params.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/ozone/public/ozone_platform.h"
namespace chromecast {
namespace shell {
namespace {
constexpr int64_t kStubDisplayId = 1;
constexpr char kCastGraphicsHeight[] = "cast-graphics-height";
constexpr char kCastGraphicsWidth[] = "cast-graphics-width";
gfx::Size GetDefaultScreenResolution() {
#if BUILDFLAG(IS_CAST_AUDIO_ONLY)
return gfx::Size(1, 1);
#else
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (!chromecast::IsFeatureEnabled(kTripleBuffer720) &&
GraphicsPropertiesShlib::IsSupported(GraphicsPropertiesShlib::k1080p,
cmd_line->argv())) {
return gfx::Size(1920, 1080);
}
return gfx::Size(1280, 720);
#endif
}
// Helper to return the screen resolution (device pixels)
// to use.
gfx::Size GetScreenResolution() {
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
int cast_gfx_width = 0;
int cast_gfx_height = 0;
if (base::StringToInt(cmd_line->GetSwitchValueASCII(kCastGraphicsWidth),
&cast_gfx_width) &&
base::StringToInt(cmd_line->GetSwitchValueASCII(kCastGraphicsHeight),
&cast_gfx_height) &&
cast_gfx_width > 0 && cast_gfx_height > 0) {
return gfx::Size(cast_gfx_width, cast_gfx_height);
}
return GetDefaultScreenResolution();
}
display::Display::Rotation RotationFromPanelOrientation(
display::PanelOrientation orientation) {
switch (orientation) {
case display::kNormal:
return display::Display::ROTATE_0;
case display::kRightUp:
return display::Display::ROTATE_90;
case display::kBottomUp:
return display::Display::ROTATE_180;
case display::kLeftUp:
return display::Display::ROTATE_270;
}
}
gfx::Rect GetScreenBounds(const gfx::Size& size_in_pixels,
display::Display::Rotation rotation) {
switch (rotation) {
case display::Display::ROTATE_90:
case display::Display::ROTATE_270:
return gfx::Rect(
gfx::Size(size_in_pixels.height(), size_in_pixels.width()));
case display::Display::ROTATE_0:
case display::Display::ROTATE_180:
default:
return gfx::Rect(size_in_pixels);
}
}
} // namespace
CastDisplayConfigurator::CastDisplayConfigurator(CastScreen* screen)
: delegate_(
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_CAST_AUDIO_ONLY)
ui::OzonePlatform::GetInstance()->CreateNativeDisplayDelegate()
#else
nullptr
#endif
),
touch_device_manager_(std::make_unique<CastTouchDeviceManager>()),
display_(nullptr),
cast_screen_(screen),
weak_factory_(this) {
if (delegate_) {
delegate_->AddObserver(this);
delegate_->Initialize();
ForceInitialConfigure();
} else {
ConfigureDisplayFromCommandLine();
}
}
CastDisplayConfigurator::~CastDisplayConfigurator() {
if (delegate_)
delegate_->RemoveObserver(this);
}
// display::NativeDisplayObserver interface
void CastDisplayConfigurator::OnConfigurationChanged() {
DCHECK(delegate_);
delegate_->GetDisplays(base::BindOnce(
&CastDisplayConfigurator::OnDisplaysAcquired, weak_factory_.GetWeakPtr(),
false /* force_initial_configure */));
}
void CastDisplayConfigurator::OnDisplaySnapshotsInvalidated() {
display_ = nullptr;
}
void CastDisplayConfigurator::EnableDisplay(
display::ConfigureCallback callback) {
if (!delegate_ || !display_)
return;
display::DisplayConfigurationParams display_config_params(
display_->display_id(), gfx::Point(), display_->native_mode());
std::vector<display::DisplayConfigurationParams> config_request;
config_request.push_back(std::move(display_config_params));
delegate_->Configure(config_request, std::move(callback),
{display::ModesetFlag::kTestModeset,
display::ModesetFlag::kCommitModeset});
NotifyObservers();
}
void CastDisplayConfigurator::DisableDisplay(
display::ConfigureCallback callback) {
if (!delegate_ || !display_)
return;
display::DisplayConfigurationParams display_config_params(
display_->display_id(), gfx::Point(), nullptr);
std::vector<display::DisplayConfigurationParams> config_request;
config_request.push_back(std::move(display_config_params));
delegate_->Configure(config_request, std::move(callback),
{display::ModesetFlag::kTestModeset,
display::ModesetFlag::kCommitModeset});
}
void CastDisplayConfigurator::ConfigureDisplayFromCommandLine() {
const gfx::Size size = GetScreenResolution();
UpdateScreen(kStubDisplayId, gfx::Rect(size), GetDeviceScaleFactor(size),
display::Display::ROTATE_0);
}
void CastDisplayConfigurator::SetColorTemperatureAdjustment(
const display::ColorTemperatureAdjustment& cta) {
if (!delegate_ || !display_)
return;
delegate_->SetColorTemperatureAdjustment(display_->display_id(), cta);
NotifyObservers();
}
void CastDisplayConfigurator::SetGammaAdjustment(
const display::GammaAdjustment& adjustment) {
if (!delegate_ || !display_)
return;
delegate_->SetGammaAdjustment(display_->display_id(), adjustment);
NotifyObservers();
}
void CastDisplayConfigurator::NotifyObservers() {
for (Observer& observer : observers_)
observer.OnDisplayStateChanged();
}
void CastDisplayConfigurator::ForceInitialConfigure() {
if (!delegate_)
return;
delegate_->GetDisplays(base::BindOnce(
&CastDisplayConfigurator::OnDisplaysAcquired, weak_factory_.GetWeakPtr(),
true /* force_initial_configure */));
}
void CastDisplayConfigurator::OnDisplaysAcquired(
bool force_initial_configure,
const std::vector<raw_ptr<display::DisplaySnapshot, VectorExperimental>>&
displays) {
DCHECK(delegate_);
if (displays.empty()) {
LOG(WARNING) << "No displays detected, skipping display init.";
return;
}
if (displays.size() > 1) {
LOG(WARNING) << "Multiple display detected, using the first one.";
}
display_ = displays[0];
if (!display_->native_mode()) {
LOG(WARNING) << "Display " << display_->display_id()
<< " doesn't have a native mode.";
return;
}
gfx::Point origin;
gfx::Size native_size(display_->native_mode()->size());
if (force_initial_configure) {
// For initial configuration, pass the native geometry to gfx::Screen
// before calling Configure(), so that this information is available
// to chrome during startup. Otherwise we will not have a valid display
// during the first queries to display::Screen.
UpdateScreen(display_->display_id(), gfx::Rect(origin, native_size),
GetDeviceScaleFactor(native_size),
RotationFromPanelOrientation(display_->panel_orientation()));
}
display::DisplayConfigurationParams display_config_params(
display_->display_id(), origin, display_->native_mode());
std::vector<display::DisplayConfigurationParams> config_request;
config_request.push_back(std::move(display_config_params));
delegate_->Configure(
config_request,
base::BindRepeating(&CastDisplayConfigurator::OnDisplayConfigured,
weak_factory_.GetWeakPtr()),
{display::ModesetFlag::kTestModeset,
display::ModesetFlag::kCommitModeset});
}
void CastDisplayConfigurator::OnDisplayConfigured(
const std::vector<display::DisplayConfigurationParams>& request_results,
bool config_success) {
DCHECK_EQ(request_results.size(), 1u);
const auto& result = request_results[0];
DCHECK(result.mode);
// Discard events for previous configurations. It is safe to discard since a
// new configuration round was initiated and we're waiting for another
// OnDisplayConfigured() event with the up-to-date display to arrive.
//
// This typically only happens when there's crashes and the state updates at
// the same time old notifications are received.
if (result.id != display_->display_id()) {
return;
}
const gfx::Rect bounds(result.origin, result.mode->size());
DVLOG(1) << __func__ << " success=" << config_success
<< " bounds=" << bounds.ToString();
if (config_success) {
// Need to update the display state otherwise it becomes stale.
display_->set_current_mode(result.mode.get());
display_->set_origin(result.origin);
UpdateScreen(display_->display_id(), bounds,
GetDeviceScaleFactor(display_->native_mode()->size()),
RotationFromPanelOrientation(display_->panel_orientation()));
} else {
LOG(FATAL) << "Failed to configure display";
}
}
void CastDisplayConfigurator::UpdateScreen(
int64_t display_id,
const gfx::Rect& bounds,
float device_scale_factor,
display::Display::Rotation rotation) {
cast_screen_->OnDisplayChanged(display_id, device_scale_factor, rotation,
GetScreenBounds(bounds.size(), rotation));
touch_device_manager_->OnDisplayConfigured(display_id, rotation, bounds);
}
void CastDisplayConfigurator::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void CastDisplayConfigurator::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
} // namespace shell
} // namespace chromecast