// 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 "chrome/browser/extensions/system_display/display_info_provider_chromeos.h"
#include "base/task/single_thread_task_runner.h"
#include <stdint.h>
#include <cmath>
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/public/ash_interfaces.h"
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/lacros/lacros_service.h"
#endif
#include "base/functional/bind.h"
#include "chrome/browser/extensions/system_display/display_info_provider.h"
#include "chrome/browser/extensions/system_display/display_info_provider_utils.h"
#include "extensions/common/api/system_display.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
namespace extensions {
namespace system_display = api::system_display;
namespace {
void RunResultCallback(DisplayInfoProvider::ErrorCallback callback,
std::optional<std::string> error) {
if (error) {
LOG(ERROR) << "API call failed: " << *error;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(error)));
}
std::optional<std::string> GetStringResult(
crosapi::mojom::DisplayConfigResult result) {
switch (result) {
case crosapi::mojom::DisplayConfigResult::kSuccess:
return std::nullopt;
case crosapi::mojom::DisplayConfigResult::kInvalidOperationError:
return "Invalid operation";
case crosapi::mojom::DisplayConfigResult::kInvalidDisplayIdError:
return "Invalid display id";
case crosapi::mojom::DisplayConfigResult::kUnifiedNotEnabledError:
return "enableUnifiedDesktop must be called before setting is_unified";
case crosapi::mojom::DisplayConfigResult::kPropertyValueOutOfRangeError:
return "Property value out of range";
case crosapi::mojom::DisplayConfigResult::
kNotSupportedOnInternalDisplayError:
return "Not supported for internal displays";
case crosapi::mojom::DisplayConfigResult::kNegativeValueError:
return "Negative values not supported";
case crosapi::mojom::DisplayConfigResult::kSetDisplayModeError:
return "Setting the display mode failed";
case crosapi::mojom::DisplayConfigResult::kInvalidDisplayLayoutError:
return "Invalid display layout";
case crosapi::mojom::DisplayConfigResult::kSingleDisplayError:
return "This mode requires multiple displays";
case crosapi::mojom::DisplayConfigResult::kMirrorModeSourceIdError:
return "Mirror mode source id invalid";
case crosapi::mojom::DisplayConfigResult::kMirrorModeDestIdError:
return "Mirror mode destination id invalid";
case crosapi::mojom::DisplayConfigResult::kCalibrationNotAvailableError:
return "Calibration not available";
case crosapi::mojom::DisplayConfigResult::kCalibrationNotStartedError:
return "Calibration not started";
case crosapi::mojom::DisplayConfigResult::kCalibrationInProgressError:
return "Calibration in progress";
case crosapi::mojom::DisplayConfigResult::kCalibrationInvalidDataError:
return "Calibration data invalid";
case crosapi::mojom::DisplayConfigResult::kCalibrationFailedError:
return "Calibration failed";
}
return "Unknown error";
}
void LogErrorResult(crosapi::mojom::DisplayConfigResult result) {
std::optional<std::string> str_result = GetStringResult(result);
if (!str_result) {
return;
}
LOG(ERROR) << *str_result;
}
} // namespace
DisplayInfoProviderChromeOS::DisplayInfoProviderChromeOS(
mojo::PendingRemote<crosapi::mojom::CrosDisplayConfigController>
display_config)
: cros_display_config_(std::move(display_config)) {}
DisplayInfoProviderChromeOS::~DisplayInfoProviderChromeOS() = default;
void DisplayInfoProviderChromeOS::SetDisplayProperties(
const std::string& display_id_str,
const api::system_display::DisplayProperties& properties,
ErrorCallback callback) {
std::optional<std::string> error =
ValidateDisplayPropertiesInput(display_id_str, properties);
if (error) {
RunResultCallback(std::move(callback), std::move(*error));
return;
}
// Process the 'isUnified' property.
if (properties.is_unified) {
auto layout_info = crosapi::mojom::DisplayLayoutInfo::New();
layout_info->layout_mode = *properties.is_unified
? crosapi::mojom::DisplayLayoutMode::kUnified
: crosapi::mojom::DisplayLayoutMode::kNormal;
cros_display_config_->SetDisplayLayoutInfo(
std::move(layout_info),
base::BindOnce(
[](ErrorCallback callback,
crosapi::mojom::DisplayConfigResult result) {
std::move(callback).Run(GetStringResult(result));
},
std::move(callback)));
// Note: If other properties are set they will be ignored.
return;
}
// Process the deprecated 'mirroringSourceId' property. Validation ensures
// that no other properties are set.
if (properties.mirroring_source_id) {
bool mirror = !properties.mirroring_source_id->empty();
if (mirror) {
// A display with the given id should exist and if should not be the same
// as the target display's id.
int64_t mirroring_id =
GetDisplayForId(*properties.mirroring_source_id).id();
if (mirroring_id == display::kInvalidDisplayId) {
RunResultCallback(std::move(callback), "Invalid mirroring source id");
return;
}
if (mirroring_id == GetDisplayId(display_id_str)) {
RunResultCallback(std::move(callback), "Not allowed to mirror self");
return;
}
}
api::system_display::MirrorModeInfo info;
info.mode = system_display::MirrorMode::kNormal;
SetMirrorMode(info, std::move(callback));
return;
}
// Global config properties.
auto config_properties = crosapi::mojom::DisplayConfigProperties::New();
config_properties->set_primary =
properties.is_primary ? *properties.is_primary : false;
if (properties.overscan) {
config_properties->overscan = GetInsets(*properties.overscan);
}
if (properties.rotation) {
config_properties->rotation = crosapi::mojom::DisplayRotation::New(
GetMojomDisplayRotationOptions(*properties.rotation));
}
if (properties.bounds_origin_x || properties.bounds_origin_y) {
gfx::Point bounds_origin;
display::Display display = GetDisplayForId(display_id_str);
if (display.id() != display::kInvalidDisplayId) {
bounds_origin = display.bounds().origin();
} else {
DLOG(ERROR) << "Unable to get origin for display: " << display_id_str;
}
if (properties.bounds_origin_x) {
bounds_origin.set_x(*properties.bounds_origin_x);
}
if (properties.bounds_origin_y) {
bounds_origin.set_y(*properties.bounds_origin_y);
}
LOG(ERROR) << "Bounds origin: " << bounds_origin.ToString();
config_properties->bounds_origin = std::move(bounds_origin);
}
config_properties->display_zoom_factor =
properties.display_zoom_factor ? *properties.display_zoom_factor : 0;
// Display mode.
if (properties.display_mode) {
auto mojo_display_mode = crosapi::mojom::DisplayMode::New();
const api::system_display::DisplayMode& api_display_mode =
*properties.display_mode;
mojo_display_mode->size =
gfx::Size(api_display_mode.width, api_display_mode.height);
mojo_display_mode->size_in_native_pixels =
gfx::Size(api_display_mode.width_in_native_pixels,
api_display_mode.height_in_native_pixels);
mojo_display_mode->device_scale_factor =
api_display_mode.device_scale_factor;
mojo_display_mode->refresh_rate = api_display_mode.refresh_rate;
mojo_display_mode->is_native = api_display_mode.is_native;
mojo_display_mode->is_interlaced =
api_display_mode.is_interlaced && *(api_display_mode.is_interlaced);
config_properties->display_mode = std::move(mojo_display_mode);
}
cros_display_config_->SetDisplayProperties(
display_id_str, std::move(config_properties),
crosapi::mojom::DisplayConfigSource::kUser,
base::BindOnce(
[](ErrorCallback callback,
crosapi::mojom::DisplayConfigResult result) {
std::move(callback).Run(GetStringResult(result));
},
std::move(callback)));
}
void DisplayInfoProviderChromeOS::SetDisplayLayout(
const DisplayLayoutList& layout_list,
ErrorCallback callback) {
auto layout_info = crosapi::mojom::DisplayLayoutInfo::New();
// Generate the new list of layouts.
std::vector<crosapi::mojom::DisplayLayoutPtr> display_layouts;
for (const system_display::DisplayLayout& layout : layout_list) {
auto display_layout = crosapi::mojom::DisplayLayout::New();
display_layout->id = layout.id;
display_layout->parent_id = layout.parent_id;
display_layout->position = GetDisplayLayoutPosition(layout.position);
display_layout->offset = layout.offset;
display_layouts.emplace_back(std::move(display_layout));
}
layout_info->layouts = std::move(display_layouts);
// We need to get the current layout info to provide the layout mode.
cros_display_config_->GetDisplayLayoutInfo(
base::BindOnce(&DisplayInfoProviderChromeOS::CallSetDisplayLayoutInfo,
weak_ptr_factory_.GetWeakPtr(), std::move(layout_info),
std::move(callback)));
}
void DisplayInfoProviderChromeOS::CallSetDisplayLayoutInfo(
crosapi::mojom::DisplayLayoutInfoPtr layout_info,
ErrorCallback callback,
crosapi::mojom::DisplayLayoutInfoPtr cur_info) {
// Copy the existing layout_mode.
layout_info->layout_mode = cur_info->layout_mode;
cros_display_config_->SetDisplayLayoutInfo(
std::move(layout_info),
base::BindOnce(
[](ErrorCallback callback,
crosapi::mojom::DisplayConfigResult result) {
std::move(callback).Run(GetStringResult(result));
},
std::move(callback)));
}
void DisplayInfoProviderChromeOS::EnableUnifiedDesktop(bool enable) {
cros_display_config_->SetUnifiedDesktopEnabled(enable);
}
void DisplayInfoProviderChromeOS::GetAllDisplaysInfo(
bool single_unified,
base::OnceCallback<void(DisplayUnitInfoList result)> callback) {
cros_display_config_->GetDisplayLayoutInfo(base::BindOnce(
&DisplayInfoProviderChromeOS::CallGetDisplayUnitInfoList,
weak_ptr_factory_.GetWeakPtr(), single_unified, std::move(callback)));
}
void DisplayInfoProviderChromeOS::CallGetDisplayUnitInfoList(
bool single_unified,
base::OnceCallback<void(DisplayUnitInfoList result)> callback,
crosapi::mojom::DisplayLayoutInfoPtr layout) {
cros_display_config_->GetDisplayUnitInfoList(
single_unified,
base::BindOnce(&DisplayInfoProviderChromeOS::OnGetDisplayUnitInfoList,
weak_ptr_factory_.GetWeakPtr(), std::move(layout),
std::move(callback)));
}
void DisplayInfoProviderChromeOS::OnGetDisplayUnitInfoList(
crosapi::mojom::DisplayLayoutInfoPtr layout,
base::OnceCallback<void(DisplayUnitInfoList)> callback,
std::vector<crosapi::mojom::DisplayUnitInfoPtr> info_list) {
DisplayUnitInfoList all_displays;
for (const crosapi::mojom::DisplayUnitInfoPtr& info : info_list) {
system_display::DisplayUnitInfo display = GetDisplayUnitInfoFromMojo(*info);
SetDisplayUnitInfoLayoutProperties(*layout, &display);
all_displays.push_back(std::move(display));
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(all_displays)));
}
void DisplayInfoProviderChromeOS::GetDisplayLayout(
base::OnceCallback<void(DisplayLayoutList)> callback) {
cros_display_config_->GetDisplayLayoutInfo(
base::BindOnce(&OnGetDisplayLayoutResult, std::move(callback)));
}
bool DisplayInfoProviderChromeOS::OverscanCalibrationStart(
const std::string& id) {
cros_display_config_->OverscanCalibration(
id, crosapi::mojom::DisplayConfigOperation::kStart, std::nullopt,
base::BindOnce(&LogErrorResult));
return true;
}
bool DisplayInfoProviderChromeOS::OverscanCalibrationAdjust(
const std::string& id,
const system_display::Insets& delta) {
cros_display_config_->OverscanCalibration(
id, crosapi::mojom::DisplayConfigOperation::kAdjust, GetInsets(delta),
base::BindOnce(&LogErrorResult));
return true;
}
bool DisplayInfoProviderChromeOS::OverscanCalibrationReset(
const std::string& id) {
cros_display_config_->OverscanCalibration(
id, crosapi::mojom::DisplayConfigOperation::kReset, std::nullopt,
base::BindOnce(&LogErrorResult));
return true;
}
bool DisplayInfoProviderChromeOS::OverscanCalibrationComplete(
const std::string& id) {
cros_display_config_->OverscanCalibration(
id, crosapi::mojom::DisplayConfigOperation::kComplete, std::nullopt,
base::BindOnce(&LogErrorResult));
return true;
}
void DisplayInfoProviderChromeOS::ShowNativeTouchCalibration(
const std::string& id,
ErrorCallback callback) {
CallTouchCalibration(id, crosapi::mojom::DisplayConfigOperation::kShowNative,
nullptr, std::move(callback));
}
bool DisplayInfoProviderChromeOS::StartCustomTouchCalibration(
const std::string& id) {
touch_calibration_target_id_ = id;
CallTouchCalibration(id, crosapi::mojom::DisplayConfigOperation::kStart,
nullptr, ErrorCallback());
return true;
}
bool DisplayInfoProviderChromeOS::CompleteCustomTouchCalibration(
const api::system_display::TouchCalibrationPairQuad& pairs,
const api::system_display::Bounds& bounds) {
auto calibration = crosapi::mojom::TouchCalibration::New();
calibration->pairs.emplace_back(GetTouchCalibrationPair(pairs.pair1));
calibration->pairs.emplace_back(GetTouchCalibrationPair(pairs.pair2));
calibration->pairs.emplace_back(GetTouchCalibrationPair(pairs.pair3));
calibration->pairs.emplace_back(GetTouchCalibrationPair(pairs.pair4));
calibration->bounds = gfx::Size(bounds.width, bounds.height);
CallTouchCalibration(touch_calibration_target_id_,
crosapi::mojom::DisplayConfigOperation::kComplete,
std::move(calibration), ErrorCallback());
return true;
}
bool DisplayInfoProviderChromeOS::ClearTouchCalibration(const std::string& id) {
CallTouchCalibration(id, crosapi::mojom::DisplayConfigOperation::kReset,
nullptr, ErrorCallback());
return true;
}
void DisplayInfoProviderChromeOS::CallTouchCalibration(
const std::string& id,
crosapi::mojom::DisplayConfigOperation op,
crosapi::mojom::TouchCalibrationPtr calibration,
ErrorCallback callback) {
cros_display_config_->TouchCalibration(
id, op, std::move(calibration),
base::BindOnce(
[](ErrorCallback callback,
crosapi::mojom::DisplayConfigResult result) {
if (!callback) {
return;
}
std::move(callback).Run(
result == crosapi::mojom::DisplayConfigResult::kSuccess
? std::nullopt
: GetStringResult(result));
},
std::move(callback)));
}
void DisplayInfoProviderChromeOS::SetMirrorMode(
const api::system_display::MirrorModeInfo& info,
ErrorCallback callback) {
auto display_layout_info = crosapi::mojom::DisplayLayoutInfo::New();
if (info.mode == api::system_display::MirrorMode::kOff) {
display_layout_info->layout_mode =
crosapi::mojom::DisplayLayoutMode::kNormal;
} else {
display_layout_info->layout_mode =
crosapi::mojom::DisplayLayoutMode::kMirrored;
if (info.mode == api::system_display::MirrorMode::kMixed) {
if (!info.mirroring_source_id) {
RunResultCallback(std::move(callback), "Mirror mode source id invalid");
return;
}
if (!info.mirroring_destination_ids) {
RunResultCallback(std::move(callback),
"Mixed mirror mode requires destination ids");
return;
}
display_layout_info->mirror_source_id = *info.mirroring_source_id;
display_layout_info->mirror_destination_ids =
std::make_optional<std::vector<std::string>>(
*info.mirroring_destination_ids);
}
}
cros_display_config_->SetDisplayLayoutInfo(
std::move(display_layout_info),
base::BindOnce(
[](ErrorCallback callback,
crosapi::mojom::DisplayConfigResult result) {
std::move(callback).Run(GetStringResult(result));
},
std::move(callback)));
}
void DisplayInfoProviderChromeOS::StartObserving() {
DisplayInfoProvider::StartObserving();
mojo::PendingAssociatedRemote<crosapi::mojom::CrosDisplayConfigObserver>
observer;
cros_display_config_observer_receiver_.Bind(
observer.InitWithNewEndpointAndPassReceiver());
cros_display_config_->AddObserver(std::move(observer));
}
void DisplayInfoProviderChromeOS::StopObserving() {
DisplayInfoProvider::StopObserving();
cros_display_config_observer_receiver_.reset();
}
void DisplayInfoProviderChromeOS::OnDisplayConfigChanged() {
DispatchOnDisplayChangedEvent();
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
std::unique_ptr<DisplayInfoProvider> CreateChromeDisplayInfoProvider() {
mojo::PendingRemote<crosapi::mojom::CrosDisplayConfigController>
display_config;
ash::BindCrosDisplayConfigController(
display_config.InitWithNewPipeAndPassReceiver());
return std::make_unique<DisplayInfoProviderChromeOS>(
std::move(display_config));
}
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
std::unique_ptr<DisplayInfoProvider> CreateChromeDisplayInfoProvider() {
// Assume that LacrosService has already been initialized.
auto* lacros_service = chromeos::LacrosService::Get();
if (lacros_service &&
lacros_service
->IsAvailable<crosapi::mojom::CrosDisplayConfigController>()) {
auto& remote =
lacros_service
->GetRemote<crosapi::mojom::CrosDisplayConfigController>();
return std::make_unique<DisplayInfoProviderChromeOS>(remote.Unbind());
}
LOG(ERROR) << "Cannot create a DisplayInfoProvider instance in Lacros. "
"CrosDisplayConfigController interface is not available.";
return nullptr;
}
#endif
} // namespace extensions