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 "modules/desktop_capture/mac/desktop_configuration.h"
12
13#include <math.h>
14#include <algorithm>
15#include <Cocoa/Cocoa.h>
16
17namespace webrtc {
18
19namespace {
20
21DesktopRect NSRectToDesktopRect(const NSRect& ns_rect) {
22  return DesktopRect::MakeLTRB(
23      static_cast<int>(floor(ns_rect.origin.x)),
24      static_cast<int>(floor(ns_rect.origin.y)),
25      static_cast<int>(ceil(ns_rect.origin.x + ns_rect.size.width)),
26      static_cast<int>(ceil(ns_rect.origin.y + ns_rect.size.height)));
27}
28
29// Inverts the position of |rect| from bottom-up coordinates to top-down,
30// relative to |bounds|.
31void InvertRectYOrigin(const DesktopRect& bounds,
32                       DesktopRect* rect) {
33  assert(bounds.top() == 0);
34  *rect = DesktopRect::MakeXYWH(
35      rect->left(), bounds.bottom() - rect->bottom(),
36      rect->width(), rect->height());
37}
38
39MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) {
40  MacDisplayConfiguration display_config;
41
42  // Fetch the NSScreenNumber, which is also the CGDirectDisplayID.
43  NSDictionary* device_description = [screen deviceDescription];
44  display_config.id = static_cast<CGDirectDisplayID>(
45      [[device_description objectForKey:@"NSScreenNumber"] intValue]);
46
47  // Determine the display's logical & physical dimensions.
48  NSRect ns_bounds = [screen frame];
49  display_config.bounds = NSRectToDesktopRect(ns_bounds);
50
51  // If the host is running Mac OS X 10.7+ or later, query the scaling factor
52  // between logical and physical (aka "backing") pixels, otherwise assume 1:1.
53  if ([screen respondsToSelector:@selector(backingScaleFactor)] &&
54      [screen respondsToSelector:@selector(convertRectToBacking:)]) {
55    display_config.dip_to_pixel_scale = [screen backingScaleFactor];
56    NSRect ns_pixel_bounds = [screen convertRectToBacking: ns_bounds];
57    display_config.pixel_bounds = NSRectToDesktopRect(ns_pixel_bounds);
58  } else {
59    display_config.pixel_bounds = display_config.bounds;
60  }
61
62  // Determine if the display is built-in or external.
63  display_config.is_builtin = CGDisplayIsBuiltin(display_config.id);
64
65  return display_config;
66}
67
68}  // namespace
69
70MacDisplayConfiguration::MacDisplayConfiguration() = default;
71MacDisplayConfiguration::MacDisplayConfiguration(
72    const MacDisplayConfiguration& other) = default;
73MacDisplayConfiguration::MacDisplayConfiguration(
74    MacDisplayConfiguration&& other) = default;
75MacDisplayConfiguration::~MacDisplayConfiguration() = default;
76
77MacDisplayConfiguration& MacDisplayConfiguration::operator=(
78    const MacDisplayConfiguration& other) = default;
79MacDisplayConfiguration& MacDisplayConfiguration::operator=(
80    MacDisplayConfiguration&& other) = default;
81
82MacDesktopConfiguration::MacDesktopConfiguration() = default;
83MacDesktopConfiguration::MacDesktopConfiguration(
84    const MacDesktopConfiguration& other) = default;
85MacDesktopConfiguration::MacDesktopConfiguration(
86    MacDesktopConfiguration&& other) = default;
87MacDesktopConfiguration::~MacDesktopConfiguration() = default;
88
89MacDesktopConfiguration& MacDesktopConfiguration::operator=(
90    const MacDesktopConfiguration& other) = default;
91MacDesktopConfiguration& MacDesktopConfiguration::operator=(
92    MacDesktopConfiguration&& other) = default;
93
94// static
95MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) {
96  MacDesktopConfiguration desktop_config;
97
98  NSArray* screens = [NSScreen screens];
99  assert(screens);
100
101  // Iterator over the monitors, adding the primary monitor and monitors whose
102  // DPI match that of the primary monitor.
103  for (NSUInteger i = 0; i < [screens count]; ++i) {
104    MacDisplayConfiguration display_config =
105        GetConfigurationForScreen([screens objectAtIndex: i]);
106
107    if (i == 0)
108      desktop_config.dip_to_pixel_scale = display_config.dip_to_pixel_scale;
109
110    // Cocoa uses bottom-up coordinates, so if the caller wants top-down then
111    // we need to invert the positions of secondary monitors relative to the
112    // primary one (the primary monitor's position is (0,0) in both systems).
113    if (i > 0 && origin == TopLeftOrigin) {
114      InvertRectYOrigin(desktop_config.displays[0].bounds,
115                        &display_config.bounds);
116      // |display_bounds| is density dependent, so we need to convert the
117      // primay monitor's position into the secondary monitor's density context.
118      float scaling_factor = display_config.dip_to_pixel_scale /
119          desktop_config.displays[0].dip_to_pixel_scale;
120      DesktopRect primary_bounds = DesktopRect::MakeLTRB(
121          desktop_config.displays[0].pixel_bounds.left() * scaling_factor,
122          desktop_config.displays[0].pixel_bounds.top() * scaling_factor,
123          desktop_config.displays[0].pixel_bounds.right() * scaling_factor,
124          desktop_config.displays[0].pixel_bounds.bottom() * scaling_factor);
125      InvertRectYOrigin(primary_bounds, &display_config.pixel_bounds);
126    }
127
128    // Add the display to the configuration.
129    desktop_config.displays.push_back(display_config);
130
131    // Update the desktop bounds to account for this display, unless the current
132    // display uses different DPI settings.
133    if (display_config.dip_to_pixel_scale ==
134        desktop_config.dip_to_pixel_scale) {
135      desktop_config.bounds.UnionWith(display_config.bounds);
136      desktop_config.pixel_bounds.UnionWith(display_config.pixel_bounds);
137    }
138  }
139
140  return desktop_config;
141}
142
143// For convenience of comparing MacDisplayConfigurations in
144// MacDesktopConfiguration::Equals.
145bool operator==(const MacDisplayConfiguration& left,
146                const MacDisplayConfiguration& right) {
147  return left.id == right.id &&
148      left.bounds.equals(right.bounds) &&
149      left.pixel_bounds.equals(right.pixel_bounds) &&
150      left.dip_to_pixel_scale == right.dip_to_pixel_scale;
151}
152
153bool MacDesktopConfiguration::Equals(const MacDesktopConfiguration& other) {
154  return bounds.equals(other.bounds) &&
155      pixel_bounds.equals(other.pixel_bounds) &&
156      dip_to_pixel_scale == other.dip_to_pixel_scale &&
157      displays == other.displays;
158}
159
160const MacDisplayConfiguration*
161MacDesktopConfiguration::FindDisplayConfigurationById(
162    CGDirectDisplayID id) {
163  bool is_builtin = CGDisplayIsBuiltin(id);
164  for (MacDisplayConfigurations::const_iterator it = displays.begin();
165      it != displays.end(); ++it) {
166    // The MBP having both discrete and integrated graphic cards will do
167    // automate graphics switching by default. When it switches from discrete to
168    // integrated one, the current display ID of the built-in display will
169    // change and this will cause screen capture stops.
170    // So make screen capture of built-in display continuing even if its display
171    // ID is changed.
172    if ((is_builtin && it->is_builtin) || (!is_builtin && it->id == id)) return &(*it);
173  }
174  return NULL;
175}
176
177}  // namespace webrtc
178