| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/display/display_prefs.h" |
| |
| #include <stddef.h> |
| |
| #include <string> |
| #include <utility> |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "base/check_is_test.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| #include "base/values.h" |
| #include "components/metrics/structured/structured_events.h" |
| #include "components/metrics/structured/structured_metrics_client.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "ui/display/display_features.h" |
| #include "ui/display/manager/display_layout_store.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/manager/json_converter.h" |
| #include "ui/display/manager/util/display_manager_util.h" |
| #include "ui/display/screen.h" |
| #include "ui/display/types/display_constants.h" |
| #include "ui/display/util/display_util.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "url/url_canon.h" |
| #include "url/url_util.h" |
| |
| using chromeos::DisplayPowerState; |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr char kInsetsTopKey[] = "insets_top"; |
| constexpr char kInsetsLeftKey[] = "insets_left"; |
| constexpr char kInsetsBottomKey[] = "insets_bottom"; |
| constexpr char kInsetsRightKey[] = "insets_right"; |
| |
| constexpr char kTouchCalibrationWidth[] = "touch_calibration_width"; |
| constexpr char kTouchCalibrationHeight[] = "touch_calibration_height"; |
| constexpr char kTouchCalibrationPointPairs[] = "touch_calibration_point_pairs"; |
| |
| constexpr char kTouchAssociationTimestamp[] = "touch_association_timestamp"; |
| constexpr char kTouchAssociationCalibrationData[] = |
| "touch_association_calibration_data"; |
| |
| constexpr char kTouchDeviceIdentifier[] = "touch_device_identifer"; |
| constexpr char kPortAssociationDisplayId[] = "port_association_display_id"; |
| |
| constexpr char kMirroringSourceId[] = "mirroring_source_id"; |
| constexpr char kMirroringDestinationIds[] = "mirroring_destination_ids"; |
| |
| constexpr char kDisplayZoom[] = "display_zoom_factor"; |
| constexpr char kDisplayZoomMap[] = "display_zoom_factor_map"; |
| |
| constexpr char kDisplayPowerAllOn[] = "all_on"; |
| constexpr char kDisplayPowerInternalOffExternalOn[] = |
| "internal_off_external_on"; |
| constexpr char kDisplayPowerInternalOnExternalOff[] = |
| "internal_on_external_off"; |
| |
| constexpr char kVariableRefreshRateState[] = "vrr_state"; |
| constexpr char kVsyncRateMin[] = "vsync_rate_min"; |
| |
| constexpr double kDefaultDisplayZoomValue = 1.0; |
| |
| // This kind of boilerplates should be done by base::JSONValueConverter but it |
| // doesn't support classes like gfx::Insets for now. |
| // TODO(mukai): fix base::JSONValueConverter and use it here. |
| bool ValueToInsets(const base::Value::Dict& dict, gfx::Insets* insets) { |
| DCHECK(insets); |
| |
| std::optional<int> top = dict.FindInt(kInsetsTopKey); |
| std::optional<int> left = dict.FindInt(kInsetsLeftKey); |
| std::optional<int> bottom = dict.FindInt(kInsetsBottomKey); |
| std::optional<int> right = dict.FindInt(kInsetsRightKey); |
| if (top && left && bottom && right) { |
| *insets = gfx::Insets::TLBR(*top, *left, *bottom, *right); |
| return true; |
| } |
| return false; |
| } |
| |
| void InsetsToValue(const gfx::Insets& insets, base::Value::Dict& dict) { |
| dict.Set(kInsetsTopKey, insets.top()); |
| dict.Set(kInsetsLeftKey, insets.left()); |
| dict.Set(kInsetsBottomKey, insets.bottom()); |
| dict.Set(kInsetsRightKey, insets.right()); |
| } |
| |
| // Unmarshalls the string containing CalibrationPointPairQuad and populates |
| // |point_pair_quad| with the unmarshalled data. |
| bool ParseTouchCalibrationStringValue( |
| const std::string& str, |
| display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad) { |
| DCHECK(point_pair_quad); |
| int x = 0, y = 0; |
| std::vector<std::string> parts = base::SplitString( |
| str, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| size_t total = point_pair_quad->size(); |
| gfx::Point display_point, touch_point; |
| for (std::size_t row = 0; row < total; row++) { |
| if (!base::StringToInt(parts[row * total], &x) || |
| !base::StringToInt(parts[row * total + 1], &y)) { |
| return false; |
| } |
| display_point.SetPoint(x, y); |
| |
| if (!base::StringToInt(parts[row * total + 2], &x) || |
| !base::StringToInt(parts[row * total + 3], &y)) { |
| return false; |
| } |
| touch_point.SetPoint(x, y); |
| |
| (*point_pair_quad)[row] = std::make_pair(display_point, touch_point); |
| } |
| return true; |
| } |
| |
| // Retrieves touch calibration associated data from the dictionary and stores |
| // it in an instance of TouchCalibrationData struct. |
| bool ValueToTouchData(const base::Value::Dict& dict, |
| display::TouchCalibrationData* touch_calibration_data) { |
| display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad = |
| &(touch_calibration_data->point_pairs); |
| |
| const std::string* str = dict.FindString(kTouchCalibrationPointPairs); |
| if (!str) { |
| return false; |
| } |
| |
| if (!ParseTouchCalibrationStringValue(*str, point_pair_quad)) { |
| return false; |
| } |
| |
| std::optional<int> width = dict.FindInt(kTouchCalibrationWidth); |
| std::optional<int> height = dict.FindInt(kTouchCalibrationHeight); |
| if (!width || !height) { |
| return false; |
| } |
| touch_calibration_data->bounds = gfx::Size(*width, *height); |
| return true; |
| } |
| |
| // Stores the touch calibration data into the dictionary. |
| void TouchDataToValue( |
| const display::TouchCalibrationData& touch_calibration_data, |
| base::Value::Dict& dict) { |
| std::string str; |
| for (std::size_t row = 0; row < touch_calibration_data.point_pairs.size(); |
| row++) { |
| str += base::NumberToString( |
| touch_calibration_data.point_pairs[row].first.x()) + |
| " "; |
| str += base::NumberToString( |
| touch_calibration_data.point_pairs[row].first.y()) + |
| " "; |
| str += base::NumberToString( |
| touch_calibration_data.point_pairs[row].second.x()) + |
| " "; |
| str += base::NumberToString( |
| touch_calibration_data.point_pairs[row].second.y()); |
| if (row != touch_calibration_data.point_pairs.size() - 1) { |
| str += " "; |
| } |
| } |
| dict.Set(kTouchCalibrationPointPairs, str); |
| dict.Set(kTouchCalibrationWidth, touch_calibration_data.bounds.width()); |
| dict.Set(kTouchCalibrationHeight, touch_calibration_data.bounds.height()); |
| } |
| |
| display::DisplayManager* GetDisplayManager() { |
| return Shell::Get()->display_manager(); |
| } |
| |
| // Returns true if the current user can write display preferences to |
| // Local State. |
| bool UserCanSaveDisplayPreference() { |
| SessionControllerImpl* controller = Shell::Get()->session_controller(); |
| auto user_type = controller->GetUserType(); |
| if (!user_type) { |
| return false; |
| } |
| |
| return *user_type == user_manager::UserType::kRegular || |
| *user_type == user_manager::UserType::kChild || |
| *user_type == user_manager::UserType::kKioskChromeApp || |
| (*user_type == user_manager::UserType::kPublicAccount && |
| Shell::Get()->local_state()->GetBoolean( |
| prefs::kAllowMGSToStoreDisplayProperties)); |
| } |
| |
| void LoadDisplayLayouts(PrefService* local_state) { |
| display::DisplayLayoutStore* layout_store = |
| GetDisplayManager()->layout_store(); |
| |
| for (const auto it : local_state->GetDict(prefs::kSecondaryDisplays)) { |
| std::unique_ptr<display::DisplayLayout> layout(new display::DisplayLayout); |
| if (!display::JsonToDisplayLayout(it.second, layout.get())) { |
| LOG(WARNING) << "Invalid preference value for " << it.first; |
| continue; |
| } |
| |
| if (base::Contains(it.first, ",")) { |
| std::vector<std::string> ids_str = base::SplitString( |
| it.first, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| std::vector<int64_t> ids; |
| for (std::string id_str : ids_str) { |
| int64_t id; |
| if (!base::StringToInt64(id_str, &id)) { |
| continue; |
| } |
| ids.push_back(id); |
| } |
| display::DisplayIdList list = display::GenerateDisplayIdList(ids); |
| layout_store->RegisterLayoutForDisplayIdList(list, std::move(layout)); |
| } |
| } |
| } |
| |
| void LoadDisplayProperties(PrefService* local_state) { |
| for (const auto it : local_state->GetDict(prefs::kDisplayProperties)) { |
| const base::Value::Dict* dict_value = it.second.GetIfDict(); |
| if (!dict_value) { |
| continue; |
| } |
| int64_t id = display::kInvalidDisplayId; |
| if (!base::StringToInt64(it.first, &id) || |
| id == display::kInvalidDisplayId) { |
| continue; |
| } |
| const gfx::Insets* insets_to_set = nullptr; |
| |
| display::Display::Rotation rotation = display::Display::ROTATE_0; |
| if (std::optional<int> rotation_value = dict_value->FindInt("rotation")) { |
| rotation = static_cast<display::Display::Rotation>(*rotation_value); |
| } |
| |
| int width = dict_value->FindInt("width").value_or(0); |
| int height = dict_value->FindInt("height").value_or(0); |
| gfx::Size resolution_in_pixels(width, height); |
| |
| float device_scale_factor = 1.0; |
| if (std::optional<int> dsf_value = |
| dict_value->FindInt("device-scale-factor")) { |
| device_scale_factor = static_cast<float>(*dsf_value) / 1000.0f; |
| } |
| |
| // Default refresh rate is 60 Hz, until |
| // DisplayManager::OnNativeDisplaysChanged() updates us with the actual |
| // display info. |
| double refresh_rate = 60.0; |
| bool is_interlaced = false; |
| if (display::features::IsListAllDisplayModesEnabled()) { |
| refresh_rate = |
| dict_value->FindDouble("refresh-rate").value_or(refresh_rate); |
| std::optional<bool> is_interlaced_opt = |
| dict_value->FindBool("interlaced"); |
| is_interlaced = is_interlaced_opt.value_or(false); |
| } |
| |
| gfx::Insets insets; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kOverscanInsetsOverride)) { |
| std::string value = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| switches::kOverscanInsetsOverride); |
| auto values = base::SplitString(value, ",", |
| base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_ALL); |
| int top, left, bottom, right; |
| if (values.size() == 4 && base::StringToInt(values[0], &top) && |
| base::StringToInt(values[1], &left) && |
| base::StringToInt(values[2], &bottom) && |
| base::StringToInt(values[3], &right)) { |
| insets = gfx::Insets::TLBR(top, left, bottom, right); |
| insets_to_set = &insets; |
| } else { |
| LOG(ERROR) << "Failed to parse overscan insets:" << value; |
| } |
| } else if (ValueToInsets(*dict_value, &insets) && !insets.IsEmpty()) { |
| insets_to_set = &insets; |
| } |
| |
| display::DisplaySizeToZoomFactorMap display_zoom_map; |
| if (const auto* display_zoom_dict = dict_value->FindDict(kDisplayZoomMap)) { |
| for (const auto iter : *display_zoom_dict) { |
| display_zoom_map[iter.first] = |
| iter.second.GetIfDouble().value_or(kDefaultDisplayZoomValue); |
| } |
| } |
| |
| display::VariableRefreshRateState variable_refresh_rate_state = |
| display::VariableRefreshRateState::kVrrNotCapable; |
| if (std::optional<int> vrr_state_value = |
| dict_value->FindInt(kVariableRefreshRateState)) { |
| variable_refresh_rate_state = |
| static_cast<display::VariableRefreshRateState>(*vrr_state_value); |
| } |
| std::optional<float> vsync_rate_min = dict_value->FindDouble(kVsyncRateMin); |
| |
| const double display_zoom = |
| dict_value->FindDouble(kDisplayZoom).value_or(kDefaultDisplayZoomValue); |
| |
| GetDisplayManager()->RegisterDisplayProperty( |
| id, rotation, insets_to_set, resolution_in_pixels, device_scale_factor, |
| display_zoom, display_zoom_map, refresh_rate, is_interlaced, |
| variable_refresh_rate_state, vsync_rate_min); |
| } |
| } |
| |
| void LoadDisplayRotationState(PrefService* local_state) { |
| const base::Value::Dict& properties = |
| local_state->GetDict(prefs::kDisplayRotationLock); |
| const std::optional<bool> rotation_lock = properties.FindBool("lock"); |
| if (!rotation_lock) { |
| return; |
| } |
| |
| const std::optional<int> rotation = properties.FindInt("orientation"); |
| if (!rotation) { |
| return; |
| } |
| |
| GetDisplayManager()->RegisterDisplayRotationProperties( |
| *rotation_lock, static_cast<display::Display::Rotation>(*rotation)); |
| } |
| |
| void LoadDisplayTouchAssociations(PrefService* local_state) { |
| display::TouchDeviceManager::TouchAssociationMap touch_associations; |
| for (const auto item : |
| local_state->GetDict(prefs::kDisplayTouchAssociations)) { |
| uint32_t identifier_raw; |
| if (!base::StringToUint(item.first, &identifier_raw)) { |
| continue; |
| } |
| display::TouchDeviceIdentifier identifier(identifier_raw); |
| touch_associations.emplace( |
| identifier, display::TouchDeviceManager::AssociationInfoMap()); |
| if (!item.second.is_dict()) { |
| continue; |
| } |
| for (const auto association_info_item : item.second.GetDict()) { |
| display::TouchDeviceManager::TouchAssociationInfo info; |
| int64_t display_id; |
| if (!base::StringToInt64(association_info_item.first, &display_id)) { |
| continue; |
| } |
| std::optional<double> value = |
| association_info_item.second.GetDict().FindDouble( |
| kTouchAssociationTimestamp); |
| if (!value) { |
| continue; |
| } |
| info.timestamp = base::Time().FromSecondsSinceUnixEpoch(*value); |
| |
| const base::Value::Dict* calibration_data_dict = |
| association_info_item.second.GetDict().FindDict( |
| kTouchAssociationCalibrationData); |
| if (!calibration_data_dict) { |
| continue; |
| } |
| ValueToTouchData(*calibration_data_dict, &info.calibration_data); |
| touch_associations.at(identifier).emplace(display_id, info); |
| } |
| } |
| |
| // Retrieve all the legacy format identifiers. This should be removed after |
| // a couple of milestones when everything is stable. |
| const display::TouchDeviceIdentifier& fallback_identifier = |
| display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier(); |
| for (const auto it : local_state->GetDict(prefs::kDisplayProperties)) { |
| const base::Value::Dict* dict_value = it.second.GetIfDict(); |
| if (!dict_value) { |
| continue; |
| } |
| int64_t id = display::kInvalidDisplayId; |
| if (!base::StringToInt64(it.first, &id) || |
| id == display::kInvalidDisplayId) { |
| continue; |
| } |
| display::TouchCalibrationData calibration_data; |
| display::TouchCalibrationData* calibration_data_to_set = nullptr; |
| if (ValueToTouchData(*dict_value, &calibration_data)) { |
| calibration_data_to_set = &calibration_data; |
| } |
| |
| if (calibration_data_to_set) { |
| if (!base::Contains(touch_associations, fallback_identifier)) { |
| touch_associations.emplace( |
| fallback_identifier, |
| display::TouchDeviceManager::AssociationInfoMap()); |
| } |
| display::TouchDeviceManager::TouchAssociationInfo info; |
| info.calibration_data = *calibration_data_to_set; |
| touch_associations.at(fallback_identifier).emplace(id, info); |
| } |
| } |
| |
| // Retrieve port association information. |
| display::TouchDeviceManager::PortAssociationMap port_associations; |
| for (const auto item : |
| local_state->GetDict(prefs::kDisplayTouchPortAssociations)) { |
| // Retrieve the secondary id that identifies the port. |
| uint32_t secondary_id_raw; |
| if (!base::StringToUint(item.first, &secondary_id_raw)) { |
| continue; |
| } |
| |
| if (!item.second.is_dict()) { |
| continue; |
| } |
| |
| // Retrieve the touch device identifier that identifies the touch device. |
| const std::string* value = |
| item.second.GetDict().FindString(kTouchDeviceIdentifier); |
| if (!value) { |
| continue; |
| } |
| uint32_t identifier_raw; |
| if (!base::StringToUint(*value, &identifier_raw)) { |
| continue; |
| } |
| |
| // Retrieve the display that the touch device identified by |identifier_raw| |
| // was associated with. |
| value = item.second.GetDict().FindString(kPortAssociationDisplayId); |
| if (!value) { |
| continue; |
| } |
| int64_t display_id; |
| if (!base::StringToInt64(*value, &display_id)) { |
| continue; |
| } |
| |
| port_associations.emplace( |
| std::piecewise_construct, |
| std::forward_as_tuple(identifier_raw, secondary_id_raw), |
| std::forward_as_tuple(display_id)); |
| } |
| |
| GetDisplayManager()->touch_device_manager()->RegisterTouchAssociations( |
| touch_associations, port_associations); |
| } |
| |
| // Loads mirror info for each external display, the info will later be used to |
| // restore mirror mode. |
| void LoadExternalDisplayMirrorInfo(PrefService* local_state) { |
| const base::Value::List& pref_data = |
| local_state->GetList(prefs::kExternalDisplayMirrorInfo); |
| std::set<int64_t> external_display_mirror_info; |
| for (const auto& it : pref_data) { |
| const std::string* display_id_str = it.GetIfString(); |
| if (!display_id_str) { |
| continue; |
| } |
| |
| int64_t display_id; |
| if (!base::StringToInt64(*display_id_str, &display_id)) { |
| continue; |
| } |
| |
| external_display_mirror_info.emplace(display_id); |
| } |
| GetDisplayManager()->set_external_display_mirror_info( |
| external_display_mirror_info); |
| } |
| |
| // Loads mixed mirror mode parameters which will later be used to restore mixed |
| // mirror mode. Return false if the parameters fail to be loaded. |
| void LoadDisplayMixedMirrorModeParams(PrefService* local_state) { |
| const base::Value::Dict& pref_data = |
| local_state->GetDict(prefs::kDisplayMixedMirrorModeParams); |
| |
| // This function is called once for system (re)start, so the parameters should |
| // be empty. |
| DCHECK(!GetDisplayManager()->mixed_mirror_mode_params()); |
| |
| auto* mirroring_source_id_string = pref_data.FindString(kMirroringSourceId); |
| if (!mirroring_source_id_string) { |
| return; |
| } |
| |
| int64_t mirroring_source_id; |
| if (!base::StringToInt64(*mirroring_source_id_string, &mirroring_source_id)) { |
| return; |
| } |
| |
| auto* mirroring_destination_ids_list = |
| pref_data.FindList(kMirroringDestinationIds); |
| if (!mirroring_destination_ids_list) { |
| return; |
| } |
| |
| display::DisplayIdList mirroring_destination_ids; |
| for (const auto& entry : *mirroring_destination_ids_list) { |
| int64_t id; |
| if (!base::StringToInt64(entry.GetString(), &id)) { |
| return; |
| } |
| mirroring_destination_ids.emplace_back(id); |
| } |
| |
| GetDisplayManager()->set_mixed_mirror_mode_params( |
| std::optional<display::MixedMirrorModeParams>( |
| std::in_place, mirroring_source_id, mirroring_destination_ids)); |
| } |
| |
| void StoreDisplayLayoutPref(PrefService* pref_service, |
| const display::DisplayIdList& list, |
| const display::DisplayLayout& display_layout) { |
| DCHECK(display::DisplayLayout::Validate(list, display_layout)); |
| std::string name = display::DisplayIdListToString(list); |
| |
| ScopedDictPrefUpdate update(pref_service, prefs::kSecondaryDisplays); |
| base::Value::Dict* layout_dict = update->EnsureDict(name); |
| // This call modifies `layout_dict` in place. |
| display::DisplayLayoutToJson(display_layout, *layout_dict); |
| } |
| |
| void StoreCurrentDisplayLayoutPrefs(PrefService* pref_service) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| if (!UserCanSaveDisplayPreference() || |
| display_manager->num_connected_displays() < 2) { |
| return; |
| } |
| |
| display::DisplayIdList list = display_manager->GetConnectedDisplayIdList(); |
| const display::DisplayLayout& display_layout = |
| display_manager->layout_store()->GetRegisteredDisplayLayout(list); |
| |
| if (!display::DisplayLayout::Validate(list, display_layout)) { |
| // We should never apply an invalid layout, if we do, it persists and the |
| // user has no way of fixing it except by deleting the local state. |
| LOG(ERROR) << "Attempting to store an invalid display layout in the local" |
| << " state. Skipping."; |
| return; |
| } |
| |
| StoreDisplayLayoutPref(pref_service, list, display_layout); |
| } |
| |
| void StoreCurrentDisplayProperties(PrefService* pref_service) { |
| display::DisplayManager* display_manager = GetDisplayManager(); |
| |
| ScopedDictPrefUpdate update(pref_service, prefs::kDisplayProperties); |
| base::Value::Dict& pref_data = update.Get(); |
| |
| // Pre-process data related to legacy touch calibration to opitmize lookup. |
| const display::TouchDeviceIdentifier& fallback_identifier = |
| display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier(); |
| display::TouchDeviceManager::AssociationInfoMap legacy_data_map; |
| if (base::Contains( |
| display_manager->touch_device_manager()->touch_associations(), |
| fallback_identifier)) { |
| legacy_data_map = |
| display_manager->touch_device_manager()->touch_associations().at( |
| fallback_identifier); |
| } |
| |
| size_t num = display_manager->GetNumDisplays(); |
| for (size_t i = 0; i < num; ++i) { |
| const display::Display& display = display_manager->GetDisplayAt(i); |
| int64_t id = display.id(); |
| display::ManagedDisplayInfo info = display_manager->GetDisplayInfo(id); |
| |
| base::Value::Dict property_value; |
| // Don't save the display preference in unified mode because its |
| // size and modes can change depending on the combination of displays. |
| if (display_manager->IsInUnifiedMode()) { |
| continue; |
| } |
| // Don't save rotation when in tablet mode, so that if the device is |
| // rebooted into clamshell mode, it won't have an unexpected rotation. |
| // https://crbug.com/733092. |
| // But we should keep any original value so that it can be restored when |
| // exiting tablet mode. |
| if (display::Screen::Get()->InTabletMode()) { |
| const base::Value::Dict* original_property = |
| pref_data.FindDict(base::NumberToString(id)); |
| if (original_property) { |
| std::optional<int> original_rotation = |
| original_property->FindInt("rotation"); |
| if (original_rotation) { |
| property_value.Set("rotation", *original_rotation); |
| } |
| } |
| } else { |
| property_value.Set("rotation", |
| static_cast<int>(info.GetRotation( |
| display::Display::RotationSource::USER))); |
| } |
| |
| display::ManagedDisplayMode mode; |
| if (!display.IsInternal() && |
| display_manager->GetSelectedModeForDisplayId(id, &mode) && |
| !mode.native()) { |
| property_value.Set("width", mode.size().width()); |
| property_value.Set("height", mode.size().height()); |
| property_value.Set("device-scale-factor", |
| static_cast<int>(mode.device_scale_factor() * 1000)); |
| |
| if (display::features::IsListAllDisplayModesEnabled()) { |
| property_value.Set("interlaced", mode.is_interlaced()); |
| property_value.Set("refresh-rate", mode.refresh_rate()); |
| } |
| } |
| if (!info.overscan_insets_in_dip().IsEmpty()) { |
| InsetsToValue(info.overscan_insets_in_dip(), property_value); |
| } |
| |
| // Store the legacy format touch calibration data. This can be removed after |
| // a couple of milestones when every device has migrated to the new format. |
| if (legacy_data_map.size() && base::Contains(legacy_data_map, id)) { |
| TouchDataToValue(legacy_data_map.at(id).calibration_data, property_value); |
| } |
| |
| property_value.Set(kDisplayZoom, info.zoom_factor()); |
| |
| base::Value::Dict display_zoom_dict; |
| for (const auto& it : info.zoom_factor_map()) { |
| display_zoom_dict.Set(it.first, it.second); |
| } |
| property_value.Set(kDisplayZoomMap, std::move(display_zoom_dict)); |
| |
| property_value.Set(kVariableRefreshRateState, |
| static_cast<int>(info.variable_refresh_rate_state())); |
| if (const std::optional<float>& vsync_rate_min = info.vsync_rate_min()) { |
| property_value.Set(kVsyncRateMin, vsync_rate_min.value()); |
| } |
| |
| pref_data.Set(base::NumberToString(id), std::move(property_value)); |
| } |
| } |
| |
| bool GetDisplayPowerStateFromString(const std::string& state_string, |
| chromeos::DisplayPowerState* power_state) { |
| if (state_string == kDisplayPowerAllOn) { |
| *power_state = chromeos::DISPLAY_POWER_ALL_ON; |
| } else if (state_string == kDisplayPowerInternalOffExternalOn) { |
| *power_state = chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON; |
| } else if (state_string == kDisplayPowerInternalOnExternalOff) { |
| *power_state = chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF; |
| } else { |
| // Don't restore ALL_OFF state. http://crbug.com/318456. |
| return false; |
| } |
| return true; |
| } |
| |
| void StoreDisplayPowerState(PrefService* pref_service, |
| DisplayPowerState power_state) { |
| const char* state_string = nullptr; |
| switch (power_state) { |
| case chromeos::DISPLAY_POWER_ALL_ON: |
| state_string = kDisplayPowerAllOn; |
| break; |
| case chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON: |
| state_string = kDisplayPowerInternalOffExternalOn; |
| break; |
| case chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF: |
| state_string = kDisplayPowerInternalOnExternalOff; |
| break; |
| case chromeos::DISPLAY_POWER_ALL_OFF: |
| // Don't store ALL_OFF state. http://crbug.com/318456. |
| break; |
| } |
| if (state_string) { |
| pref_service->Set(prefs::kDisplayPowerState, base::Value(state_string)); |
| } |
| } |
| |
| void StoreCurrentDisplayPowerState(PrefService* pref_service) { |
| StoreDisplayPowerState( |
| pref_service, |
| Shell::Get()->display_configurator()->GetRequestedPowerState()); |
| } |
| |
| void StoreDisplayRotationPrefs(PrefService* pref_service, |
| display::Display::Rotation rotation, |
| bool rotation_lock) { |
| ScopedDictPrefUpdate update(pref_service, prefs::kDisplayRotationLock); |
| update->Set("lock", rotation_lock); |
| update->Set("orientation", static_cast<int>(rotation)); |
| } |
| |
| void StoreCurrentDisplayRotationLockPrefs(PrefService* pref_service) { |
| if (!display::HasInternalDisplay()) { |
| return; |
| } |
| display::Display::Rotation rotation = |
| Shell::Get()->display_manager()->registered_internal_display_rotation(); |
| |
| bool rotation_lock = Shell::Get() |
| ->display_manager() |
| ->registered_internal_display_rotation_lock(); |
| StoreDisplayRotationPrefs(pref_service, rotation, rotation_lock); |
| } |
| |
| void StoreDisplayTouchAssociations(PrefService* pref_service) { |
| display::TouchDeviceManager* touch_device_manager = |
| GetDisplayManager()->touch_device_manager(); |
| |
| ScopedDictPrefUpdate update(pref_service, prefs::kDisplayTouchAssociations); |
| base::Value::Dict& pref_data = update.Get(); |
| pref_data.clear(); |
| |
| const display::TouchDeviceManager::TouchAssociationMap& touch_associations = |
| touch_device_manager->touch_associations(); |
| |
| for (const auto& association : touch_associations) { |
| base::Value::Dict association_info_map_value; |
| for (const auto& association_info : association.second) { |
| // Iteration for each pair of <Display ID, TouchAssociationInfo>. |
| base::Value::Dict association_info_value; |
| |
| // Parsing each member of TouchAssociationInfo and storing them in |
| // |association_info_value|. |
| |
| // Serialize timestamp. |
| association_info_value.Set( |
| kTouchAssociationTimestamp, |
| association_info.second.timestamp.InSecondsFSinceUnixEpoch()); |
| |
| // Serialize TouchCalibrationData. |
| base::Value::Dict calibration_data_value; |
| TouchDataToValue(association_info.second.calibration_data, |
| calibration_data_value); |
| association_info_value.Set(kTouchAssociationCalibrationData, |
| std::move(calibration_data_value)); |
| |
| // Move the searialzed TouchAssociationInfo stored in |
| // |association_info_value| to |association_info_map_value| against the |
| // display id as key. This is a 1 to 1 mapping of a single entry from |
| // AssociationInfoMap to its serialized form. |
| association_info_map_value.Set( |
| base::NumberToString(association_info.first), |
| std::move(association_info_value)); |
| } |
| if (association_info_map_value.empty()) { |
| continue; |
| } |
| |
| // Move the already serialized entry of AssociationInfoMap from |
| // |association_info_map_value| to |pref_data| against the |
| // TouchDeviceIdentifier as key. This is a 1 to 1 mapping of a single entry |
| // from TouchAssociationMap to its serialized form. |
| pref_data.Set(association.first.ToString(), |
| std::move(association_info_map_value)); |
| } |
| |
| // Store the port mappings. What display a touch device connected to a |
| // particular port is associated with. |
| ScopedDictPrefUpdate update_port(pref_service, |
| prefs::kDisplayTouchPortAssociations); |
| base::Value::Dict& port_pref_data = update_port.Get(); |
| port_pref_data.clear(); |
| |
| const display::TouchDeviceManager::PortAssociationMap& port_associations = |
| touch_device_manager->port_associations(); |
| |
| // For each port identified by the secondary id of TouchDeviceIdentifier, |
| // we store the touch device and the display associated with it. |
| for (const auto& association : port_associations) { |
| base::Value::Dict association_info_value; |
| association_info_value.Set(kTouchDeviceIdentifier, |
| association.first.ToString()); |
| association_info_value.Set(kPortAssociationDisplayId, |
| base::NumberToString(association.second)); |
| |
| port_pref_data.Set(association.first.SecondaryIdToString(), |
| std::move(association_info_value)); |
| } |
| } |
| |
| void ReportToPopularityMetricsAndStore(PrefService* pref_service) { |
| // NOTE: This number must change every time we add/remove/edit any fields to |
| // force the device to resubmit a report with updated fields. |
| constexpr uint64_t kCurrentVersion = 1; |
| |
| auto cached_version = |
| pref_service->GetUint64(prefs::kDisplayPopularityRevNumber); |
| if (cached_version != kCurrentVersion) { |
| pref_service->ClearPref(prefs::kDisplayPopularityUserReportedDisplays); |
| pref_service->SetUint64(prefs::kDisplayPopularityRevNumber, |
| kCurrentVersion); |
| } |
| |
| base::Value::List cached_list = |
| pref_service->GetList(prefs::kDisplayPopularityUserReportedDisplays) |
| .Clone(); |
| for (int64_t id : GetDisplayManager()->GetConnectedDisplayIdList()) { |
| const display::ManagedDisplayInfo& display = |
| GetDisplayManager()->GetDisplayInfo(id); |
| |
| // We don't want to report internal panels. |
| if (display::IsInternalDisplayId(id)) { |
| continue; |
| } |
| |
| std::string display_id = base::NumberToString(display.edid_display_id()); |
| // If we've already reported that display, don't report it again. |
| if (base::Contains(cached_list, display_id)) { |
| continue; |
| } |
| |
| const display::ManagedDisplayInfo::ManagedDisplayModeList& modes = |
| display.display_modes(); |
| CHECK(modes.size()); |
| auto it = std::find_if( |
| modes.begin(), modes.end(), |
| [](const display::ManagedDisplayMode& mode) { return mode.native(); }); |
| const display::ManagedDisplayMode* native_mode = |
| it == modes.end() ? nullptr : &(*it); |
| CHECK(it != modes.end()); |
| |
| int product_id; |
| base::StringToInt(display.product_id(), &product_id); |
| |
| metrics::structured::StructuredMetricsClient::Record(std::move( |
| metrics::structured::events::v2::popular_displays::MonitorInfo() |
| .SetDisplayName(display.name()) |
| .SetManufacturerId(display.manufacturer_id()) |
| .SetProductId(product_id) |
| .SetNativeModeSize(native_mode->size().ToString()) |
| .SetNativeModeRefreshRate(native_mode->refresh_rate()) |
| .SetPhysicalSize(display.physical_size().ToString()) |
| .SetConnectionType( |
| display::DisplayConnectionTypeString(display.connection_type())) |
| .SetIsVrrCapable( |
| display.variable_refresh_rate_state() < |
| display::VariableRefreshRateState::kVrrNotCapable))); |
| |
| cached_list.Append(display_id); |
| } |
| |
| pref_service->SetList(prefs::kDisplayPopularityUserReportedDisplays, |
| std::move(cached_list)); |
| } |
| |
| // Stores mirror info for each external display. |
| void StoreExternalDisplayMirrorInfo(PrefService* pref_service) { |
| ScopedListPrefUpdate update(pref_service, prefs::kExternalDisplayMirrorInfo); |
| base::Value::List& pref_data = update.Get(); |
| pref_data.clear(); |
| const std::set<int64_t>& external_display_mirror_info = |
| GetDisplayManager()->external_display_mirror_info(); |
| for (const auto& id : external_display_mirror_info) { |
| pref_data.Append(base::NumberToString(id)); |
| } |
| } |
| |
| // Stores mixed mirror mode parameters. Clear the preferences if |
| // |mixed_mirror_mode_params| is null. |
| void StoreDisplayMixedMirrorModeParams( |
| PrefService* pref_service, |
| const std::optional<display::MixedMirrorModeParams>& mixed_params) { |
| ScopedDictPrefUpdate update(pref_service, |
| prefs::kDisplayMixedMirrorModeParams); |
| base::Value::Dict& pref_data = update.Get(); |
| pref_data.clear(); |
| |
| if (!mixed_params) { |
| return; |
| } |
| |
| pref_data.Set(kMirroringSourceId, |
| base::NumberToString(mixed_params->source_id)); |
| |
| base::Value::List mirroring_destination_ids_list; |
| for (const auto& id : mixed_params->destination_ids) { |
| mirroring_destination_ids_list.Append(base::NumberToString(id)); |
| } |
| pref_data.Set(kMirroringDestinationIds, |
| std::move(mirroring_destination_ids_list)); |
| } |
| |
| void StoreCurrentDisplayMixedMirrorModeParams(PrefService* pref_service) { |
| StoreDisplayMixedMirrorModeParams( |
| pref_service, GetDisplayManager()->mixed_mirror_mode_params()); |
| } |
| |
| } // namespace |
| |
| // static |
| void DisplayPrefs::RegisterLocalStatePrefs(PrefRegistrySimple* registry) { |
| registry->RegisterDictionaryPref(prefs::kSecondaryDisplays); |
| registry->RegisterDictionaryPref(prefs::kDisplayProperties); |
| registry->RegisterStringPref(prefs::kDisplayPowerState, kDisplayPowerAllOn); |
| registry->RegisterDictionaryPref(prefs::kDisplayRotationLock); |
| registry->RegisterDictionaryPref(prefs::kDisplayTouchAssociations); |
| registry->RegisterDictionaryPref(prefs::kDisplayTouchPortAssociations); |
| registry->RegisterListPref(prefs::kExternalDisplayMirrorInfo); |
| registry->RegisterDictionaryPref(prefs::kDisplayMixedMirrorModeParams); |
| registry->RegisterBooleanPref(prefs::kAllowMGSToStoreDisplayProperties, |
| false); |
| registry->RegisterListPref(prefs::kDisplayPopularityUserReportedDisplays); |
| registry->RegisterUint64Pref(prefs::kDisplayPopularityRevNumber, 0); |
| } |
| |
| DisplayPrefs::DisplayPrefs(PrefService* local_state) |
| : local_state_(local_state) { |
| Shell::Get()->session_controller()->AddObserver(this); |
| |
| // |local_state_| could be null in tests. |
| if (local_state_) { |
| LoadDisplayPreferences(); |
| } |
| } |
| |
| DisplayPrefs::~DisplayPrefs() { |
| Shell::Get()->session_controller()->RemoveObserver(this); |
| } |
| |
| void DisplayPrefs::OnFirstSessionStarted() { |
| if (store_requested_) { |
| MaybeStoreDisplayPrefs(); |
| } |
| } |
| |
| void DisplayPrefs::MaybeStoreDisplayPrefs() { |
| DCHECK(local_state_); |
| |
| // Stores the power state regardless of the login status, because the power |
| // state respects to the current status (close/open) of the lid which can be |
| // changed in any situation. See http://crbug.com/285360 |
| StoreCurrentDisplayPowerState(local_state_); |
| StoreCurrentDisplayRotationLockPrefs(local_state_); |
| |
| // We cannot really decide whether to store display prefs until there is an |
| // active user session. |OnFirstSessionStarted()| should eventually attempt to |
| // do a store in this case. |
| if (!Shell::Get()->session_controller()->GetUserType()) { |
| store_requested_ = true; |
| return; |
| } |
| |
| // There are multiple scenarios where we don't want to save display prefs. |
| // Some user types are not allowed, we don't want to change them while a |
| // display change confirmation dialog is still visible, etc. |
| if (!UserCanSaveDisplayPreference() || |
| !Shell::Get()->ShouldSaveDisplaySettings()) { |
| return; |
| } |
| |
| store_requested_ = false; |
| // Don't save certain display properties when in tablet mode, so if |
| // the device is rebooted in clamshell mode, it won't have an unexpected |
| // mirroring layout. https://crbug.com/733092. |
| if (!display::Screen::Get()->InTabletMode()) { |
| StoreCurrentDisplayLayoutPrefs(local_state_); |
| StoreExternalDisplayMirrorInfo(local_state_); |
| StoreCurrentDisplayMixedMirrorModeParams(local_state_); |
| } |
| StoreCurrentDisplayProperties(local_state_); |
| StoreDisplayTouchAssociations(local_state_); |
| ReportToPopularityMetricsAndStore(local_state_); |
| // The display prefs need to be committed immediately to guarantee they're not |
| // lost, and are restored properly on reboot. https://crbug.com/936884. |
| // This sends a request via mojo to commit the prefs to disk. |
| local_state_->CommitPendingWrite(); |
| } |
| |
| void DisplayPrefs::LoadDisplayPreferences() { |
| LoadDisplayLayouts(local_state_); |
| LoadDisplayProperties(local_state_); |
| LoadExternalDisplayMirrorInfo(local_state_); |
| LoadDisplayMixedMirrorModeParams(local_state_); |
| LoadDisplayRotationState(local_state_); |
| LoadDisplayTouchAssociations(local_state_); |
| |
| // Now that the display prefs have been loaded, request to reconfigure the |
| // displays, but signal the display manager to restore the mirror state of |
| // external displays from the loaded prefs (if any). |
| Shell::Get() |
| ->display_manager() |
| ->set_should_restore_mirror_mode_from_display_prefs(true); |
| Shell::Get()->display_configurator()->OnConfigurationChanged(); |
| |
| // Ensure that we have a reasonable initial display power state if |
| // powerd fails to send us one over D-Bus. Otherwise, we won't restore |
| // displays correctly after retaking control when changing virtual terminals. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kFirstExecAfterBoot)) { |
| Shell::Get()->display_configurator()->InitializeDisplayPowerState(); |
| return; |
| } |
| |
| // Restore DisplayPowerState: |
| const std::string value = |
| local_state_->GetValue(prefs::kDisplayPowerState).GetString(); |
| chromeos::DisplayPowerState power_state; |
| if (GetDisplayPowerStateFromString(value, &power_state)) { |
| Shell::Get()->display_configurator()->SetInitialDisplayPower(power_state); |
| } |
| } |
| |
| void DisplayPrefs::StoreDisplayRotationPrefsForTest( |
| display::Display::Rotation rotation, |
| bool rotation_lock) { |
| StoreDisplayRotationPrefs(local_state_, rotation, rotation_lock); |
| } |
| |
| void DisplayPrefs::StoreDisplayLayoutPrefForTest( |
| const display::DisplayIdList& list, |
| const display::DisplayLayout& layout) { |
| StoreDisplayLayoutPref(local_state_, list, layout); |
| } |
| |
| void DisplayPrefs::StoreDisplayPowerStateForTest( |
| DisplayPowerState power_state) { |
| StoreDisplayPowerState(local_state_, power_state); |
| } |
| |
| void DisplayPrefs::LoadTouchAssociationPreferenceForTest() { |
| LoadDisplayTouchAssociations(local_state_); |
| } |
| |
| void DisplayPrefs::LoadDisplayPrefsForTest() { |
| CHECK_IS_TEST(); |
| LoadDisplayPreferences(); |
| } |
| |
| void DisplayPrefs::StoreLegacyTouchDataForTest( |
| int64_t display_id, |
| const display::TouchCalibrationData& data) { |
| ScopedDictPrefUpdate update(local_state_, prefs::kDisplayProperties); |
| base::Value::Dict property_value; |
| TouchDataToValue(data, property_value); |
| update->Set(base::NumberToString(display_id), std::move(property_value)); |
| } |
| |
| bool DisplayPrefs::ParseTouchCalibrationStringForTest( |
| const std::string& str, |
| display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad) { |
| return ParseTouchCalibrationStringValue(str, point_pair_quad); |
| } |
| |
| void DisplayPrefs::StoreDisplayMixedMirrorModeParamsForTest( |
| const std::optional<display::MixedMirrorModeParams>& mixed_params) { |
| StoreDisplayMixedMirrorModeParams(local_state_, mixed_params); |
| } |
| |
| bool DisplayPrefs::IsDisplayAvailableInPref(int64_t display_id) const { |
| for (const auto it : local_state_->GetDict(prefs::kDisplayProperties)) { |
| int64_t id = display::kInvalidDisplayId; |
| if (base::StringToInt64(it.first, &id) && id == display_id) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| } // namespace ash |