1/*
2 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
12
13#include <math.h>
14#include <algorithm>
15#include <Cocoa/Cocoa.h>
16
17#include "webrtc/system_wrappers/include/logging.h"
18
19#if !defined(MAC_OS_X_VERSION_10_7) || \
20    MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
21
22@interface NSScreen (LionAPI)
23- (CGFloat)backingScaleFactor;
24- (NSRect)convertRectToBacking:(NSRect)aRect;
25@end
26
27#endif  // MAC_OS_X_VERSION_10_7
28
29namespace webrtc {
30
31namespace {
32
33DesktopRect NSRectToDesktopRect(const NSRect& ns_rect) {
34  return DesktopRect::MakeLTRB(
35      static_cast<int>(floor(ns_rect.origin.x)),
36      static_cast<int>(floor(ns_rect.origin.y)),
37      static_cast<int>(ceil(ns_rect.origin.x + ns_rect.size.width)),
38      static_cast<int>(ceil(ns_rect.origin.y + ns_rect.size.height)));
39}
40
41DesktopRect JoinRects(const DesktopRect& a,
42                              const DesktopRect& b) {
43  return DesktopRect::MakeLTRB(
44      std::min(a.left(), b.left()),
45      std::min(a.top(), b.top()),
46      std::max(a.right(), b.right()),
47      std::max(a.bottom(), b.bottom()));
48}
49
50// Inverts the position of |rect| from bottom-up coordinates to top-down,
51// relative to |bounds|.
52void InvertRectYOrigin(const DesktopRect& bounds,
53                       DesktopRect* rect) {
54  assert(bounds.top() == 0);
55  *rect = DesktopRect::MakeXYWH(
56      rect->left(), bounds.bottom() - rect->bottom(),
57      rect->width(), rect->height());
58}
59
60MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) {
61  MacDisplayConfiguration display_config;
62
63  // Fetch the NSScreenNumber, which is also the CGDirectDisplayID.
64  NSDictionary* device_description = [screen deviceDescription];
65  display_config.id = static_cast<CGDirectDisplayID>(
66      [[device_description objectForKey:@"NSScreenNumber"] intValue]);
67
68  // Determine the display's logical & physical dimensions.
69  NSRect ns_bounds = [screen frame];
70  display_config.bounds = NSRectToDesktopRect(ns_bounds);
71
72  // If the host is running Mac OS X 10.7+ or later, query the scaling factor
73  // between logical and physical (aka "backing") pixels, otherwise assume 1:1.
74  if ([screen respondsToSelector:@selector(backingScaleFactor)] &&
75      [screen respondsToSelector:@selector(convertRectToBacking:)]) {
76    display_config.dip_to_pixel_scale = [screen backingScaleFactor];
77    NSRect ns_pixel_bounds = [screen convertRectToBacking: ns_bounds];
78    display_config.pixel_bounds = NSRectToDesktopRect(ns_pixel_bounds);
79  } else {
80    display_config.pixel_bounds = display_config.bounds;
81  }
82
83  return display_config;
84}
85
86}  // namespace
87
88MacDisplayConfiguration::MacDisplayConfiguration()
89    : id(0),
90      dip_to_pixel_scale(1.0f) {
91}
92
93MacDesktopConfiguration::MacDesktopConfiguration()
94    : dip_to_pixel_scale(1.0f) {
95}
96
97MacDesktopConfiguration::~MacDesktopConfiguration() {
98}
99
100// static
101MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) {
102  MacDesktopConfiguration desktop_config;
103
104  NSArray* screens = [NSScreen screens];
105  assert(screens);
106
107  // Iterator over the monitors, adding the primary monitor and monitors whose
108  // DPI match that of the primary monitor.
109  for (NSUInteger i = 0; i < [screens count]; ++i) {
110    MacDisplayConfiguration display_config =
111        GetConfigurationForScreen([screens objectAtIndex: i]);
112
113    if (i == 0)
114      desktop_config.dip_to_pixel_scale = display_config.dip_to_pixel_scale;
115
116    // Cocoa uses bottom-up coordinates, so if the caller wants top-down then
117    // we need to invert the positions of secondary monitors relative to the
118    // primary one (the primary monitor's position is (0,0) in both systems).
119    if (i > 0 && origin == TopLeftOrigin) {
120      InvertRectYOrigin(desktop_config.displays[0].bounds,
121                        &display_config.bounds);
122      // |display_bounds| is density dependent, so we need to convert the
123      // primay monitor's position into the secondary monitor's density context.
124      float scaling_factor = display_config.dip_to_pixel_scale /
125          desktop_config.displays[0].dip_to_pixel_scale;
126      DesktopRect primary_bounds = DesktopRect::MakeLTRB(
127          desktop_config.displays[0].pixel_bounds.left() * scaling_factor,
128          desktop_config.displays[0].pixel_bounds.top() * scaling_factor,
129          desktop_config.displays[0].pixel_bounds.right() * scaling_factor,
130          desktop_config.displays[0].pixel_bounds.bottom() * scaling_factor);
131      InvertRectYOrigin(primary_bounds, &display_config.pixel_bounds);
132    }
133
134    // Add the display to the configuration.
135    desktop_config.displays.push_back(display_config);
136
137    // Update the desktop bounds to account for this display, unless the current
138    // display uses different DPI settings.
139    if (display_config.dip_to_pixel_scale ==
140        desktop_config.dip_to_pixel_scale) {
141      desktop_config.bounds =
142          JoinRects(desktop_config.bounds, display_config.bounds);
143      desktop_config.pixel_bounds =
144          JoinRects(desktop_config.pixel_bounds, display_config.pixel_bounds);
145    }
146  }
147
148  return desktop_config;
149}
150
151// For convenience of comparing MacDisplayConfigurations in
152// MacDesktopConfiguration::Equals.
153bool operator==(const MacDisplayConfiguration& left,
154                const MacDisplayConfiguration& right) {
155  return left.id == right.id &&
156      left.bounds.equals(right.bounds) &&
157      left.pixel_bounds.equals(right.pixel_bounds) &&
158      left.dip_to_pixel_scale == right.dip_to_pixel_scale;
159}
160
161bool MacDesktopConfiguration::Equals(const MacDesktopConfiguration& other) {
162  return bounds.equals(other.bounds) &&
163      pixel_bounds.equals(other.pixel_bounds) &&
164      dip_to_pixel_scale == other.dip_to_pixel_scale &&
165      displays == other.displays;
166}
167
168// Finds the display configuration with the specified id.
169const MacDisplayConfiguration*
170MacDesktopConfiguration::FindDisplayConfigurationById(
171    CGDirectDisplayID id) {
172  for (MacDisplayConfigurations::const_iterator it = displays.begin();
173      it != displays.end(); ++it) {
174    if (it->id == id)
175      return &(*it);
176  }
177  return NULL;
178}
179
180}  // namespace webrtc
181