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