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