chromium/chromecast/browser/cast_display_configurator.cc

// 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