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/. */
6
7#include "ScreenHelperCocoa.h"
8
9#import <Cocoa/Cocoa.h>
10
11#include "mozilla/Logging.h"
12#include "nsCocoaUtils.h"
13#include "nsObjCExceptions.h"
14
15using namespace mozilla;
16
17static LazyLogModule sScreenLog("WidgetScreen");
18
19@interface ScreenHelperDelegate : NSObject {
20 @private
21  mozilla::widget::ScreenHelperCocoa* mHelper;
22}
23
24- (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper;
25- (void)didChangeScreenParameters:(NSNotification*)aNotification;
26@end
27
28@implementation ScreenHelperDelegate
29- (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper {
30  if ((self = [self init])) {
31    mHelper = aScreenHelper;
32
33    [[NSNotificationCenter defaultCenter]
34        addObserver:self
35           selector:@selector(didChangeScreenParameters:)
36               name:NSApplicationDidChangeScreenParametersNotification
37             object:nil];
38  }
39
40  return self;
41}
42
43- (void)dealloc {
44  [[NSNotificationCenter defaultCenter] removeObserver:self];
45  [super dealloc];
46}
47
48- (void)didChangeScreenParameters:(NSNotification*)aNotification {
49  MOZ_LOG(sScreenLog, LogLevel::Debug,
50          ("Received NSApplicationDidChangeScreenParametersNotification"));
51
52  mHelper->RefreshScreens();
53}
54@end
55
56namespace mozilla {
57namespace widget {
58
59ScreenHelperCocoa::ScreenHelperCocoa() {
60  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
61
62  MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperCocoa created"));
63
64  mDelegate = [[ScreenHelperDelegate alloc] initWithScreenHelper:this];
65
66  RefreshScreens();
67
68  NS_OBJC_END_TRY_IGNORE_BLOCK;
69}
70
71ScreenHelperCocoa::~ScreenHelperCocoa() {
72  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
73
74  [mDelegate release];
75
76  NS_OBJC_END_TRY_IGNORE_BLOCK;
77}
78
79static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
80  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
81
82  DesktopToLayoutDeviceScale contentsScaleFactor(nsCocoaUtils::GetBackingScaleFactor(aScreen));
83  CSSToLayoutDeviceScale defaultCssScaleFactor(contentsScaleFactor.scale);
84  NSRect frame = [aScreen frame];
85  LayoutDeviceIntRect rect =
86      nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, contentsScaleFactor.scale);
87  frame = [aScreen visibleFrame];
88  LayoutDeviceIntRect availRect =
89      nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, contentsScaleFactor.scale);
90  NSWindowDepth depth = [aScreen depth];
91  uint32_t pixelDepth = NSBitsPerPixelFromDepth(depth);
92  float dpi = 96.0f;
93  CGDirectDisplayID displayID =
94      [[[aScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
95  CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
96  if (heightMM > 0) {
97    dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
98  }
99  MOZ_LOG(sScreenLog, LogLevel::Debug,
100          ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y, rect.width,
101           rect.height, availRect.x, availRect.y, availRect.width, availRect.height, pixelDepth,
102           contentsScaleFactor.scale, defaultCssScaleFactor.scale, dpi));
103
104  RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth, contentsScaleFactor,
105                                     defaultCssScaleFactor, dpi);
106  return screen.forget();
107
108  NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
109}
110
111void ScreenHelperCocoa::RefreshScreens() {
112  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
113
114  MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
115
116  AutoTArray<RefPtr<Screen>, 4> screens;
117
118  for (NSScreen* screen in [NSScreen screens]) {
119    NSDictionary* desc = [screen deviceDescription];
120    if ([desc objectForKey:NSDeviceIsScreen] == nil) {
121      continue;
122    }
123    screens.AppendElement(MakeScreen(screen));
124  }
125
126  ScreenManager& screenManager = ScreenManager::GetSingleton();
127  screenManager.Refresh(std::move(screens));
128
129  NS_OBJC_END_TRY_IGNORE_BLOCK;
130}
131
132NSScreen* ScreenHelperCocoa::CocoaScreenForScreen(nsIScreen* aScreen) {
133  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
134
135  for (NSScreen* screen in [NSScreen screens]) {
136    NSDictionary* desc = [screen deviceDescription];
137    if ([desc objectForKey:NSDeviceIsScreen] == nil) {
138      continue;
139    }
140    LayoutDeviceIntRect rect;
141    double scale;
142    aScreen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height);
143    aScreen->GetContentsScaleFactor(&scale);
144    NSRect frame = [screen frame];
145    LayoutDeviceIntRect frameRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, scale);
146    if (rect == frameRect) {
147      return screen;
148    }
149  }
150  return [NSScreen mainScreen];
151
152  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
153}
154
155}  // namespace widget
156}  // namespace mozilla
157