| // 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> |
| |
| #include "ash/public/ash_interfaces.h" |
| |
| #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(); |
| } |
| |
| 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)); |
| } |
| |
| } // namespace extensions |