| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/display/cros_display_config.h" |
| |
| #include <utility> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/display/display_alignment_controller.h" |
| #include "ash/display/display_configuration_controller.h" |
| #include "ash/display/display_highlight_controller.h" |
| #include "ash/display/display_prefs.h" |
| #include "ash/display/overscan_calibrator.h" |
| #include "ash/display/resolution_notification_controller.h" |
| #include "ash/display/screen_orientation_controller.h" |
| #include "ash/display/touch_calibrator_controller.h" |
| #include "ash/display/window_tree_host_manager.h" |
| #include "ash/public/cpp/tablet_mode_observer.h" |
| #include "ash/public/mojom/cros_display_config.mojom.h" |
| #include "ash/shell.h" |
| #include "ash/touch/ash_touch_transform_controller.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/bind.h" |
| #include "base/containers/contains.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "ui/display/display.h" |
| #include "ui/display/display_layout.h" |
| #include "ui/display/display_layout_builder.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/display_manager_util.h" |
| #include "ui/display/mojom/display_mojom_traits.h" |
| #include "ui/display/screen.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Maximum allowed bounds origin absolute value. |
| constexpr int kMaxBoundsOrigin = 200 * 1000; |
| |
| // This is the default range of display width in logical pixels allowed after |
| // applying display zoom. |
| constexpr int kDefaultMaxZoomWidth = 4096; |
| constexpr int kDefaultMinZoomWidth = 640; |
| |
| display::DisplayManager* GetDisplayManager() { |
| return Shell::Get()->display_manager(); |
| } |
| |
| int64_t GetDisplayId(const std::string& display_id_str) { |
| int64_t display_id; |
| if (!base::StringToInt64(display_id_str, &display_id)) |
| display_id = display::kInvalidDisplayId; |
| return display_id; |
| } |
| |
| // Gets the display with the provided string id. |
| display::Display GetDisplay(const std::string& display_id_str) { |
| int64_t display_id = GetDisplayId(display_id_str); |
| if (display_id == display::kInvalidDisplayId) |
| return display::Display(); |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| if (display_manager->IsInUnifiedMode() && |
| display_id != display::kUnifiedDisplayId) { |
| // In unified desktop mode, the mirroring displays, which constitue the |
| // combined unified display, are contained in the software mirroring |
| // displays list in the display manager. |
| // The active list of displays only contains a single unified display entry |
| // with id |kUnifiedDisplayId|. |
| return display_manager->GetMirroringDisplayById(display_id); |
| } |
| return display_manager->GetDisplayForId(display_id); |
| } |
| |
| mojom::DisplayLayoutPosition GetMojomDisplayLayoutPosition( |
| display::DisplayPlacement::Position position) { |
| switch (position) { |
| case display::DisplayPlacement::TOP: |
| return mojom::DisplayLayoutPosition::kTop; |
| case display::DisplayPlacement::RIGHT: |
| return mojom::DisplayLayoutPosition::kRight; |
| case display::DisplayPlacement::BOTTOM: |
| return mojom::DisplayLayoutPosition::kBottom; |
| case display::DisplayPlacement::LEFT: |
| return mojom::DisplayLayoutPosition::kLeft; |
| } |
| NOTREACHED(); |
| return mojom::DisplayLayoutPosition::kLeft; |
| } |
| |
| display::DisplayPlacement::Position GetDisplayPlacementPosition( |
| mojom::DisplayLayoutPosition position) { |
| switch (position) { |
| case mojom::DisplayLayoutPosition::kTop: |
| return display::DisplayPlacement::TOP; |
| case mojom::DisplayLayoutPosition::kRight: |
| return display::DisplayPlacement::RIGHT; |
| case mojom::DisplayLayoutPosition::kBottom: |
| return display::DisplayPlacement::BOTTOM; |
| case mojom::DisplayLayoutPosition::kLeft: |
| return display::DisplayPlacement::LEFT; |
| } |
| NOTREACHED(); |
| return display::DisplayPlacement::LEFT; |
| } |
| |
| std::vector<mojom::DisplayLayoutPtr> GetDisplayLayouts() { |
| auto layouts = std::vector<mojom::DisplayLayoutPtr>(); |
| display::Screen* screen = display::Screen::GetScreen(); |
| const std::vector<display::Display>& displays = screen->GetAllDisplays(); |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| for (const display::Display& display : displays) { |
| const display::DisplayPlacement placement = |
| display_manager->GetCurrentResolvedDisplayLayout().FindPlacementById( |
| display.id()); |
| if (placement.display_id == display::kInvalidDisplayId) |
| continue; |
| auto layout = mojom::DisplayLayout::New(); |
| layout->id = base::NumberToString(placement.display_id); |
| layout->parent_id = base::NumberToString(placement.parent_display_id); |
| layout->position = GetMojomDisplayLayoutPosition(placement.position); |
| layout->offset = placement.offset; |
| layouts.emplace_back(std::move(layout)); |
| } |
| return layouts; |
| } |
| |
| std::vector<mojom::DisplayLayoutPtr> GetDisplayUnifiedLayouts() { |
| auto layouts = std::vector<mojom::DisplayLayoutPtr>(); |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| |
| const display::UnifiedDesktopLayoutMatrix& matrix = |
| display_manager->current_unified_desktop_matrix(); |
| for (size_t row_index = 0; row_index < matrix.size(); ++row_index) { |
| const auto& row = matrix[row_index]; |
| for (size_t column_index = 0; column_index < row.size(); ++column_index) { |
| if (column_index == 0 && row_index == 0) { |
| // No placement for the primary display. |
| continue; |
| } |
| auto layout = mojom::DisplayLayout::New(); |
| const int64_t display_id = row[column_index]; |
| // Parent display is either the one in the above row, or the one on the |
| // left in the same row. |
| const int64_t parent_id = column_index == 0 |
| ? matrix[row_index - 1][column_index] |
| : row[column_index - 1]; |
| layout->id = base::NumberToString(display_id); |
| layout->parent_id = base::NumberToString(parent_id); |
| layout->position = column_index == 0 |
| ? mojom::DisplayLayoutPosition::kBottom |
| : mojom::DisplayLayoutPosition::kRight; |
| layout->offset = 0; |
| layouts.emplace_back(std::move(layout)); |
| } |
| } |
| return layouts; |
| } |
| |
| mojom::DisplayConfigResult SetDisplayLayoutMode( |
| const mojom::DisplayLayoutInfo& info) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| if (info.layout_mode == mojom::DisplayLayoutMode::kNormal) { |
| display_manager->SetDefaultMultiDisplayModeForCurrentDisplays( |
| display::DisplayManager::EXTENDED); |
| display_manager->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| if (info.layout_mode == mojom::DisplayLayoutMode::kUnified) { |
| if (!display_manager->unified_desktop_enabled()) |
| return mojom::DisplayConfigResult::kUnifiedNotEnabledError; |
| display_manager->SetDefaultMultiDisplayModeForCurrentDisplays( |
| display::DisplayManager::UNIFIED); |
| display_manager->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| DCHECK(info.layout_mode == mojom::DisplayLayoutMode::kMirrored); |
| |
| // 'Normal' mirror mode. |
| if (!info.mirror_source_id) { |
| display_manager->SetMirrorMode(display::MirrorMode::kNormal, absl::nullopt); |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| // 'Mixed' mirror mode. |
| display::Display source = GetDisplay(*info.mirror_source_id); |
| if (source.id() == display::kInvalidDisplayId) |
| return mojom::DisplayConfigResult::kMirrorModeSourceIdError; |
| display::DisplayIdList destination_ids; |
| if (info.mirror_destination_ids) { |
| for (const std::string& id_str : *info.mirror_destination_ids) { |
| int64_t destination_id = GetDisplayId(id_str); |
| if (destination_id == display::kInvalidDisplayId) |
| return mojom::DisplayConfigResult::kMirrorModeDestIdError; |
| destination_ids.emplace_back(destination_id); |
| } |
| } else { |
| const std::vector<display::Display>& displays = |
| display::Screen::GetScreen()->GetAllDisplays(); |
| for (const display::Display& display : displays) |
| destination_ids.emplace_back(display.id()); |
| } |
| absl::optional<display::MixedMirrorModeParams> mixed_params( |
| absl::in_place, source.id(), destination_ids); |
| const display::MixedMirrorModeParamsErrors error_type = |
| display::ValidateParamsForMixedMirrorMode( |
| display_manager->GetConnectedDisplayIdList(), *mixed_params); |
| switch (error_type) { |
| case display::MixedMirrorModeParamsErrors::kErrorSingleDisplay: |
| return mojom::DisplayConfigResult::kMirrorModeSingleDisplayError; |
| case display::MixedMirrorModeParamsErrors::kErrorSourceIdNotFound: |
| return mojom::DisplayConfigResult::kMirrorModeSourceIdError; |
| case display::MixedMirrorModeParamsErrors::kErrorDestinationIdsEmpty: |
| case display::MixedMirrorModeParamsErrors::kErrorDestinationIdNotFound: |
| case display::MixedMirrorModeParamsErrors::kErrorDuplicateId: |
| return mojom::DisplayConfigResult::kMirrorModeDestIdError; |
| case display::MixedMirrorModeParamsErrors::kSuccess: |
| break; |
| } |
| display_manager->SetMirrorMode(display::MirrorMode::kMixed, mixed_params); |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| mojom::DisplayModePtr GetDisplayMode( |
| const display::ManagedDisplayInfo& display_info, |
| const display::ManagedDisplayMode& display_mode) { |
| auto result = mojom::DisplayMode::New(); |
| gfx::Size size_dip = display_mode.GetSizeInDIP(); |
| result->size = size_dip; |
| result->size_in_native_pixels = display_mode.size(); |
| result->device_scale_factor = display_mode.device_scale_factor(); |
| result->refresh_rate = display_mode.refresh_rate(); |
| result->is_native = display_mode.native(); |
| result->is_interlaced = display_mode.is_interlaced(); |
| return result; |
| } |
| |
| display::Display::Rotation DisplayRotationFromRotationOptions( |
| mojom::DisplayRotationOptions option) { |
| switch (option) { |
| case mojom::DisplayRotationOptions::kAutoRotate: |
| return display::Display::ROTATE_0; |
| |
| case mojom::DisplayRotationOptions::kZeroDegrees: |
| return display::Display::ROTATE_0; |
| |
| case mojom::DisplayRotationOptions::k90Degrees: |
| return display::Display::ROTATE_90; |
| |
| case mojom::DisplayRotationOptions::k180Degrees: |
| return display::Display::ROTATE_180; |
| |
| case mojom::DisplayRotationOptions::k270Degrees: |
| return display::Display::ROTATE_270; |
| } |
| } |
| |
| mojom::DisplayRotationOptions RotationOptionsFromDisplayRotation( |
| display::Display::Rotation rotation) { |
| auto* screen_orientation_controller = |
| Shell::Get()->screen_orientation_controller(); |
| const bool is_auto_rotation_allowed = |
| screen_orientation_controller->IsAutoRotationAllowed(); |
| const bool is_auto_rotate_enabled = |
| !screen_orientation_controller->user_rotation_locked(); |
| if (is_auto_rotation_allowed && is_auto_rotate_enabled) |
| return mojom::DisplayRotationOptions::kAutoRotate; |
| |
| switch (rotation) { |
| case display::Display::ROTATE_0: |
| return mojom::DisplayRotationOptions::kZeroDegrees; |
| |
| case display::Display::ROTATE_90: |
| return mojom::DisplayRotationOptions::k90Degrees; |
| |
| case display::Display::ROTATE_180: |
| return mojom::DisplayRotationOptions::k180Degrees; |
| |
| case display::Display::ROTATE_270: |
| return mojom::DisplayRotationOptions::k270Degrees; |
| } |
| } |
| |
| mojom::DisplayUnitInfoPtr GetDisplayUnitInfo(const display::Display& display, |
| int64_t primary_id) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| const display::ManagedDisplayInfo& display_info = |
| display_manager->GetDisplayInfo(display.id()); |
| |
| auto info = mojom::DisplayUnitInfo::New(); |
| info->id = base::NumberToString(display.id()); |
| info->name = display_manager->GetDisplayNameForId(display.id()); |
| |
| if (!display_info.manufacturer_id().empty() || |
| !display_info.product_id().empty() || |
| (display_info.year_of_manufacture() != |
| display::kInvalidYearOfManufacture)) { |
| info->edid = mojom::Edid::New(); |
| info->edid->manufacturer_id = display_info.manufacturer_id(); |
| info->edid->product_id = display_info.product_id(); |
| info->edid->year_of_manufacture = display_info.year_of_manufacture(); |
| } |
| |
| info->is_primary = display.id() == primary_id; |
| info->is_internal = display.IsInternal(); |
| info->is_enabled = true; |
| info->is_auto_rotation_allowed = |
| Shell::Get()->screen_orientation_controller()->IsAutoRotationAllowed(); |
| const bool has_accelerometer_support = |
| display.accelerometer_support() == |
| display::Display::AccelerometerSupport::AVAILABLE; |
| info->has_touch_support = |
| display.touch_support() == display::Display::TouchSupport::AVAILABLE; |
| info->has_accelerometer_support = has_accelerometer_support; |
| |
| const float device_dpi = display_info.device_dpi(); |
| info->dpi_x = device_dpi * display.size().width() / |
| display_info.bounds_in_native().width(); |
| info->dpi_y = device_dpi * display.size().height() / |
| display_info.bounds_in_native().height(); |
| |
| info->rotation_options = |
| RotationOptionsFromDisplayRotation(display.rotation()); |
| info->bounds = display.bounds(); |
| info->overscan = display_manager->GetOverscanInsets(display.id()); |
| info->work_area = display.work_area(); |
| |
| int display_mode_index = 0; |
| display::ManagedDisplayMode active_mode; |
| bool has_active_mode = display_manager->GetActiveModeForDisplayId( |
| display_info.id(), &active_mode); |
| for (const display::ManagedDisplayMode& display_mode : |
| display_info.display_modes()) { |
| info->available_display_modes.emplace_back( |
| GetDisplayMode(display_info, display_mode)); |
| if (has_active_mode && display_mode.IsEquivalent(active_mode)) |
| info->selected_display_mode_index = display_mode_index; |
| ++display_mode_index; |
| } |
| |
| info->display_zoom_factor = display_info.zoom_factor(); |
| if (has_active_mode) { |
| auto zoom_levels = display::GetDisplayZoomFactors(active_mode); |
| info->available_display_zoom_factors.reserve(zoom_levels.size()); |
| info->available_display_zoom_factors.assign(zoom_levels.begin(), |
| zoom_levels.end()); |
| } else { |
| info->available_display_zoom_factors.push_back(display_info.zoom_factor()); |
| } |
| |
| return info; |
| } |
| |
| // Validates that DisplayProperties are valid with the current DisplayManager |
| // configuration. Returns an error on failure. |
| mojom::DisplayConfigResult ValidateDisplayProperties( |
| const mojom::DisplayConfigProperties& properties, |
| const display::Display& display) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| |
| int64_t id = display.id(); |
| if (id == display::kInvalidDisplayId) |
| return mojom::DisplayConfigResult::kInvalidDisplayIdError; |
| |
| // Overscan cannot be changed for the internal display, and should be at most |
| // half of the screen size. |
| if (properties.overscan) { |
| if (display.IsInternal()) |
| return mojom::DisplayConfigResult::kNotSupportedOnInternalDisplayError; |
| |
| if (properties.overscan->left() < 0 || properties.overscan->top() < 0 || |
| properties.overscan->right() < 0 || properties.overscan->bottom() < 0) { |
| DLOG(ERROR) << "Negative overscan"; |
| return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError; |
| } |
| |
| const gfx::Insets overscan = display_manager->GetOverscanInsets(id); |
| int screen_width = display.bounds().width() + overscan.width(); |
| int screen_height = display.bounds().height() + overscan.height(); |
| |
| if ((properties.overscan->left() + properties.overscan->right()) * 2 > |
| screen_width || |
| (properties.overscan->top() + properties.overscan->bottom()) * 2 > |
| screen_height) { |
| DLOG(ERROR) << "Overscan: " << properties.overscan->ToString() |
| << " exceeds bounds: " << screen_width << "x" |
| << screen_height; |
| return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError; |
| } |
| } |
| |
| // The bounds cannot be changed for the primary display and should be inside |
| // a reasonable bounds. |
| if (properties.bounds_origin) { |
| const display::Display& primary = |
| display::Screen::GetScreen()->GetPrimaryDisplay(); |
| if (id == primary.id() || properties.set_primary) |
| return mojom::DisplayConfigResult::kNotSupportedOnInternalDisplayError; |
| if (properties.bounds_origin->x() > kMaxBoundsOrigin || |
| properties.bounds_origin->x() < -kMaxBoundsOrigin || |
| properties.bounds_origin->y() > kMaxBoundsOrigin || |
| properties.bounds_origin->y() < -kMaxBoundsOrigin) { |
| DLOG(ERROR) << "Bounds origin out of range"; |
| return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError; |
| } |
| } |
| |
| if (properties.display_zoom_factor > 0) { |
| display::ManagedDisplayMode current_mode; |
| if (!display_manager->GetActiveModeForDisplayId(id, ¤t_mode)) { |
| DLOG(ERROR) << "No active mode for display: " << id; |
| return mojom::DisplayConfigResult::kInvalidDisplayIdError; |
| } |
| // This check is added to limit the range of display zoom that can be |
| // applied via the system display API. The said range is such that when a |
| // display zoom is applied, the final logical width in pixels should lie |
| // within the range of 640 pixels and 4096 pixels. |
| const int landscape_width = |
| std::max(current_mode.size().width(), current_mode.size().height()); |
| const int max_allowed_width = |
| std::max(kDefaultMaxZoomWidth, landscape_width); |
| const int min_allowed_width = |
| std::min(kDefaultMinZoomWidth, landscape_width); |
| int current_width = static_cast<float>(landscape_width) / |
| current_mode.device_scale_factor(); |
| if (current_width / properties.display_zoom_factor > max_allowed_width || |
| current_width / properties.display_zoom_factor < min_allowed_width) { |
| DLOG(ERROR) << "Display zoom factor out of range"; |
| return mojom::DisplayConfigResult::kPropertyValueOutOfRangeError; |
| } |
| } |
| |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| // Sets the display layout for the target display in reference to the primary |
| // display. |
| void SetDisplayLayoutFromBounds(const gfx::Rect& primary_display_bounds, |
| int64_t primary_display_id, |
| const gfx::Rect& target_display_bounds, |
| int64_t target_display_id) { |
| display::DisplayPlacement placement( |
| display::DisplayLayout::CreatePlacementForRectangles( |
| primary_display_bounds, target_display_bounds)); |
| placement.display_id = target_display_id; |
| placement.parent_display_id = primary_display_id; |
| |
| std::unique_ptr<display::DisplayLayout> layout(new display::DisplayLayout); |
| layout->placement_list.push_back(placement); |
| layout->primary_id = primary_display_id; |
| |
| Shell::Get()->display_configuration_controller()->SetDisplayLayout( |
| std::move(layout)); |
| } |
| |
| // Attempts to set the display mode for display |id|. |
| mojom::DisplayConfigResult SetDisplayMode( |
| int64_t id, |
| const mojom::DisplayMode& display_mode, |
| mojom::DisplayConfigSource source) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| |
| display::ManagedDisplayMode current_mode; |
| if (!display_manager->GetActiveModeForDisplayId(id, ¤t_mode)) |
| return mojom::DisplayConfigResult::kInvalidDisplayIdError; |
| |
| display::ManagedDisplayMode new_mode( |
| display_mode.size_in_native_pixels, display_mode.refresh_rate, |
| display_mode.is_interlaced, display_mode.is_native, |
| display_mode.device_scale_factor); |
| |
| if (!new_mode.IsEquivalent(current_mode)) { |
| // For the internal display, the display mode will be applied directly. |
| // Otherwise a confirm/revert notification will be prepared first, and the |
| // display mode will be applied. If the user accepts the mode change by |
| // dismissing the notification, MaybeStoreDisplayPrefs() will be called back |
| // to persist the new preferences. |
| if (!Shell::Get() |
| ->resolution_notification_controller() |
| ->PrepareNotificationAndSetDisplayMode( |
| id, current_mode, new_mode, source, base::BindOnce([]() { |
| Shell::Get()->display_prefs()->MaybeStoreDisplayPrefs(); |
| }))) { |
| return mojom::DisplayConfigResult::kSetDisplayModeError; |
| } |
| } |
| |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| display::TouchCalibrationData::CalibrationPointPair GetCalibrationPair( |
| const mojom::TouchCalibrationPair& pair) { |
| return std::make_pair(pair.display_point, pair.touch_point); |
| } |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // CrosDisplayConfig::ObserverImpl: |
| |
| // Observes display and tablet mode events, and notifies the |
| // CrosDisplayConfigObservers with OnDisplayConfigChanged() in response to those |
| // events. |
| class CrosDisplayConfig::ObserverImpl |
| : public display::DisplayObserver, |
| public TabletModeObserver, |
| public ScreenOrientationController::Observer { |
| public: |
| explicit ObserverImpl() { |
| Shell::Get()->tablet_mode_controller()->AddObserver(this); |
| Shell::Get()->screen_orientation_controller()->AddObserver(this); |
| } |
| |
| ObserverImpl(const ObserverImpl&) = delete; |
| ObserverImpl& operator=(const ObserverImpl&) = delete; |
| |
| ~ObserverImpl() override { |
| Shell::Get()->screen_orientation_controller()->RemoveObserver(this); |
| Shell::Get()->tablet_mode_controller()->RemoveObserver(this); |
| } |
| |
| void AddObserver( |
| mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> |
| observer) { |
| observers_.Add(mojo::AssociatedRemote<mojom::CrosDisplayConfigObserver>( |
| std::move(observer))); |
| } |
| |
| // display::DisplayObserver: |
| void OnDisplayAdded(const display::Display& new_display) override { |
| NotifyObserversDisplayConfigChanged(); |
| } |
| |
| void OnDisplayRemoved(const display::Display& old_display) override { |
| NotifyObserversDisplayConfigChanged(); |
| } |
| |
| void OnDisplayMetricsChanged(const display::Display& display, |
| uint32_t metrics) override { |
| NotifyObserversDisplayConfigChanged(); |
| } |
| |
| // TabletModeObserver: |
| void OnTabletPhysicalStateChanged() override { |
| NotifyObserversDisplayConfigChanged(); |
| } |
| |
| // ScreenOrientationController::Observer: |
| void OnUserRotationLockChanged() override { |
| NotifyObserversDisplayConfigChanged(); |
| } |
| |
| private: |
| void NotifyObserversDisplayConfigChanged() { |
| for (auto& observer : observers_) |
| observer->OnDisplayConfigChanged(); |
| } |
| |
| mojo::AssociatedRemoteSet<mojom::CrosDisplayConfigObserver> observers_; |
| display::ScopedDisplayObserver display_observer_{this}; |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // CrosDisplayConfig: |
| |
| CrosDisplayConfig::CrosDisplayConfig() |
| : observer_impl_(std::make_unique<ObserverImpl>()) {} |
| |
| CrosDisplayConfig::~CrosDisplayConfig() = default; |
| |
| void CrosDisplayConfig::BindReceiver( |
| mojo::PendingReceiver<mojom::CrosDisplayConfigController> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void CrosDisplayConfig::AddObserver( |
| mojo::PendingAssociatedRemote<mojom::CrosDisplayConfigObserver> observer) { |
| observer_impl_->AddObserver(std::move(observer)); |
| } |
| |
| void CrosDisplayConfig::GetDisplayLayoutInfo( |
| GetDisplayLayoutInfoCallback callback) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| |
| auto info = mojom::DisplayLayoutInfo::New(); |
| if (display_manager->IsInUnifiedMode()) { |
| info->layout_mode = mojom::DisplayLayoutMode::kUnified; |
| } else if (display_manager->IsInMirrorMode()) { |
| info->layout_mode = mojom::DisplayLayoutMode::kMirrored; |
| info->mirror_source_id = |
| base::NumberToString(display_manager->mirroring_source_id()); |
| info->mirror_destination_ids = std::vector<std::string>(); |
| for (int64_t id : display_manager->GetMirroringDestinationDisplayIdList()) |
| info->mirror_destination_ids->emplace_back(base::NumberToString(id)); |
| } else { |
| info->layout_mode = mojom::DisplayLayoutMode::kNormal; |
| } |
| |
| if (display_manager->IsInUnifiedMode()) { |
| info->layouts = GetDisplayUnifiedLayouts(); |
| } else if (display_manager->num_connected_displays() > 1) { |
| info->layouts = GetDisplayLayouts(); |
| } |
| |
| std::move(callback).Run(std::move(info)); |
| } |
| |
| mojom::DisplayConfigResult SetDisplayLayouts( |
| const std::vector<mojom::DisplayLayoutPtr>& layouts) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| display::DisplayLayoutBuilder builder( |
| display_manager->GetCurrentResolvedDisplayLayout()); |
| int64_t root_id = display::kInvalidDisplayId; |
| std::set<int64_t> layout_ids; |
| builder.ClearPlacements(); |
| for (const mojom::DisplayLayoutPtr& layout_ptr : layouts) { |
| const mojom::DisplayLayout& layout = *layout_ptr; |
| display::Display display = GetDisplay(layout.id); |
| if (display.id() == display::kInvalidDisplayId) { |
| LOG(ERROR) << "Display layout has invalid id: " << layout.id; |
| return mojom::DisplayConfigResult::kInvalidDisplayIdError; |
| } |
| display::Display parent = GetDisplay(layout.parent_id); |
| if (parent.id() == display::kInvalidDisplayId) { |
| if (root_id != display::kInvalidDisplayId) { |
| LOG(ERROR) << "Display layout has invalid parent: " << layout.parent_id; |
| return mojom::DisplayConfigResult::kInvalidDisplayLayoutError; |
| } |
| root_id = display.id(); |
| continue; // No placement for root (primary) display. |
| } |
| layout_ids.insert(display.id()); |
| display::DisplayPlacement::Position position = |
| GetDisplayPlacementPosition(layout.position); |
| builder.AddDisplayPlacement(display.id(), parent.id(), position, |
| layout.offset); |
| } |
| |
| const display::DisplayIdList display_ids = |
| display_manager->GetConnectedDisplayIdList(); |
| std::unique_ptr<display::DisplayLayout> layout = builder.Build(); |
| if (display_manager->IsInUnifiedMode()) { |
| if (root_id == display::kInvalidDisplayId) { |
| // Look for a display with no layout info to use as the root. |
| for (int64_t id : display_ids) { |
| if (!base::Contains(layout_ids, id)) { |
| root_id = id; |
| break; |
| } |
| } |
| if (root_id == display::kInvalidDisplayId) { |
| LOG(ERROR) << "Invalid unified layout: No root id"; |
| return mojom::DisplayConfigResult::kInvalidDisplayLayoutError; |
| } |
| } |
| layout->primary_id = root_id; |
| display::UnifiedDesktopLayoutMatrix matrix; |
| if (!display::BuildUnifiedDesktopMatrix(display_ids, *layout, &matrix)) { |
| LOG(ERROR) << "Invalid unified layout: No proper conversion to a matrix"; |
| return mojom::DisplayConfigResult::kInvalidDisplayLayoutError; |
| } |
| Shell::Get() |
| ->display_configuration_controller() |
| ->SetUnifiedDesktopLayoutMatrix(matrix); |
| } else { |
| if (!display::DisplayLayout::Validate(display_ids, *layout)) { |
| return mojom::DisplayConfigResult::kInvalidDisplayLayoutError; |
| } |
| Shell::Get()->display_configuration_controller()->SetDisplayLayout( |
| std::move(layout)); |
| } |
| return mojom::DisplayConfigResult::kSuccess; |
| } |
| |
| void CrosDisplayConfig::SetDisplayLayoutInfo( |
| mojom::DisplayLayoutInfoPtr info, |
| SetDisplayLayoutInfoCallback callback) { |
| mojom::DisplayConfigResult result = SetDisplayLayoutMode(*info); |
| if (result != mojom::DisplayConfigResult::kSuccess) { |
| std::move(callback).Run(result); |
| return; |
| } |
| if (info->layouts) { |
| result = SetDisplayLayouts(*info->layouts); |
| if (result != mojom::DisplayConfigResult::kSuccess) { |
| std::move(callback).Run(result); |
| return; |
| } |
| } |
| std::move(callback).Run(mojom::DisplayConfigResult::kSuccess); |
| } |
| |
| void CrosDisplayConfig::GetDisplayUnitInfoList( |
| bool single_unified, |
| GetDisplayUnitInfoListCallback callback) { |
| std::vector<mojom::DisplayUnitInfoPtr> info_list; |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| |
| std::vector<display::Display> displays; |
| int64_t primary_id; |
| if (!display_manager->IsInUnifiedMode()) { |
| displays = display::Screen::GetScreen()->GetAllDisplays(); |
| primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id(); |
| } else if (single_unified) { |
| for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) |
| displays.push_back(display_manager->GetDisplayAt(i)); |
| primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id(); |
| } else { |
| displays = display_manager->software_mirroring_display_list(); |
| primary_id = Shell::Get() |
| ->display_configuration_controller() |
| ->GetPrimaryMirroringDisplayForUnifiedDesktop() |
| .id(); |
| } |
| |
| for (const display::Display& display : displays) |
| info_list.emplace_back(GetDisplayUnitInfo(display, primary_id)); |
| std::move(callback).Run(std::move(info_list)); |
| } |
| |
| void CrosDisplayConfig::SetDisplayProperties( |
| const std::string& id, |
| mojom::DisplayConfigPropertiesPtr properties, |
| mojom::DisplayConfigSource source, |
| SetDisplayPropertiesCallback callback) { |
| const display::Display display = GetDisplay(id); |
| mojom::DisplayConfigResult result = |
| ValidateDisplayProperties(*properties, display); |
| if (result != mojom::DisplayConfigResult::kSuccess) { |
| std::move(callback).Run(result); |
| return; |
| } |
| |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| DisplayConfigurationController* display_configuration_controller = |
| Shell::Get()->display_configuration_controller(); |
| const display::Display& primary = |
| display::Screen::GetScreen()->GetPrimaryDisplay(); |
| |
| if (properties->set_primary && display.id() != primary.id()) { |
| display_configuration_controller->SetPrimaryDisplayId( |
| display.id(), false /* don't throttle */); |
| } |
| |
| if (properties->overscan) |
| display_manager->SetOverscanInsets(display.id(), *properties->overscan); |
| |
| if (properties->rotation) { |
| const mojom::DisplayRotationOptions rotation_options = |
| properties->rotation->rotation; |
| auto* screen_orientation_controller = |
| Shell::Get()->screen_orientation_controller(); |
| const bool is_auto_rotation_allowed = |
| screen_orientation_controller->IsAutoRotationAllowed(); |
| const bool auto_rotate_requested = |
| rotation_options == mojom::DisplayRotationOptions::kAutoRotate; |
| if (auto_rotate_requested && !is_auto_rotation_allowed) { |
| LOG(ERROR) << "Auto-rotate is supported when the device is in physical " |
| << "tablet state or kSupportsClamshellAutoRotation is set. " |
| << "This will be treated as a request to set the display " |
| << "rotation to 0 degrees."; |
| } |
| |
| display::Display::Rotation rotation = |
| DisplayRotationFromRotationOptions(properties->rotation->rotation); |
| if (is_auto_rotation_allowed) { |
| if (auto_rotate_requested) { |
| if (screen_orientation_controller->user_rotation_locked()) |
| screen_orientation_controller->ToggleUserRotationLock(); |
| } else { |
| screen_orientation_controller->SetLockToRotation(rotation); |
| } |
| } else { |
| display_configuration_controller->SetDisplayRotation( |
| display.id(), rotation, display::Display::RotationSource::USER); |
| } |
| } |
| |
| if (properties->bounds_origin && |
| *properties->bounds_origin != display.bounds().origin()) { |
| gfx::Rect display_bounds = display.bounds(); |
| display_bounds.Offset( |
| properties->bounds_origin->x() - display.bounds().x(), |
| properties->bounds_origin->y() - display.bounds().y()); |
| SetDisplayLayoutFromBounds(primary.bounds(), primary.id(), display_bounds, |
| display.id()); |
| } |
| |
| if (properties->display_zoom_factor > 0) { |
| display_manager->UpdateZoomFactor(display.id(), |
| properties->display_zoom_factor); |
| } |
| |
| // Set the display mode. Note: if this returns an error, other properties |
| // will have already been applied. TODO(stevenjb): Validate the display mode |
| // before applying any properties. |
| if (properties->display_mode) { |
| result = SetDisplayMode(display.id(), *properties->display_mode, source); |
| if (result != mojom::DisplayConfigResult::kSuccess) { |
| std::move(callback).Run(result); |
| return; |
| } |
| } |
| |
| std::move(callback).Run(mojom::DisplayConfigResult::kSuccess); |
| } |
| |
| void CrosDisplayConfig::SetUnifiedDesktopEnabled(bool enabled) { |
| GetDisplayManager()->SetUnifiedDesktopEnabled(enabled); |
| } |
| |
| void CrosDisplayConfig::OverscanCalibration( |
| const std::string& display_id, |
| mojom::DisplayConfigOperation op, |
| const absl::optional<gfx::Insets>& delta, |
| OverscanCalibrationCallback callback) { |
| display::Display display = GetDisplay(display_id); |
| if (display.id() == display::kInvalidDisplayId) { |
| std::move(callback).Run(mojom::DisplayConfigResult::kInvalidDisplayIdError); |
| return; |
| } |
| |
| OverscanCalibrator* calibrator = GetOverscanCalibrator(display_id); |
| if (!calibrator && op != mojom::DisplayConfigOperation::kStart) { |
| LOG(ERROR) << "Calibrator does not exist for op=" << op; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationNotAvailableError); |
| return; |
| } |
| switch (op) { |
| case mojom::DisplayConfigOperation::kStart: { |
| DVLOG(1) << "OverscanCalibrationStart: " << display_id; |
| gfx::Insets insets = |
| Shell::Get()->window_tree_host_manager()->GetOverscanInsets( |
| display.id()); |
| if (calibrator) |
| DVLOG(1) << "Replacing existing calibrator for id: " << display_id; |
| overscan_calibrators_[display_id] = |
| std::make_unique<OverscanCalibrator>(display, insets); |
| break; |
| } |
| case mojom::DisplayConfigOperation::kAdjust: |
| DVLOG(1) << "OverscanCalibrationAdjust: " << display_id; |
| if (!delta) { |
| LOG(ERROR) << "Delta not provided for for adjust: " << display_id; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationFailedError); |
| return; |
| } |
| calibrator->UpdateInsets(calibrator->insets() + *delta); |
| break; |
| case mojom::DisplayConfigOperation::kReset: |
| DVLOG(1) << "OverscanCalibrationReset: " << display_id; |
| calibrator->Reset(); |
| break; |
| case mojom::DisplayConfigOperation::kComplete: |
| DVLOG(1) << "OverscanCalibrationComplete: " << display_id; |
| calibrator->Commit(); |
| overscan_calibrators_[display_id].reset(); |
| break; |
| case mojom::DisplayConfigOperation::kShowNative: |
| LOG(ERROR) << "Operation not supported: " << op; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kInvalidOperationError); |
| return; |
| } |
| std::move(callback).Run(mojom::DisplayConfigResult::kSuccess); |
| } |
| |
| void CrosDisplayConfig::TouchCalibration(const std::string& display_id, |
| mojom::DisplayConfigOperation op, |
| mojom::TouchCalibrationPtr calibration, |
| TouchCalibrationCallback callback) { |
| display::Display display = GetDisplay(display_id); |
| if (display.id() == display::kInvalidDisplayId) { |
| std::move(callback).Run(mojom::DisplayConfigResult::kInvalidDisplayIdError); |
| return; |
| } |
| if (display.IsInternal()) { |
| LOG(ERROR) << "Internal display cannot be calibrated for touch: " |
| << display_id; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationNotAvailableError); |
| return; |
| } |
| if (!display::HasExternalTouchscreenDevice()) { |
| LOG(ERROR) |
| << "Touch calibration called with no external touch screen device."; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationNotAvailableError); |
| return; |
| } |
| |
| if (op == mojom::DisplayConfigOperation::kStart || |
| op == mojom::DisplayConfigOperation::kShowNative) { |
| if (touch_calibrator_ && touch_calibrator_->IsCalibrating()) { |
| LOG(ERROR) << "Touch calibration already active."; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationInProgressError); |
| return; |
| } |
| if (!touch_calibrator_) |
| touch_calibrator_ = std::make_unique<TouchCalibratorController>(); |
| if (op == mojom::DisplayConfigOperation::kShowNative) { |
| // For native calibration, |callback| is not run until calibration |
| // completes. |
| touch_calibrator_->StartCalibration( |
| display, /*is_custom_calibration=*/false, |
| base::BindOnce( |
| [](TouchCalibrationCallback callback, bool result) { |
| std::move(callback).Run( |
| result |
| ? mojom::DisplayConfigResult::kSuccess |
| : mojom::DisplayConfigResult::kCalibrationFailedError); |
| }, |
| std::move(callback))); |
| return; |
| } |
| // For custom calibration, start calibration and run |callback| now. |
| touch_calibrator_->StartCalibration(display, /*is_custom_calibration=*/true, |
| base::OnceCallback<void(bool)>()); |
| std::move(callback).Run(mojom::DisplayConfigResult::kSuccess); |
| return; |
| } |
| |
| if (op == mojom::DisplayConfigOperation::kReset) { |
| Shell::Get()->display_manager()->ClearTouchCalibrationData(display.id(), |
| absl::nullopt); |
| std::move(callback).Run(mojom::DisplayConfigResult::kSuccess); |
| return; |
| } |
| |
| if (op != mojom::DisplayConfigOperation::kComplete) { |
| LOG(ERROR) << "Unknown operation: " << op; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationNotStartedError); |
| return; |
| } |
| |
| if (!touch_calibrator_) { |
| LOG(ERROR) << "Touch calibration not active."; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationNotStartedError); |
| return; |
| } |
| |
| if (!calibration || calibration->pairs.size() != 4) { |
| LOG(ERROR) << "Touch calibration requires four calibration pairs."; |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationInvalidDataError); |
| return; |
| } |
| |
| Shell::Get()->touch_transformer_controller()->SetForCalibration(false); |
| |
| display::TouchCalibrationData::CalibrationPointPairQuad calibration_points; |
| calibration_points[0] = GetCalibrationPair(*calibration->pairs[0]); |
| calibration_points[1] = GetCalibrationPair(*calibration->pairs[1]); |
| calibration_points[2] = GetCalibrationPair(*calibration->pairs[2]); |
| calibration_points[3] = GetCalibrationPair(*calibration->pairs[3]); |
| |
| gfx::Size bounds = calibration->bounds; |
| for (size_t row = 0; row < calibration_points.size(); row++) { |
| // Coordinates for display and touch point cannot be negative. |
| if (calibration_points[row].first.x() < 0 || |
| calibration_points[row].first.y() < 0 || |
| calibration_points[row].second.x() < 0 || |
| calibration_points[row].second.y() < 0) { |
| LOG(ERROR) |
| << "Display points and touch points cannot have negative coordinates"; |
| touch_calibrator_->StopCalibrationAndResetParams(); |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationInvalidDataError); |
| return; |
| } |
| // Coordinates for display points cannot be greater than the screen |
| // bounds. |
| if (calibration_points[row].first.x() > bounds.width() || |
| calibration_points[row].first.y() > bounds.height()) { |
| LOG(ERROR) << "Display point coordinates cannot be more than size of the " |
| "display."; |
| touch_calibrator_->StopCalibrationAndResetParams(); |
| std::move(callback).Run( |
| mojom::DisplayConfigResult::kCalibrationInvalidDataError); |
| return; |
| } |
| } |
| |
| touch_calibrator_->CompleteCalibration(calibration_points, bounds); |
| std::move(callback).Run(mojom::DisplayConfigResult::kSuccess); |
| } |
| |
| OverscanCalibrator* CrosDisplayConfig::GetOverscanCalibrator( |
| const std::string& id) { |
| auto iter = overscan_calibrators_.find(id); |
| return iter == overscan_calibrators_.end() ? nullptr : iter->second.get(); |
| } |
| |
| void CrosDisplayConfig::HighlightDisplay(int64_t display_id) { |
| Shell::Get()->display_highlight_controller()->SetHighlightedDisplay( |
| display_id); |
| } |
| |
| void CrosDisplayConfig::DragDisplayDelta(int64_t display_id, |
| int32_t delta_x, |
| int32_t delta_y) { |
| DCHECK(features::IsDisplayAlignmentAssistanceEnabled()); |
| Shell::Get()->display_alignment_controller()->DisplayDragged( |
| display_id, delta_x, delta_y); |
| } |
| |
| } // namespace ash |