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 "nsCocoaUtils.h"
13 #include "nsObjCExceptions.h"
15 using namespace mozilla;
17 static LazyLogModule sScreenLog("WidgetScreen");
19 @interface ScreenHelperDelegate : NSObject {
21 mozilla::widget::ScreenHelperCocoa* mHelper;
24 - (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper;
25 - (void)didChangeScreenParameters:(NSNotification*)aNotification;
28 @implementation ScreenHelperDelegate
29 - (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper {
30 if ((self = [self init])) {
31 mHelper = aScreenHelper;
33 [[NSNotificationCenter defaultCenter]
35 selector:@selector(didChangeScreenParameters:)
36 name:NSApplicationDidChangeScreenParametersNotification
44 [[NSNotificationCenter defaultCenter] removeObserver:self];
48 - (void)didChangeScreenParameters:(NSNotification*)aNotification {
49 MOZ_LOG(sScreenLog, LogLevel::Debug,
50 ("Received NSApplicationDidChangeScreenParametersNotification"));
52 mHelper->RefreshScreens();
59 ScreenHelperCocoa::ScreenHelperCocoa() {
60 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
62 MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperCocoa created"));
64 mDelegate = [[ScreenHelperDelegate alloc] initWithScreenHelper:this];
68 NS_OBJC_END_TRY_IGNORE_BLOCK;
71 ScreenHelperCocoa::~ScreenHelperCocoa() {
72 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
76 NS_OBJC_END_TRY_IGNORE_BLOCK;
79 static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
80 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
82 DesktopToLayoutDeviceScale contentsScaleFactor(
83 nsCocoaUtils::GetBackingScaleFactor(aScreen));
84 CSSToLayoutDeviceScale defaultCssScaleFactor(contentsScaleFactor.scale);
85 NSRect frame = [aScreen frame];
86 LayoutDeviceIntRect rect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
87 frame, contentsScaleFactor.scale);
88 frame = [aScreen visibleFrame];
89 LayoutDeviceIntRect availRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(
90 frame, contentsScaleFactor.scale);
92 // aScreen may be capable of displaying multiple pixel depths, for example by
93 // transitioning to an HDR-capable depth when required by a window displayed
94 // on the screen. We want to note the maximum capabilities of the screen, so
95 // we use the largest depth it offers.
96 uint32_t pixelDepth = 0;
97 const NSWindowDepth* depths = [aScreen supportedWindowDepths];
98 for (size_t d = 0; NSWindowDepth depth = depths[d]; d++) {
99 uint32_t bpp = NSBitsPerPixelFromDepth(depth);
100 if (bpp > pixelDepth) {
105 // But it confuses content if we return too-high a value here. Cap depth with
106 // a value that matches what Chrome returns for high bpp screens.
107 static const uint32_t MAX_REPORTED_PIXEL_DEPTH = 30;
108 if (pixelDepth > MAX_REPORTED_PIXEL_DEPTH) {
109 pixelDepth = MAX_REPORTED_PIXEL_DEPTH;
113 CGDirectDisplayID displayID =
114 [[[aScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
115 CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
117 dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
119 MOZ_LOG(sScreenLog, LogLevel::Debug,
120 ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y,
121 rect.width, rect.height, availRect.x, availRect.y, availRect.width,
122 availRect.height, pixelDepth, contentsScaleFactor.scale,
123 defaultCssScaleFactor.scale, dpi));
125 // Getting the refresh rate is a little hard on OS X. We could use
126 // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
127 // involved. Ideally we could query it from vsync. For now, we leave it out.
128 RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth, 0,
129 contentsScaleFactor, defaultCssScaleFactor,
130 dpi, Screen::IsPseudoDisplay::No);
131 return screen.forget();
133 NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
136 void ScreenHelperCocoa::RefreshScreens() {
137 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
139 MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
141 AutoTArray<RefPtr<Screen>, 4> screens;
143 for (NSScreen* screen in [NSScreen screens]) {
144 NSDictionary* desc = [screen deviceDescription];
145 if ([desc objectForKey:NSDeviceIsScreen] == nil) {
148 screens.AppendElement(MakeScreen(screen));
151 ScreenManager::Refresh(std::move(screens));
153 NS_OBJC_END_TRY_IGNORE_BLOCK;
156 NSScreen* ScreenHelperCocoa::CocoaScreenForScreen(nsIScreen* aScreen) {
157 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
159 for (NSScreen* screen in [NSScreen screens]) {
160 NSDictionary* desc = [screen deviceDescription];
161 if ([desc objectForKey:NSDeviceIsScreen] == nil) {
164 LayoutDeviceIntRect rect;
166 aScreen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height);
167 aScreen->GetContentsScaleFactor(&scale);
168 NSRect frame = [screen frame];
169 LayoutDeviceIntRect frameRect =
170 nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, scale);
171 if (rect == frameRect) {
175 return [NSScreen mainScreen];
177 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
180 } // namespace widget
181 } // namespace mozilla