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