| // Copyright 2018 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/ash/policy/display/display_resolution_handler.h" |
| |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/values.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "chromeos/ash/components/settings/cros_settings_names.h" |
| #include "mojo/public/cpp/bindings/struct_traits.h" |
| |
| namespace policy { |
| |
| using DisplayUnitTraits = |
| mojo::StructTraits<crosapi::mojom::DisplayUnitInfo::DataView, |
| crosapi::mojom::DisplayUnitInfoPtr>; |
| |
| struct DisplayResolutionHandler::InternalDisplaySettings { |
| int scale_percentage = 0; |
| |
| explicit InternalDisplaySettings(int scale_percentage) |
| : scale_percentage(scale_percentage) {} |
| |
| friend bool operator==(const InternalDisplaySettings&, |
| const InternalDisplaySettings&) = default; |
| |
| // Create display config for the internal display using policy settings from |
| // |internal_display_settings_|. |
| crosapi::mojom::DisplayConfigPropertiesPtr ToDisplayConfigProperties() { |
| auto new_config = crosapi::mojom::DisplayConfigProperties::New(); |
| // Converting percentage to factor. |
| new_config->display_zoom_factor = scale_percentage / 100.0; |
| return new_config; |
| } |
| |
| // Get settings for the internal display from |
| // |ash::kDeviceDisplayResolution| setting value. |
| static std::unique_ptr<InternalDisplaySettings> FromPolicySetting( |
| const base::Value::Dict* pref) { |
| const std::optional<int> scale_value = |
| pref->FindInt(ash::kDeviceDisplayResolutionKeyInternalScale); |
| return scale_value ? std::make_unique<InternalDisplaySettings>(*scale_value) |
| : nullptr; |
| } |
| }; |
| |
| struct DisplayResolutionHandler::ExternalDisplaySettings { |
| bool use_native = false; |
| int width = 0; |
| int height = 0; |
| std::optional<int> scale_percentage = std::nullopt; |
| |
| bool operator==(const ExternalDisplaySettings& rhs) const { |
| return use_native == rhs.use_native && width == rhs.width && |
| height == rhs.height && scale_percentage == rhs.scale_percentage; |
| } |
| |
| bool operator!=(const ExternalDisplaySettings& rhs) const { |
| return !(*this == rhs); |
| } |
| |
| // Check if either |use_native| flag is set and mode is native or the mode |
| // has required resolution. |
| bool IsSuitableDisplayMode(const crosapi::mojom::DisplayModePtr& mode) { |
| return (use_native && mode->is_native) || |
| (!use_native && width == mode->size.width() && |
| height == mode->size.height()); |
| } |
| |
| // Create display config for the external display using policy settings from |
| // |external_display_settings_|. |
| crosapi::mojom::DisplayConfigPropertiesPtr ToDisplayConfigProperties( |
| const std::vector<crosapi::mojom::DisplayModePtr>& display_modes) { |
| bool found_suitable_mode = false; |
| auto new_config = crosapi::mojom::DisplayConfigProperties::New(); |
| for (const crosapi::mojom::DisplayModePtr& mode : display_modes) { |
| // Check if the current display mode has required resolution and its |
| // refresh rate is higher than refresh rate of the already found mode. |
| if (IsSuitableDisplayMode(mode) && |
| (!found_suitable_mode || |
| mode->refresh_rate > new_config->display_mode->refresh_rate)) { |
| new_config->display_mode = mode->Clone(); |
| found_suitable_mode = true; |
| } |
| } |
| // If we couldn't find the required mode and and scale percentage doesn't |
| // need to be changed, we have nothing to do. |
| if (!found_suitable_mode && !scale_percentage) { |
| return crosapi::mojom::DisplayConfigPropertiesPtr(); |
| } |
| |
| if (scale_percentage) { |
| // Converting percentage to the factor. |
| new_config->display_zoom_factor = *scale_percentage / 100.0; |
| } |
| |
| return new_config; |
| } |
| |
| // Get settings for the external displays from |
| // |ash::kDeviceDisplayResolution| setting value; |
| static std::unique_ptr<ExternalDisplaySettings> FromPolicySetting( |
| const base::Value::Dict* pref) { |
| auto result = std::make_unique<ExternalDisplaySettings>(); |
| |
| // Scale can be used for both native and non-native modes |
| result->scale_percentage = |
| pref->FindInt(ash::kDeviceDisplayResolutionKeyExternalScale); |
| |
| const std::optional<bool> use_native_value = |
| pref->FindBool(ash::kDeviceDisplayResolutionKeyExternalUseNative); |
| if (use_native_value && *use_native_value) { |
| result->use_native = true; |
| return result; |
| } |
| |
| const std::optional<int> width_value = |
| pref->FindInt(ash::kDeviceDisplayResolutionKeyExternalWidth); |
| const std::optional<int> height_value = |
| pref->FindInt(ash::kDeviceDisplayResolutionKeyExternalHeight); |
| if (width_value && height_value) { |
| result->width = *width_value; |
| result->height = *height_value; |
| return result; |
| } |
| |
| return nullptr; |
| } |
| }; |
| |
| DisplayResolutionHandler::DisplayResolutionHandler() = default; |
| |
| DisplayResolutionHandler::~DisplayResolutionHandler() = default; |
| |
| const char* DisplayResolutionHandler::SettingName() { |
| return ash::kDeviceDisplayResolution; |
| } |
| |
| // Reads |ash::kDeviceDisplayResolution| from CrosSettings and stores |
| // the settings in |recommended_|, |external_display_settings_| and |
| // |internal_display_settings_|. Also updates |policy_enabled_| flag. |
| void DisplayResolutionHandler::OnSettingUpdate() { |
| policy_enabled_ = false; |
| const base::Value::Dict* resolution_pref = nullptr; |
| ash::CrosSettings::Get()->GetDictionary(ash::kDeviceDisplayResolution, |
| &resolution_pref); |
| if (!resolution_pref) |
| return; |
| |
| std::unique_ptr<ExternalDisplaySettings> new_external_config = |
| ExternalDisplaySettings::FromPolicySetting(resolution_pref); |
| std::unique_ptr<InternalDisplaySettings> new_internal_config = |
| InternalDisplaySettings::FromPolicySetting(resolution_pref); |
| |
| bool new_recommended = false; |
| policy_enabled_ = new_external_config || new_internal_config; |
| const std::optional<bool> recommended_value = |
| resolution_pref->FindBool(ash::kDeviceDisplayResolutionKeyRecommended); |
| |
| if (recommended_value) |
| new_recommended = *recommended_value; |
| |
| // We should reset locally stored settings and clear list of already updated |
| // displays if any of the policy values were updated. |
| bool should_reset_settings = false; |
| should_reset_settings |= |
| bool{new_external_config} != bool{external_display_settings_}; |
| should_reset_settings |= new_external_config && external_display_settings_ && |
| *new_external_config != *external_display_settings_; |
| should_reset_settings |= |
| bool{new_internal_config} != bool{internal_display_settings_}; |
| should_reset_settings |= new_internal_config && internal_display_settings_ && |
| *new_internal_config != *internal_display_settings_; |
| should_reset_settings |= recommended_ != new_recommended; |
| |
| if (!should_reset_settings) |
| return; |
| |
| resized_display_ids_.clear(); |
| external_display_settings_ = std::move(new_external_config); |
| internal_display_settings_ = std::move(new_internal_config); |
| recommended_ = new_recommended; |
| } |
| |
| // Applies settings received with |OnSettingUpdate| to each supported display |
| // from |info_list| if |policy_enabled_| is true. |
| void DisplayResolutionHandler::ApplyChanges( |
| crosapi::mojom::CrosDisplayConfigController* cros_display_config, |
| const std::vector<crosapi::mojom::DisplayUnitInfoPtr>& info_list) { |
| if (!policy_enabled_) |
| return; |
| for (const crosapi::mojom::DisplayUnitInfoPtr& display_unit_info : |
| info_list) { |
| std::string display_id = display_unit_info->id; |
| // If policy value is marked as "recommended" we need to change the |
| // resolution just once for each display. So we're just skipping the display |
| // if it was resized since last settings update. |
| if (recommended_ && |
| resized_display_ids_.find(display_id) != resized_display_ids_.end()) { |
| continue; |
| } |
| |
| crosapi::mojom::DisplayConfigPropertiesPtr new_config; |
| if (display_unit_info->is_internal && internal_display_settings_) { |
| new_config = internal_display_settings_->ToDisplayConfigProperties(); |
| } else if (!display_unit_info->is_internal && external_display_settings_) { |
| new_config = external_display_settings_->ToDisplayConfigProperties( |
| DisplayUnitTraits::available_display_modes(display_unit_info)); |
| } |
| |
| if (!new_config) |
| continue; |
| |
| resized_display_ids_.insert(display_id); |
| cros_display_config->SetDisplayProperties( |
| display_unit_info->id, std::move(new_config), |
| crosapi::mojom::DisplayConfigSource::kPolicy, |
| base::BindOnce([](crosapi::mojom::DisplayConfigResult result) { |
| if (result == crosapi::mojom::DisplayConfigResult::kSuccess) { |
| VLOG(1) << "Successfully changed display mode."; |
| } else { |
| LOG(ERROR) << "Couldn't change display mode. Error code: " |
| << result; |
| } |
| })); |
| } |
| } |
| |
| } // namespace policy |