| // 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 "ui/display/manager/util/display_manager_util.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cinttypes> |
| #include <cmath> |
| #include <set> |
| #include <sstream> |
| #include <vector> |
| |
| #include "base/check_op.h" |
| #include "base/command_line.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "chromeos/ash/components/system/statistics_provider.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/display/display_switches.h" |
| #include "ui/display/manager/managed_display_info.h" |
| #include "ui/display/types/display_snapshot.h" |
| #include "ui/display/util/display_util.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/geometry/size_f.h" |
| |
| namespace display { |
| |
| std::string DisplayPowerStateToString(chromeos::DisplayPowerState state) { |
| switch (state) { |
| case chromeos::DISPLAY_POWER_ALL_ON: |
| return "ALL_ON"; |
| case chromeos::DISPLAY_POWER_ALL_OFF: |
| return "ALL_OFF"; |
| case chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON: |
| return "INTERNAL_OFF_EXTERNAL_ON"; |
| case chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF: |
| return "INTERNAL_ON_EXTERNAL_OFF"; |
| default: |
| return "unknown (" + base::NumberToString(state) + ")"; |
| } |
| } |
| |
| std::string VrrStateToString(const base::flat_set<int64_t>& state) { |
| std::vector<std::string> entries; |
| for (const int64_t id : state) { |
| entries.push_back(base::NumberToString(id)); |
| } |
| return "{" + base::JoinString(entries, ", ") + "}"; |
| } |
| |
| std::string RefreshRateOverrideToString( |
| const std::unordered_map<int64_t, float>& refresh_rate_override) { |
| std::vector<std::string> entries; |
| for (const auto& [id, refresh_rate] : refresh_rate_override) { |
| entries.push_back(base::StringPrintf("%" PRId64 ": %f", id, refresh_rate)); |
| } |
| return "{" + base::JoinString(entries, ", ") + "}"; |
| } |
| |
| int GetDisplayPower( |
| const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays, |
| chromeos::DisplayPowerState state, |
| std::vector<bool>* display_power) { |
| int num_on_displays = 0; |
| if (display_power) { |
| display_power->resize(displays.size()); |
| } |
| |
| for (size_t i = 0; i < displays.size(); ++i) { |
| bool internal = displays[i]->type() == DISPLAY_CONNECTION_TYPE_INTERNAL; |
| bool on = |
| state == chromeos::DISPLAY_POWER_ALL_ON || |
| (state == chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON && |
| !internal) || |
| (state == chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF && internal); |
| if (display_power) { |
| (*display_power)[i] = on; |
| } |
| if (on) { |
| num_on_displays++; |
| } |
| } |
| return num_on_displays; |
| } |
| |
| bool WithinEpsilon(float a, float b) { |
| return std::abs(a - b) < std::numeric_limits<float>::epsilon(); |
| } |
| |
| std::string MultipleDisplayStateToString(MultipleDisplayState state) { |
| switch (state) { |
| case MULTIPLE_DISPLAY_STATE_INVALID: |
| return "INVALID"; |
| case MULTIPLE_DISPLAY_STATE_HEADLESS: |
| return "HEADLESS"; |
| case MULTIPLE_DISPLAY_STATE_SINGLE: |
| return "SINGLE"; |
| case MULTIPLE_DISPLAY_STATE_MULTI_MIRROR: |
| return "DUAL_MIRROR"; |
| case MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED: |
| return "MULTI_EXTENDED"; |
| } |
| NOTREACHED() << "Unknown state " << state; |
| } |
| |
| bool GetContentProtectionMethods(DisplayConnectionType type, |
| uint32_t* protection_mask) { |
| switch (type) { |
| case DISPLAY_CONNECTION_TYPE_NONE: |
| case DISPLAY_CONNECTION_TYPE_UNKNOWN: |
| return false; |
| |
| case DISPLAY_CONNECTION_TYPE_INTERNAL: |
| case DISPLAY_CONNECTION_TYPE_VGA: |
| case DISPLAY_CONNECTION_TYPE_NETWORK: |
| *protection_mask = CONTENT_PROTECTION_METHOD_NONE; |
| return true; |
| |
| case DISPLAY_CONNECTION_TYPE_DISPLAYPORT: |
| case DISPLAY_CONNECTION_TYPE_DVI: |
| case DISPLAY_CONNECTION_TYPE_HDMI: |
| *protection_mask = CONTENT_PROTECTION_METHOD_HDCP; |
| return true; |
| } |
| } |
| |
| std::vector<float> GetDisplayZoomFactors(const ManagedDisplayMode& mode) { |
| // Internal displays have an internal device scale factor greater than 1 |
| // associated with them. This means that if we use the usual logic, we would |
| // end up with a list of zoom levels that the user may not find very useful. |
| // Take for example the pixelbook with device scale factor of 2. Based on the |
| // usual approach, we would get a zoom range of 90% to 150%. This means: |
| // 1. Users will not be able to go to the native resolution which is |
| // achieved at 50% zoom level. |
| // 2. Due to the device scale factor, the display already has a low DPI and |
| // users dont want to zoom in, they mostly want to zoom out and add more |
| // pixels to the screen. But we only provide a zoom list of 90% to 150%. |
| // This clearly shows we need a different logic to handle internal displays |
| // which have lower DPI due to the device scale factor associated with them. |
| // |
| // OTOH if we look at an external display with a device scale factor of 1 but |
| // the same resolution as the pixel book, the DPI would usually be very high |
| // and users mostly want to zoom in to reduce the number of pixels on the |
| // screen. So having a range of 90% to 130% makes sense. |
| // TODO(malaykeshav): Investigate if we can use DPI instead of resolution or |
| // device scale factor to decide the list of zoom levels. |
| if (mode.device_scale_factor() > 1.f) { |
| return GetDisplayZoomFactorForDsf(mode.device_scale_factor()); |
| } |
| |
| // There may be cases where the device scale factor is less than 1. This can |
| // happen during testing or local linux builds. |
| const int effective_width = std::round( |
| static_cast<float>(std::max(mode.size().width(), mode.size().height())) / |
| mode.device_scale_factor()); |
| return GetDisplayZoomFactorsByDisplayWidth(effective_width); |
| } |
| |
| std::vector<float> GetDisplayZoomFactorsByDisplayWidth( |
| const int display_width) { |
| std::size_t index = kZoomListBuckets.size() - 1; |
| while (index > 0 && display_width < kZoomListBuckets[index].first) { |
| index--; |
| } |
| DCHECK_GE(display_width, kZoomListBuckets[index].first); |
| |
| const auto& zoom_array = kZoomListBuckets[index].second; |
| return std::vector<float>(zoom_array.begin(), zoom_array.end()); |
| } |
| |
| std::vector<float> GetDisplayZoomFactorForDsf(float dsf) { |
| DCHECK(!WithinEpsilon(dsf, 1.f)); |
| DCHECK_GT(dsf, 1.f); |
| |
| for (const auto& bucket : kZoomListBucketsForDsf) { |
| if (WithinEpsilon(bucket.first, dsf)) { |
| return std::vector<float>(bucket.second.begin(), bucket.second.end()); |
| } |
| } |
| NOTREACHED() << "Received a DSF not on the list: " << dsf; |
| } |
| |
| ManagedDisplayInfo::ManagedDisplayModeList CreateInternalManagedDisplayModeList( |
| const ManagedDisplayMode& native_mode) { |
| ManagedDisplayMode mode(native_mode.size(), native_mode.refresh_rate(), |
| native_mode.is_interlaced(), true, |
| native_mode.device_scale_factor()); |
| return ManagedDisplayInfo::ManagedDisplayModeList{mode}; |
| } |
| |
| UnifiedDisplayModeParam::UnifiedDisplayModeParam(float dsf, |
| float scale, |
| bool is_default) |
| : device_scale_factor(dsf), |
| display_bounds_scale(scale), |
| is_default_mode(is_default) {} |
| |
| ManagedDisplayInfo::ManagedDisplayModeList CreateUnifiedManagedDisplayModeList( |
| const ManagedDisplayMode& native_mode, |
| const std::vector<UnifiedDisplayModeParam>& modes_param_list) { |
| ManagedDisplayInfo::ManagedDisplayModeList display_mode_list; |
| display_mode_list.reserve(modes_param_list.size()); |
| |
| for (auto& param : modes_param_list) { |
| gfx::SizeF scaled_size(native_mode.size()); |
| scaled_size.Scale(param.display_bounds_scale); |
| display_mode_list.emplace_back( |
| gfx::ToFlooredSize(scaled_size), native_mode.refresh_rate(), |
| native_mode.is_interlaced(), |
| param.is_default_mode ? true : false /* native */, |
| param.device_scale_factor); |
| } |
| // Sort the mode by the size in DIP. |
| std::sort(display_mode_list.begin(), display_mode_list.end(), |
| [](const ManagedDisplayMode& a, const ManagedDisplayMode& b) { |
| return a.GetSizeInDIP().GetArea() < b.GetSizeInDIP().GetArea(); |
| }); |
| return display_mode_list; |
| } |
| |
| bool ForceFirstDisplayInternal() { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| // Touch view mode is only available to internal display. We force the |
| // display as internal for emulator to test touch view mode. |
| // However, display mode change is only available to external display. To run |
| // tests on a different display mode from default we will need to set the flag |
| // --drm-virtual-connector-is-external. |
| return command_line->HasSwitch(::switches::kUseFirstDisplayAsInternal) || |
| (ash::system::StatisticsProvider::GetInstance()->IsRunningOnVm() && |
| !command_line->HasSwitch(switches::kDRMVirtualConnectorIsExternal)); |
| } |
| |
| bool ComputeBoundary(const gfx::Rect& a_bounds, |
| const gfx::Rect& b_bounds, |
| gfx::Rect* a_edge, |
| gfx::Rect* b_edge) { |
| // Find touching side. |
| int rx = std::max(a_bounds.x(), b_bounds.x()); |
| int ry = std::max(a_bounds.y(), b_bounds.y()); |
| int rr = std::min(a_bounds.right(), b_bounds.right()); |
| int rb = std::min(a_bounds.bottom(), b_bounds.bottom()); |
| |
| DisplayPlacement::Position position; |
| if (rb == ry) { |
| // top bottom |
| if (rr <= rx) { |
| // Top and bottom align, but no edges are shared. |
| return false; |
| } |
| |
| if (a_bounds.bottom() == b_bounds.y()) { |
| position = DisplayPlacement::BOTTOM; |
| } else if (a_bounds.y() == b_bounds.bottom()) { |
| position = DisplayPlacement::TOP; |
| } else { |
| return false; |
| } |
| } else if (rr == rx) { |
| // left right |
| if (rb <= ry) { |
| // Left and right align, but no edges are shared. |
| return false; |
| } |
| |
| if (a_bounds.right() == b_bounds.x()) { |
| position = DisplayPlacement::RIGHT; |
| } else if (a_bounds.x() == b_bounds.right()) { |
| position = DisplayPlacement::LEFT; |
| } else { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| |
| switch (position) { |
| case DisplayPlacement::TOP: |
| case DisplayPlacement::BOTTOM: { |
| int left = std::max(a_bounds.x(), b_bounds.x()); |
| int right = std::min(a_bounds.right(), b_bounds.right()); |
| if (position == DisplayPlacement::TOP) { |
| a_edge->SetRect(left, a_bounds.y(), right - left, 1); |
| b_edge->SetRect(left, b_bounds.bottom() - 1, right - left, 1); |
| } else { |
| a_edge->SetRect(left, a_bounds.bottom() - 1, right - left, 1); |
| b_edge->SetRect(left, b_bounds.y(), right - left, 1); |
| } |
| break; |
| } |
| case DisplayPlacement::LEFT: |
| case DisplayPlacement::RIGHT: { |
| int top = std::max(a_bounds.y(), b_bounds.y()); |
| int bottom = std::min(a_bounds.bottom(), b_bounds.bottom()); |
| if (position == DisplayPlacement::LEFT) { |
| a_edge->SetRect(a_bounds.x(), top, 1, bottom - top); |
| b_edge->SetRect(b_bounds.right() - 1, top, 1, bottom - top); |
| } else { |
| a_edge->SetRect(a_bounds.right() - 1, top, 1, bottom - top); |
| b_edge->SetRect(b_bounds.x(), top, 1, bottom - top); |
| } |
| break; |
| } |
| } |
| return true; |
| } |
| |
| bool ComputeBoundary(const Display& display_a, |
| const Display& display_b, |
| gfx::Rect* a_edge_in_screen, |
| gfx::Rect* b_edge_in_screen) { |
| return ComputeBoundary(display_a.bounds(), display_b.bounds(), |
| a_edge_in_screen, b_edge_in_screen); |
| } |
| |
| DisplayIdList CreateDisplayIdList(const Displays& list) { |
| return GenerateDisplayIdList(list, &Display::id); |
| } |
| |
| DisplayIdList CreateDisplayIdList(const DisplayInfoList& updated_displays) { |
| return GenerateDisplayIdList(updated_displays, |
| &display::ManagedDisplayInfo::id); |
| } |
| |
| void SortDisplayIdList(DisplayIdList* ids) { |
| std::sort(ids->begin(), ids->end(), |
| [](int64_t a, int64_t b) { return CompareDisplayIds(a, b); }); |
| } |
| |
| bool IsDisplayIdListSorted(const DisplayIdList& list) { |
| return std::is_sorted(list.begin(), list.end(), [](int64_t a, int64_t b) { |
| return CompareDisplayIds(a, b); |
| }); |
| } |
| |
| std::string DisplayIdListToString(const DisplayIdList& list) { |
| std::stringstream s; |
| const char* sep = ""; |
| for (int64_t id : list) { |
| s << sep << id; |
| sep = ","; |
| } |
| return s.str(); |
| } |
| |
| int64_t GetDisplayIdWithoutOutputIndex(int64_t id) { |
| constexpr uint64_t kMask = ~static_cast<uint64_t>(0xFF); |
| return static_cast<int64_t>(kMask & id); |
| } |
| |
| MixedMirrorModeParams::MixedMirrorModeParams(int64_t src_id, |
| const DisplayIdList& dst_ids) |
| : source_id(src_id), destination_ids(dst_ids) {} |
| |
| MixedMirrorModeParams::MixedMirrorModeParams( |
| const MixedMirrorModeParams& mixed_params) = default; |
| |
| MixedMirrorModeParams::~MixedMirrorModeParams() = default; |
| |
| MixedMirrorModeParamsErrors ValidateParamsForMixedMirrorMode( |
| const DisplayIdList& connected_display_ids, |
| const MixedMirrorModeParams& mixed_params) { |
| if (connected_display_ids.size() <= 1) { |
| return MixedMirrorModeParamsErrors::kErrorSingleDisplay; |
| } |
| |
| std::set<int64_t> all_display_ids; |
| for (auto& id : connected_display_ids) { |
| all_display_ids.insert(id); |
| } |
| if (!all_display_ids.count(mixed_params.source_id)) { |
| return MixedMirrorModeParamsErrors::kErrorSourceIdNotFound; |
| } |
| |
| // This set is used to check duplicate id. |
| std::set<int64_t> specified_display_ids; |
| specified_display_ids.insert(mixed_params.source_id); |
| |
| if (mixed_params.destination_ids.empty()) { |
| return MixedMirrorModeParamsErrors::kErrorDestinationIdsEmpty; |
| } |
| |
| for (auto& id : mixed_params.destination_ids) { |
| if (!all_display_ids.count(id)) { |
| return MixedMirrorModeParamsErrors::kErrorDestinationIdNotFound; |
| } |
| if (!specified_display_ids.insert(id).second) { |
| return MixedMirrorModeParamsErrors::kErrorDuplicateId; |
| } |
| } |
| return MixedMirrorModeParamsErrors::kSuccess; |
| } |
| |
| } // namespace display |