1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ui/display/screen.h" 6 7#import <ApplicationServices/ApplicationServices.h> 8#import <Cocoa/Cocoa.h> 9#include <stdint.h> 10 11#include <map> 12#include <memory> 13 14#include "base/bind.h" 15#include "base/logging.h" 16#include "base/mac/mac_util.h" 17#include "base/mac/scoped_cftyperef.h" 18#include "base/mac/scoped_nsobject.h" 19#include "base/mac/sdk_forward_declarations.h" 20#include "base/stl_util.h" 21#include "base/timer/timer.h" 22#include "ui/display/display.h" 23#include "ui/display/display_change_notifier.h" 24#include "ui/display/mac/display_link_mac.h" 25#include "ui/gfx/icc_profile.h" 26#include "ui/gfx/mac/coordinate_conversion.h" 27 28extern "C" { 29Boolean CGDisplayUsesForceToGray(void); 30} 31 32namespace display { 33namespace { 34 35// The delay to handle the display configuration changes. This is in place to 36// coalesce display update notifications and thereby avoid thrashing. 37const int64_t kConfigureDelayMs = 500; 38 39NSScreen* GetMatchingScreen(const gfx::Rect& match_rect) { 40 // Default to the monitor with the current keyboard focus, in case 41 // |match_rect| is not on any screen at all. 42 NSScreen* max_screen = [NSScreen mainScreen]; 43 int max_area = 0; 44 45 for (NSScreen* screen in [NSScreen screens]) { 46 gfx::Rect monitor_area = gfx::ScreenRectFromNSRect([screen frame]); 47 gfx::Rect intersection = gfx::IntersectRects(monitor_area, match_rect); 48 int area = intersection.width() * intersection.height(); 49 if (area > max_area) { 50 max_area = area; 51 max_screen = screen; 52 } 53 } 54 55 return max_screen; 56} 57 58Display BuildDisplayForScreen(NSScreen* screen) { 59 NSRect frame = [screen frame]; 60 61 CGDirectDisplayID display_id = [[[screen deviceDescription] 62 objectForKey:@"NSScreenNumber"] unsignedIntValue]; 63 64 Display display(display_id, gfx::Rect(NSRectToCGRect(frame))); 65 NSRect visible_frame = [screen visibleFrame]; 66 NSScreen* primary = [[NSScreen screens] firstObject]; 67 68 // Convert work area's coordinate systems. 69 if ([screen isEqual:primary]) { 70 gfx::Rect work_area = gfx::Rect(NSRectToCGRect(visible_frame)); 71 work_area.set_y(frame.size.height - visible_frame.origin.y - 72 visible_frame.size.height); 73 display.set_work_area(work_area); 74 } else { 75 display.set_bounds(gfx::ScreenRectFromNSRect(frame)); 76 display.set_work_area(gfx::ScreenRectFromNSRect(visible_frame)); 77 } 78 79 // Compute device scale factor 80 CGFloat scale = [screen backingScaleFactor]; 81 if (Display::HasForceDeviceScaleFactor()) 82 scale = Display::GetForcedDeviceScaleFactor(); 83 display.set_device_scale_factor(scale); 84 85 // Examine the presence of HDR. 86 bool enable_hdr = false; 87 if (@available(macOS 10.15, *)) { 88 if ([screen maximumPotentialExtendedDynamicRangeColorComponentValue] >= 89 2.0) { 90 enable_hdr = true; 91 } 92 } 93 94 // Compute DisplayColorSpaces. 95 gfx::ICCProfile icc_profile; 96 { 97 CGColorSpaceRef cg_color_space = [[screen colorSpace] CGColorSpace]; 98 if (cg_color_space) { 99 base::ScopedCFTypeRef<CFDataRef> cf_icc_profile( 100 CGColorSpaceCopyICCProfile(cg_color_space)); 101 if (cf_icc_profile) { 102 icc_profile = gfx::ICCProfile::FromData( 103 CFDataGetBytePtr(cf_icc_profile), CFDataGetLength(cf_icc_profile)); 104 } 105 } 106 } 107 gfx::DisplayColorSpaces display_color_spaces(icc_profile.GetColorSpace(), 108 gfx::BufferFormat::RGBA_8888); 109 if (Display::HasForceDisplayColorProfile()) { 110 if (Display::HasEnsureForcedColorProfile()) { 111 if (display_color_spaces != display.color_spaces()) { 112 LOG(FATAL) << "The display's color space does not match the color " 113 "space that was forced by the command line. This will " 114 "cause pixel tests to fail."; 115 } 116 } 117 } else { 118 if (enable_hdr) { 119 bool needs_alpha_values[] = {true, false}; 120 for (const auto& needs_alpha : needs_alpha_values) { 121 display_color_spaces.SetOutputColorSpaceAndBufferFormat( 122 gfx::ContentColorUsage::kHDR, needs_alpha, 123 gfx::ColorSpace::CreateExtendedSRGB(), gfx::BufferFormat::RGBA_F16); 124 } 125 } 126 display.set_color_spaces(display_color_spaces); 127 } 128 129 if (enable_hdr) { 130 display.set_color_depth(Display::kHDR10BitsPerPixel); 131 display.set_depth_per_component(Display::kHDR10BitsPerComponent); 132 } else { 133 display.set_color_depth(Display::kDefaultBitsPerPixel); 134 display.set_depth_per_component(Display::kDefaultBitsPerComponent); 135 } 136 display.set_is_monochrome(CGDisplayUsesForceToGray()); 137 138 if (auto display_link = ui::DisplayLinkMac::GetForDisplay(display_id)) 139 display.set_display_frequency(display_link->GetRefreshRate()); 140 141 // CGDisplayRotation returns a double. Display::SetRotationAsDegree will 142 // handle the unexpected situations were the angle is not a multiple of 90. 143 display.SetRotationAsDegree(static_cast<int>(CGDisplayRotation(display_id))); 144 return display; 145} 146 147Display BuildPrimaryDisplay() { 148 return BuildDisplayForScreen([[NSScreen screens] firstObject]); 149} 150 151// Returns the minimum Manhattan distance from |point| to corners of |screen| 152// frame. 153CGFloat GetMinimumDistanceToCorner(const NSPoint& point, NSScreen* screen) { 154 NSRect frame = [screen frame]; 155 CGFloat distance = 156 fabs(point.x - NSMinX(frame)) + fabs(point.y - NSMinY(frame)); 157 distance = std::min( 158 distance, fabs(point.x - NSMaxX(frame)) + fabs(point.y - NSMinY(frame))); 159 distance = std::min( 160 distance, fabs(point.x - NSMinX(frame)) + fabs(point.y - NSMaxY(frame))); 161 distance = std::min( 162 distance, fabs(point.x - NSMaxX(frame)) + fabs(point.y - NSMaxY(frame))); 163 return distance; 164} 165 166class ScreenMac : public Screen { 167 public: 168 ScreenMac() 169 : configure_timer_(FROM_HERE, 170 base::TimeDelta::FromMilliseconds(kConfigureDelayMs), 171 base::BindRepeating(&ScreenMac::ConfigureTimerFired, 172 base::Unretained(this))) { 173 old_displays_ = displays_ = BuildDisplaysFromQuartz(); 174 CGDisplayRegisterReconfigurationCallback( 175 ScreenMac::DisplayReconfigurationCallBack, this); 176 177 auto update_block = ^(NSNotification* notification) { 178 OnNSScreensMayHaveChanged(); 179 }; 180 181 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 182 screen_color_change_observer_.reset( 183 [[center addObserverForName:NSScreenColorSpaceDidChangeNotification 184 object:nil 185 queue:nil 186 usingBlock:update_block] retain]); 187 screen_params_change_observer_.reset([[center 188 addObserverForName:NSApplicationDidChangeScreenParametersNotification 189 object:nil 190 queue:nil 191 usingBlock:update_block] retain]); 192 } 193 194 ~ScreenMac() override { 195 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 196 [center removeObserver:screen_color_change_observer_]; 197 [center removeObserver:screen_params_change_observer_]; 198 199 CGDisplayRemoveReconfigurationCallback( 200 ScreenMac::DisplayReconfigurationCallBack, this); 201 } 202 203 gfx::Point GetCursorScreenPoint() override { 204 // Flip coordinates to gfx (0,0 in top-left corner) using primary screen. 205 return gfx::ScreenPointFromNSPoint([NSEvent mouseLocation]); 206 } 207 208 bool IsWindowUnderCursor(gfx::NativeWindow native_window) override { 209 NSWindow* window = native_window.GetNativeNSWindow(); 210 return [NSWindow windowNumberAtPoint:[NSEvent mouseLocation] 211 belowWindowWithWindowNumber:0] == [window windowNumber]; 212 } 213 214 gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override { 215 NOTIMPLEMENTED(); 216 return gfx::NativeWindow(); 217 } 218 219 int GetNumDisplays() const override { return GetAllDisplays().size(); } 220 221 const std::vector<Display>& GetAllDisplays() const override { 222 UpdateDisplaysIfNeeded(); 223 return displays_; 224 } 225 226 Display GetDisplayNearestWindow( 227 gfx::NativeWindow native_window) const override { 228 UpdateDisplaysIfNeeded(); 229 230 if (displays_.size() == 1) 231 return displays_[0]; 232 233 NSWindow* window = native_window.GetNativeNSWindow(); 234 if (!window) 235 return GetPrimaryDisplay(); 236 237 // Note the following line calls -[NSWindow 238 // _bestScreenBySpaceAssignmentOrGeometry] which is quite expensive and 239 // performs IPC with the window server process. 240 NSScreen* match_screen = [window screen]; 241 242 if (!match_screen) 243 return GetPrimaryDisplay(); 244 return GetCachedDisplayForScreen(match_screen); 245 } 246 247 Display GetDisplayNearestView(gfx::NativeView native_view) const override { 248 NSView* view = native_view.GetNativeNSView(); 249 NSWindow* window = [view window]; 250 if (!window) 251 return GetPrimaryDisplay(); 252 return GetDisplayNearestWindow(window); 253 } 254 255 Display GetDisplayNearestPoint(const gfx::Point& point) const override { 256 NSArray* screens = [NSScreen screens]; 257 if ([screens count] <= 1) 258 return GetPrimaryDisplay(); 259 260 NSPoint ns_point = NSPointFromCGPoint(point.ToCGPoint()); 261 NSScreen* primary = [screens objectAtIndex:0]; 262 ns_point.y = NSMaxY([primary frame]) - ns_point.y; 263 for (NSScreen* screen in screens) { 264 if (NSMouseInRect(ns_point, [screen frame], NO)) 265 return GetCachedDisplayForScreen(screen); 266 } 267 268 NSScreen* nearest_screen = primary; 269 CGFloat min_distance = CGFLOAT_MAX; 270 for (NSScreen* screen in screens) { 271 CGFloat distance = GetMinimumDistanceToCorner(ns_point, screen); 272 if (distance < min_distance) { 273 min_distance = distance; 274 nearest_screen = screen; 275 } 276 } 277 return GetCachedDisplayForScreen(nearest_screen); 278 } 279 280 // Returns the display that most closely intersects the provided bounds. 281 Display GetDisplayMatching(const gfx::Rect& match_rect) const override { 282 NSScreen* match_screen = GetMatchingScreen(match_rect); 283 return GetCachedDisplayForScreen(match_screen); 284 } 285 286 // Returns the primary display. 287 Display GetPrimaryDisplay() const override { 288 // Primary display is defined as the display with the menubar, 289 // which is always at index 0. 290 NSScreen* primary = [[NSScreen screens] firstObject]; 291 Display display = GetCachedDisplayForScreen(primary); 292 return display; 293 } 294 295 void AddObserver(DisplayObserver* observer) override { 296 change_notifier_.AddObserver(observer); 297 } 298 299 void RemoveObserver(DisplayObserver* observer) override { 300 change_notifier_.RemoveObserver(observer); 301 } 302 303 static void DisplayReconfigurationCallBack(CGDirectDisplayID display, 304 CGDisplayChangeSummaryFlags flags, 305 void* userInfo) { 306 ScreenMac* screen_mac = static_cast<ScreenMac*>(userInfo); 307 screen_mac->OnNSScreensMayHaveChanged(); 308 } 309 310 private: 311 Display GetCachedDisplayForScreen(NSScreen* screen) const { 312 UpdateDisplaysIfNeeded(); 313 const CGDirectDisplayID display_id = [[[screen deviceDescription] 314 objectForKey:@"NSScreenNumber"] unsignedIntValue]; 315 for (const Display& display : displays_) { 316 if (display_id == display.id()) 317 return display; 318 } 319 // In theory, this should not be reached, because |displays_require_update_| 320 // should have been set prior to -[NSScreen screens] changing. In practice, 321 // on Catalina, it has been observed that -[NSScreen screens] changes before 322 // any notifications are received. 323 // https://crbug.com/1021340. 324 OnNSScreensMayHaveChanged(); 325 DLOG(ERROR) << "Value of -[NSScreen screens] changed before notification."; 326 return BuildDisplayForScreen(screen); 327 } 328 329 void UpdateDisplaysIfNeeded() const { 330 if (displays_require_update_) { 331 displays_ = BuildDisplaysFromQuartz(); 332 displays_require_update_ = false; 333 } 334 } 335 336 void ConfigureTimerFired() { 337 UpdateDisplaysIfNeeded(); 338 change_notifier_.NotifyDisplaysChanged(old_displays_, displays_); 339 old_displays_ = displays_; 340 } 341 342 std::vector<Display> BuildDisplaysFromQuartz() const { 343 // Don't just return all online displays. This would include displays 344 // that mirror other displays, which are not desired in this list. It's 345 // tempting to use the count returned by CGGetActiveDisplayList, but active 346 // displays exclude sleeping displays, and those are desired. 347 348 // It would be ridiculous to have this many displays connected, but 349 // CGDirectDisplayID is just an integer, so supporting up to this many 350 // doesn't hurt. 351 CGDirectDisplayID online_displays[1024]; 352 CGDisplayCount online_display_count = 0; 353 if (CGGetOnlineDisplayList(base::size(online_displays), online_displays, 354 &online_display_count) != kCGErrorSuccess) { 355 return std::vector<Display>(1, BuildPrimaryDisplay()); 356 } 357 358 typedef std::map<int64_t, NSScreen*> ScreenIdsToScreensMap; 359 ScreenIdsToScreensMap screen_ids_to_screens; 360 for (NSScreen* screen in [NSScreen screens]) { 361 NSDictionary* screen_device_description = [screen deviceDescription]; 362 int64_t screen_id = [[screen_device_description 363 objectForKey:@"NSScreenNumber"] unsignedIntValue]; 364 screen_ids_to_screens[screen_id] = screen; 365 } 366 367 std::vector<Display> displays; 368 for (CGDisplayCount online_display_index = 0; 369 online_display_index < online_display_count; ++online_display_index) { 370 CGDirectDisplayID online_display = online_displays[online_display_index]; 371 if (CGDisplayMirrorsDisplay(online_display) == kCGNullDirectDisplay) { 372 // If this display doesn't mirror any other, include it in the list. 373 // The primary display in a mirrored set will be counted, but those that 374 // mirror it will not be. 375 ScreenIdsToScreensMap::iterator foundScreen = 376 screen_ids_to_screens.find(online_display); 377 if (foundScreen != screen_ids_to_screens.end()) { 378 displays.push_back(BuildDisplayForScreen(foundScreen->second)); 379 } 380 } 381 } 382 383 return displays.empty() ? std::vector<Display>(1, BuildPrimaryDisplay()) 384 : displays; 385 } 386 387 void OnNSScreensMayHaveChanged() const { 388 // Timer::Reset() ensures at least another interval passes before the 389 // associated task runs, effectively coalescing these events. 390 configure_timer_.Reset(); 391 displays_require_update_ = true; 392 } 393 394 // The displays currently attached to the device. Updated by 395 // UpdateDisplaysIfNeeded. 396 mutable std::vector<Display> displays_; 397 398 // Whether or not |displays_| might need to be upated. Set in 399 // OnNSScreensMayHaveChanged, and un-set by UpdateDisplaysIfNeeded. 400 mutable bool displays_require_update_ = false; 401 402 // The timer to delay configuring outputs and notifying observers (to coalesce 403 // several updates into one update). 404 mutable base::RetainingOneShotTimer configure_timer_; 405 406 // The displays last communicated to the DisplayChangeNotifier. 407 std::vector<Display> old_displays_; 408 409 // The observers notified by NSScreenColorSpaceDidChangeNotification and 410 // NSApplicationDidChangeScreenParametersNotification. 411 base::scoped_nsobject<id> screen_color_change_observer_; 412 base::scoped_nsobject<id> screen_params_change_observer_; 413 414 DisplayChangeNotifier change_notifier_; 415 416 DISALLOW_COPY_AND_ASSIGN(ScreenMac); 417}; 418 419} // namespace 420 421// static 422gfx::NativeWindow Screen::GetWindowForView(gfx::NativeView native_view) { 423#if !defined(USE_AURA) 424 NSView* view = native_view.GetNativeNSView(); 425 return [view window]; 426#else 427 gfx::NativeWindow window = nil; 428 return window; 429#endif 430} 431 432#if !defined(USE_AURA) 433Screen* CreateNativeScreen() { 434 return new ScreenMac; 435} 436#endif 437 438} // namespace display 439