| // Copyright 2017 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/win/color_profile_reader.h" |
| |
| #include <windows.h> |
| |
| #include <stddef.h> |
| |
| #include "base/compiler_specific.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_thread_priority.h" |
| #include "ui/display/win/display_info.h" |
| #include "ui/gfx/icc_profile.h" |
| |
| namespace display { |
| namespace win { |
| namespace { |
| |
| BOOL CALLBACK EnumMonitorForProfilePathCallback(HMONITOR monitor, |
| HDC input_hdc, |
| LPRECT rect, |
| LPARAM data) { |
| std::wstring device_name; |
| MONITORINFOEX monitor_info; |
| UNSAFE_TODO(::ZeroMemory(&monitor_info, sizeof(monitor_info))); |
| monitor_info.cbSize = sizeof(monitor_info); |
| ::GetMonitorInfo(monitor, &monitor_info); |
| device_name = std::wstring(monitor_info.szDevice); |
| |
| std::wstring profile_path; |
| HDC hdc = ::CreateDC(monitor_info.szDevice, NULL, NULL, NULL); |
| if (hdc) { |
| DWORD path_length = MAX_PATH; |
| WCHAR path[MAX_PATH + 1]; |
| BOOL result = ::GetICMProfile(hdc, &path_length, path); |
| ::DeleteDC(hdc); |
| if (result) |
| profile_path = std::wstring(path); |
| } |
| int64_t display_id = |
| internal::DisplayInfo::DisplayIdFromMonitorInfo(monitor_info); |
| std::map<int64_t, std::wstring>* display_id_to_path_map = |
| reinterpret_cast<std::map<int64_t, std::wstring>*>(data); |
| (*display_id_to_path_map)[display_id] = profile_path; |
| return TRUE; |
| } |
| |
| } // namespace |
| |
| ColorProfileReader::ColorProfileReader(Client* client) : client_(client) {} |
| |
| ColorProfileReader::~ColorProfileReader() {} |
| |
| void ColorProfileReader::UpdateIfNeeded() { |
| // There is a potential race condition wherein the result from |
| // EnumDisplayMonitors is already stale by the time that we get |
| // back to BuildDisplayIdToPathMapCompleted. To fix this we would |
| // need to record the fact that we early-out-ed because of |
| // update_in_flight_ was true, and then re-issue |
| // BuildDisplayIdToPathMapOnBackgroundThread when the update |
| // returned. |
| if (update_in_flight_) |
| return; |
| |
| update_in_flight_ = true; |
| |
| // Enumerate device profile paths on a background thread. When this |
| // completes it will run another task on a background thread to read |
| // the profiles. This can impact the color of the browser so we want |
| // to set this to a higher priority to complete the task earlier |
| // during startup. |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, |
| base::BindOnce( |
| &ColorProfileReader::BuildDisplayIdToPathMapOnBackgroundThread), |
| base::BindOnce(&ColorProfileReader::BuildDisplayIdToPathMapCompleted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // static |
| ColorProfileReader::DisplayIdToPathMap |
| ColorProfileReader::BuildDisplayIdToPathMapOnBackgroundThread() { |
| SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY(); |
| DisplayIdToPathMap display_id_to_path_map; |
| ::EnumDisplayMonitors(nullptr, nullptr, EnumMonitorForProfilePathCallback, |
| reinterpret_cast<LPARAM>(&display_id_to_path_map)); |
| return display_id_to_path_map; |
| } |
| |
| void ColorProfileReader::BuildDisplayIdToPathMapCompleted( |
| DisplayIdToPathMap new_display_id_to_path_map) { |
| DCHECK(update_in_flight_); |
| |
| // Are there any changes from previous results |
| if (display_id_to_path_map_ == new_display_id_to_path_map) { |
| update_in_flight_ = false; |
| return; |
| } |
| |
| display_id_to_path_map_ = new_display_id_to_path_map; |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, |
| base::BindOnce(&ColorProfileReader::ReadProfilesOnBackgroundThread, |
| new_display_id_to_path_map), |
| base::BindOnce(&ColorProfileReader::ReadProfilesCompleted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // static |
| ColorProfileReader::DisplayIdToProfileMap |
| ColorProfileReader::ReadProfilesOnBackgroundThread( |
| DisplayIdToPathMap new_display_id_to_path_map) { |
| DisplayIdToProfileMap new_display_id_to_profile_map; |
| for (const auto& [display_id, profile_path] : new_display_id_to_path_map) { |
| std::string profile_data; |
| base::ReadFileToString(base::FilePath(profile_path), &profile_data); |
| if (!profile_data.empty()) { |
| new_display_id_to_profile_map[display_id] = |
| gfx::ICCProfile::FromData(profile_data.data(), profile_data.size()); |
| } |
| } |
| return new_display_id_to_profile_map; |
| } |
| |
| void ColorProfileReader::ReadProfilesCompleted( |
| DisplayIdToProfileMap display_id_to_profile_map) { |
| DCHECK(update_in_flight_); |
| update_in_flight_ = false; |
| display_id_to_profile_map_ = std::move(display_id_to_profile_map); |
| client_->OnColorProfilesChanged(); |
| } |
| |
| gfx::ColorSpace ColorProfileReader::GetDisplayColorSpace( |
| int64_t display_id) const { |
| gfx::ICCProfile icc_profile; |
| auto found = display_id_to_profile_map_.find(display_id); |
| if (found != display_id_to_profile_map_.end()) |
| icc_profile = found->second; |
| return icc_profile.IsValid() ? icc_profile.GetPrimariesOnlyColorSpace() |
| : gfx::ColorSpace::CreateSRGB(); |
| } |
| |
| } // namespace win |
| } // namespace display |