// Copyright (c) 2012 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 "ui/display/win/screen_win.h" #include #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/metrics/histogram_macros.h" #include "base/win/win_util.h" #include "ui/display/display.h" #include "ui/display/display_layout.h" #include "ui/display/display_layout_builder.h" #include "ui/display/win/display_info.h" #include "ui/display/win/dpi.h" #include "ui/display/win/scaling_util.h" #include "ui/display/win/screen_win_display.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/vector2d.h" namespace display { namespace win { namespace { // TODO(robliao): http://crbug.com/615514 Remove when ScreenWin usage is // resolved with Desktop Aura and WindowTreeHost. ScreenWin* g_screen_win_instance = nullptr; float GetMonitorScaleFactor(HMONITOR monitor) { DCHECK(monitor); if (Display::HasForceDeviceScaleFactor()) return Display::GetForcedDeviceScaleFactor(); if (base::win::IsProcessPerMonitorDpiAware()) { static auto get_dpi_for_monitor_func = [](){ using GetDpiForMonitorPtr = decltype(::GetDpiForMonitor)*; HMODULE shcore_dll = ::LoadLibrary(L"shcore.dll"); if (shcore_dll) { return reinterpret_cast( ::GetProcAddress(shcore_dll, "GetDpiForMonitor")); } return static_cast(nullptr); }(); UINT dpi_x; UINT dpi_y; if (get_dpi_for_monitor_func && SUCCEEDED(get_dpi_for_monitor_func(monitor, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y))) { DCHECK_EQ(dpi_x, dpi_y); return GetScalingFactorFromDPI(dpi_x); } } return GetDPIScale(); } std::vector FindAndRemoveTouchingDisplayInfos( const DisplayInfo& ref_display_info, std::vector* display_infos) { std::vector touching_display_infos; display_infos->erase( std::remove_if(display_infos->begin(), display_infos->end(), [&touching_display_infos, ref_display_info]( const DisplayInfo& display_info) { if (DisplayInfosTouch(ref_display_info, display_info)) { touching_display_infos.push_back(display_info); return true; } return false; }), display_infos->end()); return touching_display_infos; } Display CreateDisplayFromDisplayInfo(const DisplayInfo& display_info) { Display display(display_info.id()); float scale_factor = display_info.device_scale_factor(); display.set_device_scale_factor(scale_factor); display.set_work_area( gfx::ScaleToEnclosingRect(display_info.screen_work_rect(), 1.0f / scale_factor)); display.set_bounds(gfx::ScaleToEnclosingRect(display_info.screen_rect(), 1.0f / scale_factor)); display.set_rotation(display_info.rotation()); return display; } // Windows historically has had a hard time handling displays of DPIs higher // than 96. Handling multiple DPI displays means we have to deal with Windows' // monitor physical coordinates and map into Chrome's DIP coordinates. // // To do this, DisplayInfosToScreenWinDisplays reasons over monitors as a tree // using the primary monitor as the root. All monitors touching this root are // considered a children. // // This also presumes that all monitors are connected components. Windows, by UI // construction restricts the layout of monitors to connected components except // when DPI virtualization is happening. When this happens, we scale relative // to (0, 0). // // Note that this does not handle cases where a scaled display may have // insufficient room to lay out its children. In these cases, a DIP point could // map to multiple screen points due to overlap. The first discovered screen // will take precedence. std::vector DisplayInfosToScreenWinDisplays( const std::vector& display_infos) { // Find and extract the primary display. std::vector display_infos_remaining = display_infos; auto primary_display_iter = std::find_if( display_infos_remaining.begin(), display_infos_remaining.end(), []( const DisplayInfo& display_info) { return display_info.screen_rect().origin().IsOrigin(); }); DCHECK(primary_display_iter != display_infos_remaining.end()) << "Missing primary display."; std::vector available_parents; available_parents.push_back(*primary_display_iter); DisplayLayoutBuilder builder(primary_display_iter->id()); display_infos_remaining.erase(primary_display_iter); // Build the tree and determine DisplayPlacements along the way. while (available_parents.size()) { const DisplayInfo parent = available_parents.back(); available_parents.pop_back(); for (const auto& child : FindAndRemoveTouchingDisplayInfos(parent, &display_infos_remaining)) { builder.AddDisplayPlacement(CalculateDisplayPlacement(parent, child)); available_parents.push_back(child); } } // Layout and create the ScreenWinDisplays. std::vector displays; for (const auto& display_info : display_infos) displays.push_back(CreateDisplayFromDisplayInfo(display_info)); std::unique_ptr layout(builder.Build()); layout->ApplyToDisplayList(&displays, nullptr, 0); std::vector screen_win_displays; const size_t num_displays = display_infos.size(); for (size_t i = 0; i < num_displays; ++i) screen_win_displays.emplace_back(displays[i], display_infos[i]); return screen_win_displays; } std::vector ScreenWinDisplaysToDisplays( const std::vector& screen_win_displays) { std::vector displays; for (const auto& screen_win_display : screen_win_displays) displays.push_back(screen_win_display.display()); return displays; } MONITORINFOEX MonitorInfoFromHMONITOR(HMONITOR monitor) { MONITORINFOEX monitor_info; ::ZeroMemory(&monitor_info, sizeof(monitor_info)); monitor_info.cbSize = sizeof(monitor_info); ::GetMonitorInfo(monitor, &monitor_info); return monitor_info; } BOOL CALLBACK EnumMonitorCallback(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM data) { std::vector* display_infos = reinterpret_cast*>(data); DCHECK(display_infos); display_infos->push_back(DisplayInfo(MonitorInfoFromHMONITOR(monitor), GetMonitorScaleFactor(monitor))); return TRUE; } std::vector GetDisplayInfosFromSystem() { std::vector display_infos; EnumDisplayMonitors(nullptr, nullptr, EnumMonitorCallback, reinterpret_cast(&display_infos)); DCHECK_EQ(static_cast(::GetSystemMetrics(SM_CMONITORS)), display_infos.size()); return display_infos; } // Returns a point in |to_origin|'s coordinates and position scaled by // |scale_factor|. gfx::Point ScalePointRelative(const gfx::Point& from_origin, const gfx::Point& to_origin, const float scale_factor, const gfx::Point& point) { gfx::Vector2d from_origin_vector(from_origin.x(), from_origin.y()); gfx::Vector2d to_origin_vector(to_origin.x(), to_origin.y()); gfx::Point scaled_relative_point( gfx::ScaleToFlooredPoint(point - from_origin_vector, scale_factor)); return scaled_relative_point + to_origin_vector; } } // namespace ScreenWin::ScreenWin() { DCHECK(!g_screen_win_instance); g_screen_win_instance = this; Initialize(); } ScreenWin::~ScreenWin() { DCHECK_EQ(g_screen_win_instance, this); g_screen_win_instance = nullptr; } // static gfx::Point ScreenWin::ScreenToDIPPoint(const gfx::Point& pixel_point) { const ScreenWinDisplay screen_win_display = GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestScreenPoint, pixel_point); const Display display = screen_win_display.display(); return ScalePointRelative(screen_win_display.pixel_bounds().origin(), display.bounds().origin(), 1.0f / display.device_scale_factor(), pixel_point); } // static gfx::Point ScreenWin::DIPToScreenPoint(const gfx::Point& dip_point) { const ScreenWinDisplay screen_win_display = GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestDIPPoint, dip_point); const Display display = screen_win_display.display(); return ScalePointRelative(display.bounds().origin(), screen_win_display.pixel_bounds().origin(), display.device_scale_factor(), dip_point); } // static gfx::Point ScreenWin::ClientToDIPPoint(HWND hwnd, const gfx::Point& client_point) { return ScaleToFlooredPoint(client_point, 1.0f / GetScaleFactorForHWND(hwnd)); } // static gfx::Point ScreenWin::DIPToClientPoint(HWND hwnd, const gfx::Point& dip_point) { float scale_factor = GetScaleFactorForHWND(hwnd); return ScaleToFlooredPoint(dip_point, scale_factor); } // static gfx::Rect ScreenWin::ScreenToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) { const ScreenWinDisplay screen_win_display = hwnd ? GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestHWND, hwnd) : GetScreenWinDisplayVia( &ScreenWin::GetScreenWinDisplayNearestScreenRect, pixel_bounds); float scale_factor = screen_win_display.display().device_scale_factor(); gfx::Rect dip_rect = ScaleToEnclosingRect(pixel_bounds, 1.0f / scale_factor); const Display display = screen_win_display.display(); dip_rect.set_origin(ScalePointRelative( screen_win_display.pixel_bounds().origin(), display.bounds().origin(), 1.0f / scale_factor, pixel_bounds.origin())); return dip_rect; } // static gfx::Rect ScreenWin::DIPToScreenRect(HWND hwnd, const gfx::Rect& dip_bounds) { const ScreenWinDisplay screen_win_display = hwnd ? GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestHWND, hwnd) : GetScreenWinDisplayVia( &ScreenWin::GetScreenWinDisplayNearestDIPRect, dip_bounds); float scale_factor = screen_win_display.display().device_scale_factor(); gfx::Rect screen_rect = ScaleToEnclosingRect(dip_bounds, scale_factor); const Display display = screen_win_display.display(); screen_rect.set_origin(ScalePointRelative( display.bounds().origin(), screen_win_display.pixel_bounds().origin(), scale_factor, dip_bounds.origin())); return screen_rect; } // static gfx::Rect ScreenWin::ClientToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) { return ScaleToEnclosingRect(pixel_bounds, 1.0f / GetScaleFactorForHWND(hwnd)); } // static gfx::Rect ScreenWin::DIPToClientRect(HWND hwnd, const gfx::Rect& dip_bounds) { return ScaleToEnclosingRect(dip_bounds, GetScaleFactorForHWND(hwnd)); } // static gfx::Size ScreenWin::ScreenToDIPSize(HWND hwnd, const gfx::Size& size_in_pixels) { // Always ceil sizes. Otherwise we may be leaving off part of the bounds. return ScaleToCeiledSize(size_in_pixels, 1.0f / GetScaleFactorForHWND(hwnd)); } // static gfx::Size ScreenWin::DIPToScreenSize(HWND hwnd, const gfx::Size& dip_size) { float scale_factor = GetScaleFactorForHWND(hwnd); // Always ceil sizes. Otherwise we may be leaving off part of the bounds. return ScaleToCeiledSize(dip_size, scale_factor); } // static int ScreenWin::GetSystemMetricsForHwnd(HWND hwnd, int metric) { // GetSystemMetrics returns screen values based off of the primary monitor's // DPI. This will further scale based off of the DPI for |hwnd|. if (!g_screen_win_instance) return ::GetSystemMetrics(metric); Display primary_display(g_screen_win_instance->GetPrimaryDisplay()); int system_metrics_result = g_screen_win_instance->GetSystemMetrics(metric); float metrics_relative_scale_factor = hwnd ? GetScaleFactorForHWND(hwnd) / primary_display.device_scale_factor() : 1.0f; return static_cast(std::round( system_metrics_result * metrics_relative_scale_factor)); } // static int ScreenWin::GetSystemMetricsInDIP(int metric) { if (!g_screen_win_instance) return ::GetSystemMetrics(metric); // GetSystemMetrics returns screen values based off of the primary monitor's // DPI. Display primary_display(g_screen_win_instance->GetPrimaryDisplay()); int system_metrics_result = g_screen_win_instance->GetSystemMetrics(metric); return static_cast(std::round( system_metrics_result / primary_display.device_scale_factor())); } // static float ScreenWin::GetScaleFactorForHWND(HWND hwnd) { if (!g_screen_win_instance) return ScreenWinDisplay().display().device_scale_factor(); DCHECK(hwnd); HWND rootHwnd = g_screen_win_instance->GetRootWindow(hwnd); ScreenWinDisplay screen_win_display = g_screen_win_instance->GetScreenWinDisplayNearestHWND(rootHwnd); return screen_win_display.display().device_scale_factor(); } // static float ScreenWin::GetSystemScaleFactor() { return GetUnforcedDeviceScaleFactor(); } HWND ScreenWin::GetHWNDFromNativeView(gfx::NativeView window) const { NOTREACHED(); return nullptr; } gfx::NativeWindow ScreenWin::GetNativeWindowFromHWND(HWND hwnd) const { NOTREACHED(); return nullptr; } gfx::Point ScreenWin::GetCursorScreenPoint() { POINT pt; ::GetCursorPos(&pt); gfx::Point cursor_pos_pixels(pt); return ScreenToDIPPoint(cursor_pos_pixels); } bool ScreenWin::IsWindowUnderCursor(gfx::NativeWindow window) { POINT cursor_loc; HWND hwnd = ::GetCursorPos(&cursor_loc) ? ::WindowFromPoint(cursor_loc) : nullptr; return GetNativeWindowFromHWND(hwnd) == window; } gfx::NativeWindow ScreenWin::GetWindowAtScreenPoint(const gfx::Point& point) { gfx::Point point_in_pixels = DIPToScreenPoint(point); return GetNativeWindowFromHWND(WindowFromPoint(point_in_pixels.ToPOINT())); } int ScreenWin::GetNumDisplays() const { return static_cast(screen_win_displays_.size()); } const std::vector& ScreenWin::GetAllDisplays() const { return displays_; } Display ScreenWin::GetDisplayNearestWindow(gfx::NativeWindow window) const { HWND window_hwnd = GetHWNDFromNativeView(window); if (!window_hwnd) { // When |window| isn't rooted to a display, we should just return the // default display so we get some correct display information like the // scaling factor. return GetPrimaryDisplay(); } ScreenWinDisplay screen_win_display = GetScreenWinDisplayNearestHWND(window_hwnd); return screen_win_display.display(); } Display ScreenWin::GetDisplayNearestPoint(const gfx::Point& point) const { gfx::Point screen_point(DIPToScreenPoint(point)); ScreenWinDisplay screen_win_display = GetScreenWinDisplayNearestScreenPoint(screen_point); return screen_win_display.display(); } Display ScreenWin::GetDisplayMatching(const gfx::Rect& match_rect) const { ScreenWinDisplay screen_win_display = GetScreenWinDisplayNearestScreenRect(match_rect); return screen_win_display.display(); } Display ScreenWin::GetPrimaryDisplay() const { return GetPrimaryScreenWinDisplay().display(); } void ScreenWin::AddObserver(DisplayObserver* observer) { change_notifier_.AddObserver(observer); } void ScreenWin::RemoveObserver(DisplayObserver* observer) { change_notifier_.RemoveObserver(observer); } gfx::Rect ScreenWin::ScreenToDIPRectInWindow( gfx::NativeView view, const gfx::Rect& screen_rect) const { HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr; return ScreenToDIPRect(hwnd, screen_rect); } gfx::Rect ScreenWin::DIPToScreenRectInWindow(gfx::NativeView view, const gfx::Rect& dip_rect) const { HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr; return DIPToScreenRect(hwnd, dip_rect); } void ScreenWin::UpdateFromDisplayInfos( const std::vector& display_infos) { screen_win_displays_ = DisplayInfosToScreenWinDisplays(display_infos); displays_ = ScreenWinDisplaysToDisplays(screen_win_displays_); } void ScreenWin::Initialize() { singleton_hwnd_observer_.reset( new gfx::SingletonHwndObserver( base::Bind(&ScreenWin::OnWndProc, base::Unretained(this)))); UpdateFromDisplayInfos(GetDisplayInfosFromSystem()); RecordDisplayScaleFactors(); } MONITORINFOEX ScreenWin::MonitorInfoFromScreenPoint( const gfx::Point& screen_point) const { POINT initial_loc = { screen_point.x(), screen_point.y() }; return MonitorInfoFromHMONITOR(::MonitorFromPoint(initial_loc, MONITOR_DEFAULTTONEAREST)); } MONITORINFOEX ScreenWin::MonitorInfoFromScreenRect(const gfx::Rect& screen_rect) const { RECT win_rect = screen_rect.ToRECT(); return MonitorInfoFromHMONITOR(::MonitorFromRect(&win_rect, MONITOR_DEFAULTTONEAREST)); } MONITORINFOEX ScreenWin::MonitorInfoFromWindow(HWND hwnd, DWORD default_options) const { return MonitorInfoFromHMONITOR(::MonitorFromWindow(hwnd, default_options)); } HWND ScreenWin::GetRootWindow(HWND hwnd) const { return ::GetAncestor(hwnd, GA_ROOT); } int ScreenWin::GetSystemMetrics(int metric) const { return ::GetSystemMetrics(metric); } void ScreenWin::OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { if (message != WM_DISPLAYCHANGE && !(message == WM_SETTINGCHANGE && wparam == SPI_SETWORKAREA)) return; std::vector old_displays = std::move(displays_); UpdateFromDisplayInfos(GetDisplayInfosFromSystem()); change_notifier_.NotifyDisplaysChanged(old_displays, displays_); } ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestHWND(HWND hwnd) const { return GetScreenWinDisplay(MonitorInfoFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)); } ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestScreenRect( const gfx::Rect& screen_rect) const { return GetScreenWinDisplay(MonitorInfoFromScreenRect(screen_rect)); } ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestScreenPoint( const gfx::Point& screen_point) const { return GetScreenWinDisplay(MonitorInfoFromScreenPoint(screen_point)); } ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestDIPPoint( const gfx::Point& dip_point) const { ScreenWinDisplay primary_screen_win_display; for (const auto& screen_win_display : screen_win_displays_) { Display display = screen_win_display.display(); const gfx::Rect dip_bounds = display.bounds(); if (dip_bounds.Contains(dip_point)) return screen_win_display; else if (dip_bounds.origin().IsOrigin()) primary_screen_win_display = screen_win_display; } return primary_screen_win_display; } ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestDIPRect( const gfx::Rect& dip_rect) const { ScreenWinDisplay closest_screen_win_display; int64_t closest_distance_squared = INT64_MAX; for (const auto& screen_win_display : screen_win_displays_) { Display display = screen_win_display.display(); gfx::Rect dip_bounds = display.bounds(); if (dip_rect.Intersects(dip_bounds)) return screen_win_display; int64_t distance_squared = SquaredDistanceBetweenRects(dip_rect, dip_bounds); if (distance_squared < closest_distance_squared) { closest_distance_squared = distance_squared; closest_screen_win_display = screen_win_display; } } return closest_screen_win_display; } ScreenWinDisplay ScreenWin::GetPrimaryScreenWinDisplay() const { MONITORINFOEX monitor_info = MonitorInfoFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); ScreenWinDisplay screen_win_display = GetScreenWinDisplay(monitor_info); Display display = screen_win_display.display(); // The Windows primary monitor is defined to have an origin of (0, 0). DCHECK_EQ(0, display.bounds().origin().x()); DCHECK_EQ(0, display.bounds().origin().y()); return screen_win_display; } ScreenWinDisplay ScreenWin::GetScreenWinDisplay( const MONITORINFOEX& monitor_info) const { int64_t id = DisplayInfo::DeviceIdFromDeviceName(monitor_info.szDevice); for (const auto& screen_win_display : screen_win_displays_) { if (screen_win_display.display().id() == id) return screen_win_display; } // There is 1:1 correspondence between MONITORINFOEX and ScreenWinDisplay. // If we make it here, it means we have no displays and we should hand out the // default display. DCHECK_EQ(screen_win_displays_.size(), 0u); return ScreenWinDisplay(); } // static template ScreenWinDisplay ScreenWin::GetScreenWinDisplayVia(Getter getter, GetterType value) { if (!g_screen_win_instance) return ScreenWinDisplay(); return (g_screen_win_instance->*getter)(value); } void ScreenWin::RecordDisplayScaleFactors() const { std::vector unique_scale_factors; for (const auto& screen_win_display : screen_win_displays_) { const float scale_factor = screen_win_display.display().device_scale_factor(); // Multiply the reported value by 100 to display it as a percentage. Clamp // it so that if it's wildly out-of-band we won't send it to the backend. const int reported_scale = std::min( std::max(base::checked_cast(scale_factor * 100), 0), 1000); if (std::find(unique_scale_factors.begin(), unique_scale_factors.end(), reported_scale) == unique_scale_factors.end()) { unique_scale_factors.push_back(reported_scale); UMA_HISTOGRAM_SPARSE_SLOWLY("UI.DeviceScale", reported_scale); } } } } // namespace win } // namespace display