// Copyright 2016 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/ozone/platform/drm/host/host_drm_device.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "ui/display/types/display_configuration_params.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/ozone/platform/drm/common/display_types.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/host/drm_device_connector.h"
#include "ui/ozone/platform/drm/host/drm_display_host_manager.h"
#include "ui/ozone/platform/drm/host/host_cursor_proxy.h"
namespace ui {
HostDrmDevice::HostDrmDevice(DrmCursor* cursor) : cursor_(cursor) {}
HostDrmDevice::~HostDrmDevice() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
for (GpuThreadObserver& observer : gpu_thread_observers_)
observer.OnGpuThreadRetired();
}
void HostDrmDevice::OnDrmServiceStarted() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
DCHECK(!connected_);
connected_ = true;
for (GpuThreadObserver& observer : gpu_thread_observers_)
observer.OnGpuThreadReady();
DCHECK(cursor_proxy_)
<< "We should have already created a cursor proxy previously";
cursor_->SetDrmCursorProxy(std::move(cursor_proxy_));
// TODO(rjkroege): Call ResetDrmCursorProxy when the mojo connection to the
// DRM thread is broken.
}
void HostDrmDevice::SetDisplayManager(DrmDisplayHostManager* display_manager) {
display_manager_ = display_manager;
}
void HostDrmDevice::AddGpuThreadObserver(GpuThreadObserver* observer) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
gpu_thread_observers_.AddObserver(observer);
if (IsConnected())
observer->OnGpuThreadReady();
}
void HostDrmDevice::RemoveGpuThreadObserver(GpuThreadObserver* observer) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
gpu_thread_observers_.RemoveObserver(observer);
}
bool HostDrmDevice::IsConnected() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
// TODO(rjkroege): Need to set to connected_ to false when we lose the Viz
// process connection.
return connected_;
}
// Services needed for DrmDisplayHostMananger.
void HostDrmDevice::RegisterHandlerForDrmDisplayHostManager(
DrmDisplayHostManager* handler) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_ = handler;
}
void HostDrmDevice::UnRegisterHandlerForDrmDisplayHostManager() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_ = nullptr;
}
bool HostDrmDevice::GpuCreateWindow(gfx::AcceleratedWidget widget,
const gfx::Rect& initial_bounds) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
drm_device_->CreateWindow(widget, initial_bounds);
return true;
}
bool HostDrmDevice::GpuDestroyWindow(gfx::AcceleratedWidget widget) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
drm_device_->DestroyWindow(widget);
return true;
}
bool HostDrmDevice::GpuWindowBoundsChanged(gfx::AcceleratedWidget widget,
const gfx::Rect& bounds) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
drm_device_->SetWindowBounds(widget, bounds);
return true;
}
bool HostDrmDevice::GpuRefreshNativeDisplays() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
auto callback =
base::BindOnce(&HostDrmDevice::GpuRefreshNativeDisplaysCallback, this);
drm_device_->RefreshNativeDisplays(std::move(callback));
return true;
}
void HostDrmDevice::GpuConfigureNativeDisplays(
const std::vector<display::DisplayConfigurationParams>& config_requests,
display::ConfigureCallback callback,
display::ModesetFlags modeset_flags) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (IsConnected()) {
drm_device_->ConfigureNativeDisplays(config_requests, modeset_flags,
std::move(callback));
} else {
// Post this task to protect the callstack from accumulating too many
// recursive calls to ConfigureDisplaysTask::Run() in cases in which the GPU
// process crashes repeatedly.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), config_requests, false));
}
}
bool HostDrmDevice::GpuTakeDisplayControl() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected()) {
LOG(WARNING) << __func__ << " GPU service not connected.";
return false;
}
auto callback =
base::BindOnce(&HostDrmDevice::GpuTakeDisplayControlCallback, this);
drm_device_->TakeDisplayControl(std::move(callback));
return true;
}
bool HostDrmDevice::GpuRelinquishDisplayControl() {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected()) {
LOG(WARNING) << __func__ << " GPU service not connected.";
return false;
}
auto callback =
base::BindOnce(&HostDrmDevice::GpuRelinquishDisplayControlCallback, this);
drm_device_->RelinquishDisplayControl(std::move(callback));
return true;
}
void HostDrmDevice::GpuAddGraphicsDevice(const base::FilePath& path,
base::ScopedFD fd) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!drm_device_.is_bound())
return;
drm_device_->AddGraphicsDevice(path, mojo::PlatformHandle(std::move(fd)));
}
bool HostDrmDevice::GpuRemoveGraphicsDevice(const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
drm_device_->RemoveGraphicsDevice(std::move(path));
return true;
}
void HostDrmDevice::GpuShouldDisplayEventTriggerConfiguration(
const EventPropertyMap& event_props) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
// No connection to DRM device. Block the event since the entire configuration
// will most likely fail.
if (!IsConnected()) {
GpuShouldDisplayEventTriggerConfigurationCallback(/*should_trigger=*/false);
return;
}
auto callback = base::BindOnce(
&HostDrmDevice::GpuShouldDisplayEventTriggerConfigurationCallback, this);
drm_device_->ShouldDisplayEventTriggerConfiguration(event_props,
std::move(callback));
}
bool HostDrmDevice::GpuSetHdcpKeyProp(int64_t display_id,
const std::string& key) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected()) {
return false;
}
auto callback =
base::BindOnce(&HostDrmDevice::GpuSetHdcpKeyPropCallback, this);
drm_device_->SetHdcpKeyProp(display_id, key, std::move(callback));
return true;
}
bool HostDrmDevice::GpuGetHDCPState(int64_t display_id) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
auto callback = base::BindOnce(&HostDrmDevice::GpuGetHDCPStateCallback, this);
drm_device_->GetHDCPState(display_id, std::move(callback));
return true;
}
bool HostDrmDevice::GpuSetHDCPState(
int64_t display_id,
display::HDCPState state,
display::ContentProtectionMethod protection_method) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected())
return false;
auto callback = base::BindOnce(&HostDrmDevice::GpuSetHDCPStateCallback, this);
drm_device_->SetHDCPState(display_id, state, protection_method,
std::move(callback));
return true;
}
void HostDrmDevice::GpuSetColorTemperatureAdjustment(
int64_t display_id,
const display::ColorTemperatureAdjustment& cta) {
if (!IsConnected()) {
return;
}
drm_device_->SetColorTemperatureAdjustment(display_id, cta);
}
void HostDrmDevice::GpuSetColorCalibration(
int64_t display_id,
const display::ColorCalibration& calibration) {
if (!IsConnected()) {
return;
}
drm_device_->SetColorCalibration(display_id, calibration);
}
void HostDrmDevice::GpuSetGammaAdjustment(
int64_t display_id,
const display::GammaAdjustment& adjustment) {
if (!IsConnected()) {
return;
}
drm_device_->SetGammaAdjustment(display_id, adjustment);
}
void HostDrmDevice::GpuSetPrivacyScreen(
int64_t display_id,
bool enabled,
display::SetPrivacyScreenCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (IsConnected()) {
drm_device_->SetPrivacyScreen(display_id, enabled, std::move(callback));
} else {
// There's no connection to the DRM device, so trigger Chrome's callback
// with a failed state.
std::move(callback).Run(/*success=*/false);
}
}
void HostDrmDevice::GpuGetSeamlessRefreshRates(
int64_t display_id,
display::GetSeamlessRefreshRatesCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
if (!IsConnected()) {
std::move(callback).Run(std::nullopt);
return;
}
drm_device_->GetSeamlessRefreshRates(display_id, std::move(callback));
}
void HostDrmDevice::GpuRefreshNativeDisplaysCallback(
MovableDisplaySnapshots displays) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuHasUpdatedNativeDisplays(std::move(displays));
}
void HostDrmDevice::GpuTakeDisplayControlCallback(bool success) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuTookDisplayControl(success);
}
void HostDrmDevice::GpuRelinquishDisplayControlCallback(bool success) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuRelinquishedDisplayControl(success);
}
void HostDrmDevice::GpuShouldDisplayEventTriggerConfigurationCallback(
bool should_trigger) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuShouldDisplayEventTriggerConfiguration(should_trigger);
}
void HostDrmDevice::GpuSetHdcpKeyPropCallback(int64_t display_id,
bool success) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuSetHdcpKeyProp(display_id, success);
}
void HostDrmDevice::GpuGetHDCPStateCallback(
int64_t display_id,
bool success,
display::HDCPState state,
display::ContentProtectionMethod protection_method) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuReceivedHDCPState(display_id, success, state,
protection_method);
}
void HostDrmDevice::GpuSetHDCPStateCallback(int64_t display_id,
bool success) const {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
display_manager_->GpuUpdatedHDCPState(display_id, success);
}
void HostDrmDevice::OnGpuServiceLaunched(
mojo::PendingRemote<ui::ozone::mojom::DrmDevice> drm_device) {
DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
// We can get into this state if a new instance of GpuProcessHost is created
// before the old one is destroyed.
if (IsConnected())
OnGpuServiceLost();
drm_device_.Bind(std::move(drm_device));
for (GpuThreadObserver& observer : gpu_thread_observers_)
observer.OnGpuProcessLaunched();
// Create two DeviceCursor connections: one for the UI thread and one for the
// IO thread.
mojo::PendingAssociatedRemote<ui::ozone::mojom::DeviceCursor> cursor_ui,
cursor_io;
drm_device_->GetDeviceCursor(cursor_ui.InitWithNewEndpointAndPassReceiver());
drm_device_->GetDeviceCursor(cursor_io.InitWithNewEndpointAndPassReceiver());
// The cursor is special since it will process input events on the IO thread
// and can by-pass the UI thread. As a result, it has a Remote for both the UI
// and I/O thread. cursor_io is already bound correctly to an I/O thread by
// GpuProcessHost.
cursor_proxy_ = std::make_unique<HostCursorProxy>(std::move(cursor_ui),
std::move(cursor_io));
OnDrmServiceStarted();
}
void HostDrmDevice::OnGpuServiceLost() {
cursor_proxy_.reset();
connected_ = false;
drm_device_.reset();
// TODO(rjkroege): OnGpuThreadRetired is not currently used.
for (GpuThreadObserver& observer : gpu_thread_observers_)
observer.OnGpuThreadRetired();
}
} // namespace ui