1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScreenHelperCocoa.h"
9 #import <Cocoa/Cocoa.h>
11 #include "mozilla/Logging.h"
12 #include "nsCocoaFeatures.h"
13 #include "nsCocoaUtils.h"
14 #include "nsObjCExceptions.h"
16 using namespace mozilla;
18 static LazyLogModule sScreenLog("WidgetScreen");
20 @interface ScreenHelperDelegate : NSObject {
22 mozilla::widget::ScreenHelperCocoa* mHelper;
25 - (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper;
26 - (void)didChangeScreenParameters:(NSNotification*)aNotification;
29 @implementation ScreenHelperDelegate
30 - (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper {
31 if ((self = [self init])) {
32 mHelper = aScreenHelper;
34 [[NSNotificationCenter defaultCenter]
36 selector:@selector(didChangeScreenParameters:)
37 name:NSApplicationDidChangeScreenParametersNotification
45 [[NSNotificationCenter defaultCenter] removeObserver:self];
49 - (void)didChangeScreenParameters:(NSNotification*)aNotification {
50 MOZ_LOG(sScreenLog, LogLevel::Debug,
51 ("Received NSApplicationDidChangeScreenParametersNotification"));
53 mHelper->RefreshScreens();
60 ScreenHelperCocoa::ScreenHelperCocoa() {
61 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
63 MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperCocoa created"));
65 mDelegate = [[ScreenHelperDelegate alloc] initWithScreenHelper:this];
69 NS_OBJC_END_TRY_IGNORE_BLOCK;
72 ScreenHelperCocoa::~ScreenHelperCocoa() {
73 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
77 NS_OBJC_END_TRY_IGNORE_BLOCK;
80 static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
81 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
83 DesktopToLayoutDeviceScale contentsScaleFactor(
84 nsCocoaUtils::GetBackingScaleFactor(aScreen));
85 CSSToLayoutDeviceScale defaultCssScaleFactor(contentsScaleFactor.scale);
86 NSRect frame = [aScreen frame];
87 LayoutDeviceIntRect rect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
88 frame, contentsScaleFactor.scale);
89 frame = [aScreen visibleFrame];
90 LayoutDeviceIntRect availRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
91 frame, contentsScaleFactor.scale);
93 // aScreen may be capable of displaying multiple pixel depths, for example by
94 // transitioning to an HDR-capable depth when required by a window displayed
95 // on the screen. We want to note the maximum capabilities of the screen, so
96 // we use the largest depth it offers.
97 uint32_t pixelDepth = 0;
98 const NSWindowDepth* depths = [aScreen supportedWindowDepths];
99 for (size_t d = 0; NSWindowDepth depth = depths[d]; d++) {
100 uint32_t bpp = NSBitsPerPixelFromDepth(depth);
101 if (bpp > pixelDepth) {
106 // But it confuses content if we return too-high a value here. Cap depth with
107 // a value that matches what Chrome returns for high bpp screens.
108 static const uint32_t MAX_REPORTED_PIXEL_DEPTH = 30;
109 if (pixelDepth > MAX_REPORTED_PIXEL_DEPTH) {
110 pixelDepth = MAX_REPORTED_PIXEL_DEPTH;
113 // What's the maximum color component value this screen can display? This
114 // is a reasonable stand-in for measuring peak brightness.
115 CGFloat componentValueMax =
116 aScreen.maximumPotentialExtendedDynamicRangeColorComponentValue;
118 // Should we treat this as HDR? Based on spec at
119 // https://drafts.csswg.org/mediaqueries-5/#dynamic-range, we'll consider it
120 // HDR if it has pixel depth greater than 24, and if has high peak brightness,
121 // which we measure by checking if it can represent component values greater
124 // Also, on HDR screens, users may want to force SDR by setting a different
125 // colorspace, for example by using the "Photography (P3 D65)" preset. In that
126 // case, componentValueMax will be 1.0 and we want to treat the display as
128 bool isHDR = pixelDepth > 24 && componentValueMax > 1.0;
130 // Double-check HDR against the platform capabilities.
131 isHDR &= nsCocoaFeatures::OnBigSurOrLater();
134 CGDirectDisplayID displayID =
135 [[[aScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
136 CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
138 dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
140 MOZ_LOG(sScreenLog, LogLevel::Debug,
141 ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y,
142 rect.width, rect.height, availRect.x, availRect.y, availRect.width,
143 availRect.height, pixelDepth, contentsScaleFactor.scale,
144 defaultCssScaleFactor.scale, dpi));
146 // Getting the refresh rate is a little hard on OS X. We could use
147 // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
148 // involved. Ideally we could query it from vsync. For now, we leave it out.
149 RefPtr<Screen> screen =
150 new Screen(rect, availRect, pixelDepth, pixelDepth, 0,
151 contentsScaleFactor, defaultCssScaleFactor, dpi,
152 Screen::IsPseudoDisplay::No, Screen::IsHDR(isHDR));
153 return screen.forget();
155 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
158 void ScreenHelperCocoa::RefreshScreens() {
159 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
161 MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
163 AutoTArray<RefPtr<Screen>, 4> screens;
165 for (NSScreen* screen in [NSScreen screens]) {
166 NSDictionary* desc = [screen deviceDescription];
167 if ([desc objectForKey:NSDeviceIsScreen] == nil) {
170 screens.AppendElement(MakeScreen(screen));
173 ScreenManager::Refresh(std::move(screens));
175 NS_OBJC_END_TRY_IGNORE_BLOCK;
178 NSScreen* ScreenHelperCocoa::CocoaScreenForScreen(nsIScreen* aScreen) {
179 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
181 for (NSScreen* screen in [NSScreen screens]) {
182 NSDictionary* desc = [screen deviceDescription];
183 if ([desc objectForKey:NSDeviceIsScreen] == nil) {
186 LayoutDeviceIntRect rect;
188 aScreen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height);
189 aScreen->GetContentsScaleFactor(&scale);
190 NSRect frame = [screen frame];
191 LayoutDeviceIntRect frameRect =
192 nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, scale);
193 if (rect == frameRect) {
197 return [NSScreen mainScreen];
199 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
202 } // namespace widget
203 } // namespace mozilla