diff options
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/mac')
16 files changed, 2384 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.h new file mode 100644 index 0000000000..2ad5474e44 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// Describes the configuration of a specific display. +struct MacDisplayConfiguration { + MacDisplayConfiguration(); + MacDisplayConfiguration(const MacDisplayConfiguration& other); + MacDisplayConfiguration(MacDisplayConfiguration&& other); + ~MacDisplayConfiguration(); + + MacDisplayConfiguration& operator=(const MacDisplayConfiguration& other); + MacDisplayConfiguration& operator=(MacDisplayConfiguration&& other); + + // Cocoa identifier for this display. + CGDirectDisplayID id = 0; + + // Bounds of this display in Density-Independent Pixels (DIPs). + DesktopRect bounds; + + // Bounds of this display in physical pixels. + DesktopRect pixel_bounds; + + // Scale factor from DIPs to physical pixels. + float dip_to_pixel_scale = 1.0f; + + // Display type, built-in or external. + bool is_builtin; +}; + +typedef std::vector<MacDisplayConfiguration> MacDisplayConfigurations; + +// Describes the configuration of the whole desktop. +struct RTC_EXPORT MacDesktopConfiguration { + // Used to request bottom-up or top-down coordinates. + enum Origin { BottomLeftOrigin, TopLeftOrigin }; + + MacDesktopConfiguration(); + MacDesktopConfiguration(const MacDesktopConfiguration& other); + MacDesktopConfiguration(MacDesktopConfiguration&& other); + ~MacDesktopConfiguration(); + + MacDesktopConfiguration& operator=(const MacDesktopConfiguration& other); + MacDesktopConfiguration& operator=(MacDesktopConfiguration&& other); + + // Returns the desktop & display configurations. + // If BottomLeftOrigin is used, the output is in Cocoa-style "bottom-up" + // (the origin is the bottom-left of the primary monitor, and coordinates + // increase as you move up the screen). Otherwise, the configuration will be + // converted to follow top-left coordinate system as Windows and X11. + static MacDesktopConfiguration GetCurrent(Origin origin); + + // Returns true if the given desktop configuration equals this one. + bool Equals(const MacDesktopConfiguration& other); + + // If `id` corresponds to the built-in display, return its configuration, + // otherwise return the configuration for the display with the specified id, + // or nullptr if no such display exists. + const MacDisplayConfiguration* FindDisplayConfigurationById( + CGDirectDisplayID id); + + // Bounds of the desktop excluding monitors with DPI settings different from + // the main monitor. In Density-Independent Pixels (DIPs). + DesktopRect bounds; + + // Same as bounds, but expressed in physical pixels. + DesktopRect pixel_bounds; + + // Scale factor from DIPs to physical pixels. + float dip_to_pixel_scale = 1.0f; + + // Configurations of the displays making up the desktop area. + MacDisplayConfigurations displays; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm new file mode 100644 index 0000000000..93fb3f6226 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_configuration.h" + +#include <math.h> +#include <algorithm> +#include <Cocoa/Cocoa.h> + +#include "rtc_base/checks.h" + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 + +@interface NSScreen (LionAPI) +- (CGFloat)backingScaleFactor; +- (NSRect)convertRectToBacking:(NSRect)aRect; +@end + +#endif // MAC_OS_X_VERSION_10_7 + +namespace webrtc { + +namespace { + +DesktopRect NSRectToDesktopRect(const NSRect& ns_rect) { + return DesktopRect::MakeLTRB( + static_cast<int>(floor(ns_rect.origin.x)), + static_cast<int>(floor(ns_rect.origin.y)), + static_cast<int>(ceil(ns_rect.origin.x + ns_rect.size.width)), + static_cast<int>(ceil(ns_rect.origin.y + ns_rect.size.height))); +} + +// Inverts the position of `rect` from bottom-up coordinates to top-down, +// relative to `bounds`. +void InvertRectYOrigin(const DesktopRect& bounds, + DesktopRect* rect) { + RTC_DCHECK_EQ(bounds.top(), 0); + *rect = DesktopRect::MakeXYWH( + rect->left(), bounds.bottom() - rect->bottom(), + rect->width(), rect->height()); +} + +MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) { + MacDisplayConfiguration display_config; + + // Fetch the NSScreenNumber, which is also the CGDirectDisplayID. + NSDictionary* device_description = [screen deviceDescription]; + display_config.id = static_cast<CGDirectDisplayID>( + [[device_description objectForKey:@"NSScreenNumber"] intValue]); + + // Determine the display's logical & physical dimensions. + NSRect ns_bounds = [screen frame]; + display_config.bounds = NSRectToDesktopRect(ns_bounds); + + // If the host is running Mac OS X 10.7+ or later, query the scaling factor + // between logical and physical (aka "backing") pixels, otherwise assume 1:1. + if ([screen respondsToSelector:@selector(backingScaleFactor)] && + [screen respondsToSelector:@selector(convertRectToBacking:)]) { + display_config.dip_to_pixel_scale = [screen backingScaleFactor]; + NSRect ns_pixel_bounds = [screen convertRectToBacking: ns_bounds]; + display_config.pixel_bounds = NSRectToDesktopRect(ns_pixel_bounds); + } else { + display_config.pixel_bounds = display_config.bounds; + } + + // Determine if the display is built-in or external. + display_config.is_builtin = CGDisplayIsBuiltin(display_config.id); + + return display_config; +} + +} // namespace + +MacDisplayConfiguration::MacDisplayConfiguration() = default; +MacDisplayConfiguration::MacDisplayConfiguration( + const MacDisplayConfiguration& other) = default; +MacDisplayConfiguration::MacDisplayConfiguration( + MacDisplayConfiguration&& other) = default; +MacDisplayConfiguration::~MacDisplayConfiguration() = default; + +MacDisplayConfiguration& MacDisplayConfiguration::operator=( + const MacDisplayConfiguration& other) = default; +MacDisplayConfiguration& MacDisplayConfiguration::operator=( + MacDisplayConfiguration&& other) = default; + +MacDesktopConfiguration::MacDesktopConfiguration() = default; +MacDesktopConfiguration::MacDesktopConfiguration( + const MacDesktopConfiguration& other) = default; +MacDesktopConfiguration::MacDesktopConfiguration( + MacDesktopConfiguration&& other) = default; +MacDesktopConfiguration::~MacDesktopConfiguration() = default; + +MacDesktopConfiguration& MacDesktopConfiguration::operator=( + const MacDesktopConfiguration& other) = default; +MacDesktopConfiguration& MacDesktopConfiguration::operator=( + MacDesktopConfiguration&& other) = default; + +// static +MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) { + MacDesktopConfiguration desktop_config; + + NSArray* screens = [NSScreen screens]; + RTC_DCHECK(screens); + + // Iterator over the monitors, adding the primary monitor and monitors whose + // DPI match that of the primary monitor. + for (NSUInteger i = 0; i < [screens count]; ++i) { + MacDisplayConfiguration display_config = + GetConfigurationForScreen([screens objectAtIndex: i]); + + if (i == 0) + desktop_config.dip_to_pixel_scale = display_config.dip_to_pixel_scale; + + // Cocoa uses bottom-up coordinates, so if the caller wants top-down then + // we need to invert the positions of secondary monitors relative to the + // primary one (the primary monitor's position is (0,0) in both systems). + if (i > 0 && origin == TopLeftOrigin) { + InvertRectYOrigin(desktop_config.displays[0].bounds, + &display_config.bounds); + // `display_bounds` is density dependent, so we need to convert the + // primay monitor's position into the secondary monitor's density context. + float scaling_factor = display_config.dip_to_pixel_scale / + desktop_config.displays[0].dip_to_pixel_scale; + DesktopRect primary_bounds = DesktopRect::MakeLTRB( + desktop_config.displays[0].pixel_bounds.left() * scaling_factor, + desktop_config.displays[0].pixel_bounds.top() * scaling_factor, + desktop_config.displays[0].pixel_bounds.right() * scaling_factor, + desktop_config.displays[0].pixel_bounds.bottom() * scaling_factor); + InvertRectYOrigin(primary_bounds, &display_config.pixel_bounds); + } + + // Add the display to the configuration. + desktop_config.displays.push_back(display_config); + + // Update the desktop bounds to account for this display, unless the current + // display uses different DPI settings. + if (display_config.dip_to_pixel_scale == + desktop_config.dip_to_pixel_scale) { + desktop_config.bounds.UnionWith(display_config.bounds); + desktop_config.pixel_bounds.UnionWith(display_config.pixel_bounds); + } + } + + return desktop_config; +} + +// For convenience of comparing MacDisplayConfigurations in +// MacDesktopConfiguration::Equals. +bool operator==(const MacDisplayConfiguration& left, + const MacDisplayConfiguration& right) { + return left.id == right.id && + left.bounds.equals(right.bounds) && + left.pixel_bounds.equals(right.pixel_bounds) && + left.dip_to_pixel_scale == right.dip_to_pixel_scale; +} + +bool MacDesktopConfiguration::Equals(const MacDesktopConfiguration& other) { + return bounds.equals(other.bounds) && + pixel_bounds.equals(other.pixel_bounds) && + dip_to_pixel_scale == other.dip_to_pixel_scale && + displays == other.displays; +} + +const MacDisplayConfiguration* +MacDesktopConfiguration::FindDisplayConfigurationById( + CGDirectDisplayID id) { + bool is_builtin = CGDisplayIsBuiltin(id); + for (MacDisplayConfigurations::const_iterator it = displays.begin(); + it != displays.end(); ++it) { + // The MBP having both discrete and integrated graphic cards will do + // automate graphics switching by default. When it switches from discrete to + // integrated one, the current display ID of the built-in display will + // change and this will cause screen capture stops. + // So make screen capture of built-in display continuing even if its display + // ID is changed. + if ((is_builtin && it->is_builtin) || (!is_builtin && it->id == id)) return &(*it); + } + return NULL; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc new file mode 100644 index 0000000000..048a679ecc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" + +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +DesktopConfigurationMonitor::DesktopConfigurationMonitor() { + CGError err = CGDisplayRegisterReconfigurationCallback( + DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) + RTC_LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; + MutexLock lock(&desktop_configuration_lock_); + desktop_configuration_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); +} + +DesktopConfigurationMonitor::~DesktopConfigurationMonitor() { + CGError err = CGDisplayRemoveReconfigurationCallback( + DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) + RTC_LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err; +} + +MacDesktopConfiguration DesktopConfigurationMonitor::desktop_configuration() { + MutexLock lock(&desktop_configuration_lock_); + return desktop_configuration_; +} + +// static +// This method may be called on any system thread. +void DesktopConfigurationMonitor::DisplaysReconfiguredCallback( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_parameter) { + DesktopConfigurationMonitor* monitor = + reinterpret_cast<DesktopConfigurationMonitor*>(user_parameter); + monitor->DisplaysReconfigured(display, flags); +} + +void DesktopConfigurationMonitor::DisplaysReconfigured( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags) { + TRACE_EVENT0("webrtc", "DesktopConfigurationMonitor::DisplaysReconfigured"); + RTC_LOG(LS_INFO) << "DisplaysReconfigured: " + "DisplayID " + << display << "; ChangeSummaryFlags " << flags; + + if (flags & kCGDisplayBeginConfigurationFlag) { + reconfiguring_displays_.insert(display); + return; + } + + reconfiguring_displays_.erase(display); + if (reconfiguring_displays_.empty()) { + MutexLock lock(&desktop_configuration_lock_); + desktop_configuration_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h new file mode 100644 index 0000000000..747295a538 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <memory> +#include <set> + +#include "api/ref_counted_base.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +// The class provides functions to synchronize capturing and display +// reconfiguring across threads, and the up-to-date MacDesktopConfiguration. +class DesktopConfigurationMonitor final + : public rtc::RefCountedNonVirtual<DesktopConfigurationMonitor> { + public: + DesktopConfigurationMonitor(); + ~DesktopConfigurationMonitor(); + + DesktopConfigurationMonitor(const DesktopConfigurationMonitor&) = delete; + DesktopConfigurationMonitor& operator=(const DesktopConfigurationMonitor&) = + delete; + + // Returns the current desktop configuration. + MacDesktopConfiguration desktop_configuration(); + + private: + static void DisplaysReconfiguredCallback(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_parameter); + void DisplaysReconfigured(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags); + + Mutex desktop_configuration_lock_; + MacDesktopConfiguration desktop_configuration_ + RTC_GUARDED_BY(&desktop_configuration_lock_); + std::set<CGDirectDisplayID> reconfiguring_displays_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.h new file mode 100644 index 0000000000..d6279f9b36 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_ + +#include <CoreGraphics/CoreGraphics.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +class DesktopFrameCGImage final : public DesktopFrame { + public: + // Create an image containing a snapshot of the display at the time this is + // being called. + static std::unique_ptr<DesktopFrameCGImage> CreateForDisplay( + CGDirectDisplayID display_id); + + // Create an image containing a snaphot of the given window at the time this + // is being called. This also works when the window is overlapped or in + // another workspace. + static std::unique_ptr<DesktopFrameCGImage> CreateForWindow( + CGWindowID window_id); + + ~DesktopFrameCGImage() override; + + DesktopFrameCGImage(const DesktopFrameCGImage&) = delete; + DesktopFrameCGImage& operator=(const DesktopFrameCGImage&) = delete; + + private: + static std::unique_ptr<DesktopFrameCGImage> CreateFromCGImage( + rtc::ScopedCFTypeRef<CGImageRef> cg_image); + + // This constructor expects `cg_image` to hold a non-null CGImageRef. + DesktopFrameCGImage(DesktopSize size, + int stride, + uint8_t* data, + rtc::ScopedCFTypeRef<CGImageRef> cg_image, + rtc::ScopedCFTypeRef<CFDataRef> cg_data); + + const rtc::ScopedCFTypeRef<CGImageRef> cg_image_; + const rtc::ScopedCFTypeRef<CFDataRef> cg_data_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm new file mode 100644 index 0000000000..0fb69b272d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_frame_cgimage.h" + +#include <AvailabilityMacros.h> + +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +// static +std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateForDisplay( + CGDirectDisplayID display_id) { + // Create an image containing a snapshot of the display. + rtc::ScopedCFTypeRef<CGImageRef> cg_image(CGDisplayCreateImage(display_id)); + if (!cg_image) { + return nullptr; + } + + return DesktopFrameCGImage::CreateFromCGImage(cg_image); +} + +// static +std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateForWindow(CGWindowID window_id) { + rtc::ScopedCFTypeRef<CGImageRef> cg_image( + CGWindowListCreateImage(CGRectNull, + kCGWindowListOptionIncludingWindow, + window_id, + kCGWindowImageBoundsIgnoreFraming)); + if (!cg_image) { + return nullptr; + } + + return DesktopFrameCGImage::CreateFromCGImage(cg_image); +} + +// static +std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateFromCGImage( + rtc::ScopedCFTypeRef<CGImageRef> cg_image) { + // Verify that the image has 32-bit depth. + int bits_per_pixel = CGImageGetBitsPerPixel(cg_image.get()); + if (bits_per_pixel / 8 != DesktopFrame::kBytesPerPixel) { + RTC_LOG(LS_ERROR) << "CGDisplayCreateImage() returned imaged with " << bits_per_pixel + << " bits per pixel. Only 32-bit depth is supported."; + return nullptr; + } + + // Request access to the raw pixel data via the image's DataProvider. + CGDataProviderRef cg_provider = CGImageGetDataProvider(cg_image.get()); + RTC_DCHECK(cg_provider); + + // CGDataProviderCopyData returns a new data object containing a copy of the provider’s + // data. + rtc::ScopedCFTypeRef<CFDataRef> cg_data(CGDataProviderCopyData(cg_provider)); + RTC_DCHECK(cg_data); + + // CFDataGetBytePtr returns a read-only pointer to the bytes of a CFData object. + uint8_t* data = const_cast<uint8_t*>(CFDataGetBytePtr(cg_data.get())); + RTC_DCHECK(data); + + DesktopSize size(CGImageGetWidth(cg_image.get()), CGImageGetHeight(cg_image.get())); + int stride = CGImageGetBytesPerRow(cg_image.get()); + + std::unique_ptr<DesktopFrameCGImage> frame( + new DesktopFrameCGImage(size, stride, data, cg_image, cg_data)); + + CGColorSpaceRef cg_color_space = CGImageGetColorSpace(cg_image.get()); + if (cg_color_space) { +#if !defined(MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_13 + rtc::ScopedCFTypeRef<CFDataRef> cf_icc_profile(CGColorSpaceCopyICCProfile(cg_color_space)); +#else + rtc::ScopedCFTypeRef<CFDataRef> cf_icc_profile(CGColorSpaceCopyICCData(cg_color_space)); +#endif + if (cf_icc_profile) { + const uint8_t* data_as_byte = + reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(cf_icc_profile.get())); + const size_t data_size = CFDataGetLength(cf_icc_profile.get()); + if (data_as_byte && data_size > 0) { + frame->set_icc_profile(std::vector<uint8_t>(data_as_byte, data_as_byte + data_size)); + } + } + } + + return frame; +} + +DesktopFrameCGImage::DesktopFrameCGImage(DesktopSize size, + int stride, + uint8_t* data, + rtc::ScopedCFTypeRef<CGImageRef> cg_image, + rtc::ScopedCFTypeRef<CFDataRef> cg_data) + : DesktopFrame(size, stride, data, nullptr), cg_image_(cg_image), cg_data_(cg_data) { + RTC_DCHECK(cg_image_); + RTC_DCHECK(cg_data_); +} + +DesktopFrameCGImage::~DesktopFrameCGImage() = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.h new file mode 100644 index 0000000000..73da0f693c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_ + +#include <CoreGraphics/CoreGraphics.h> +#include <IOSurface/IOSurface.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +class DesktopFrameIOSurface final : public DesktopFrame { + public: + // Lock an IOSurfaceRef containing a snapshot of a display. Return NULL if + // failed to lock. + static std::unique_ptr<DesktopFrameIOSurface> Wrap( + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface); + + ~DesktopFrameIOSurface() override; + + DesktopFrameIOSurface(const DesktopFrameIOSurface&) = delete; + DesktopFrameIOSurface& operator=(const DesktopFrameIOSurface&) = delete; + + private: + // This constructor expects `io_surface` to hold a non-null IOSurfaceRef. + explicit DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface); + + const rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm new file mode 100644 index 0000000000..b59b319db9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_frame_iosurface.h" + +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +// static +std::unique_ptr<DesktopFrameIOSurface> DesktopFrameIOSurface::Wrap( + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) { + if (!io_surface) { + return nullptr; + } + + IOSurfaceIncrementUseCount(io_surface.get()); + IOReturn status = IOSurfaceLock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr); + if (status != kIOReturnSuccess) { + RTC_LOG(LS_ERROR) << "Failed to lock the IOSurface with status " << status; + IOSurfaceDecrementUseCount(io_surface.get()); + return nullptr; + } + + // Verify that the image has 32-bit depth. + int bytes_per_pixel = IOSurfaceGetBytesPerElement(io_surface.get()); + if (bytes_per_pixel != DesktopFrame::kBytesPerPixel) { + RTC_LOG(LS_ERROR) << "CGDisplayStream handler returned IOSurface with " << (8 * bytes_per_pixel) + << " bits per pixel. Only 32-bit depth is supported."; + IOSurfaceUnlock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr); + IOSurfaceDecrementUseCount(io_surface.get()); + return nullptr; + } + + return std::unique_ptr<DesktopFrameIOSurface>(new DesktopFrameIOSurface(io_surface)); +} + +DesktopFrameIOSurface::DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) + : DesktopFrame( + DesktopSize(IOSurfaceGetWidth(io_surface.get()), IOSurfaceGetHeight(io_surface.get())), + IOSurfaceGetBytesPerRow(io_surface.get()), + static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface.get())), + nullptr), + io_surface_(io_surface) { + RTC_DCHECK(io_surface_); +} + +DesktopFrameIOSurface::~DesktopFrameIOSurface() { + IOSurfaceUnlock(io_surface_.get(), kIOSurfaceLockReadOnly, nullptr); + IOSurfaceDecrementUseCount(io_surface_.get()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.h new file mode 100644 index 0000000000..aad28d2f30 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_ + +#include <CoreGraphics/CoreGraphics.h> +#include <IOSurface/IOSurface.h> + +#include <map> +#include <memory> + +#include "api/sequence_checker.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +class DesktopFrameProvider { + public: + explicit DesktopFrameProvider(bool allow_iosurface); + ~DesktopFrameProvider(); + + DesktopFrameProvider(const DesktopFrameProvider&) = delete; + DesktopFrameProvider& operator=(const DesktopFrameProvider&) = delete; + + // The caller takes ownership of the returned desktop frame. Otherwise + // returns null if `display_id` is invalid or not ready. Note that this + // function does not remove the frame from the internal container. Caller + // has to call the Release function. + std::unique_ptr<DesktopFrame> TakeLatestFrameForDisplay( + CGDirectDisplayID display_id); + + // OS sends the latest IOSurfaceRef through + // CGDisplayStreamFrameAvailableHandler callback; we store it here. + void InvalidateIOSurface(CGDirectDisplayID display_id, + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface); + + // Expected to be called before stopping the CGDisplayStreamRef streams. + void Release(); + + private: + SequenceChecker thread_checker_; + const bool allow_iosurface_; + + // Most recent IOSurface that contains a capture of matching display. + std::map<CGDirectDisplayID, std::unique_ptr<SharedDesktopFrame>> io_surfaces_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm new file mode 100644 index 0000000000..009504a22b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_frame_provider.h" + +#include <utility> + +#include "modules/desktop_capture/mac/desktop_frame_cgimage.h" +#include "modules/desktop_capture/mac/desktop_frame_iosurface.h" + +namespace webrtc { + +DesktopFrameProvider::DesktopFrameProvider(bool allow_iosurface) + : allow_iosurface_(allow_iosurface) { + thread_checker_.Detach(); +} + +DesktopFrameProvider::~DesktopFrameProvider() { + RTC_DCHECK(thread_checker_.IsCurrent()); + + Release(); +} + +std::unique_ptr<DesktopFrame> DesktopFrameProvider::TakeLatestFrameForDisplay( + CGDirectDisplayID display_id) { + RTC_DCHECK(thread_checker_.IsCurrent()); + + if (!allow_iosurface_ || !io_surfaces_[display_id]) { + // Regenerate a snapshot. If iosurface is on it will be empty until the + // stream handler is called. + return DesktopFrameCGImage::CreateForDisplay(display_id); + } + + return io_surfaces_[display_id]->Share(); +} + +void DesktopFrameProvider::InvalidateIOSurface(CGDirectDisplayID display_id, + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) { + RTC_DCHECK(thread_checker_.IsCurrent()); + + if (!allow_iosurface_) { + return; + } + + std::unique_ptr<DesktopFrameIOSurface> desktop_frame_iosurface = + DesktopFrameIOSurface::Wrap(io_surface); + + io_surfaces_[display_id] = desktop_frame_iosurface ? + SharedDesktopFrame::Wrap(std::move(desktop_frame_iosurface)) : + nullptr; +} + +void DesktopFrameProvider::Release() { + RTC_DCHECK(thread_checker_.IsCurrent()); + + if (!allow_iosurface_) { + return; + } + + io_surfaces_.clear(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc new file mode 100644 index 0000000000..45cd3223d2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h" + +#include <libproc.h> + +#include <algorithm> +#include <functional> +#include <string> + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "api/function_view.h" +#include "modules/desktop_capture/mac/window_list_utils.h" + +namespace webrtc { +namespace { + +static constexpr const char* kPowerPointSlideShowTitles[] = { + "PowerPoint-Bildschirmpräsentation", + "Προβολή παρουσίασης PowerPoint", + "PowerPoint スライド ショー", + "PowerPoint Slide Show", + "PowerPoint 幻灯片放映", + "Presentación de PowerPoint", + "PowerPoint-slideshow", + "Presentazione di PowerPoint", + "Prezentácia programu PowerPoint", + "Apresentação do PowerPoint", + "PowerPoint-bildspel", + "Prezentace v aplikaci PowerPoint", + "PowerPoint 슬라이드 쇼", + "PowerPoint-lysbildefremvisning", + "PowerPoint-vetítés", + "PowerPoint Slayt Gösterisi", + "Pokaz slajdów programu PowerPoint", + "PowerPoint 投影片放映", + "Демонстрация PowerPoint", + "Diaporama PowerPoint", + "PowerPoint-diaesitys", + "Peragaan Slide PowerPoint", + "PowerPoint-diavoorstelling", + "การนำเสนอสไลด์ PowerPoint", + "Apresentação de slides do PowerPoint", + "הצגת שקופיות של PowerPoint", + "عرض شرائح في PowerPoint"}; + +class FullScreenMacApplicationHandler : public FullScreenApplicationHandler { + public: + using TitlePredicate = + std::function<bool(absl::string_view, absl::string_view)>; + + FullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId, + TitlePredicate title_predicate, + bool ignore_original_window) + : FullScreenApplicationHandler(sourceId), + title_predicate_(title_predicate), + owner_pid_(GetWindowOwnerPid(sourceId)), + ignore_original_window_(ignore_original_window) {} + + protected: + using CachePredicate = + rtc::FunctionView<bool(const DesktopCapturer::Source&)>; + + void InvalidateCacheIfNeeded(const DesktopCapturer::SourceList& source_list, + int64_t timestamp, + CachePredicate predicate) const { + if (timestamp != cache_timestamp_) { + cache_sources_.clear(); + std::copy_if(source_list.begin(), source_list.end(), + std::back_inserter(cache_sources_), predicate); + cache_timestamp_ = timestamp; + } + } + + WindowId FindFullScreenWindowWithSamePid( + const DesktopCapturer::SourceList& source_list, + int64_t timestamp) const { + InvalidateCacheIfNeeded(source_list, timestamp, + [&](const DesktopCapturer::Source& src) { + return src.id != GetSourceId() && + GetWindowOwnerPid(src.id) == owner_pid_; + }); + if (cache_sources_.empty()) + return kCGNullWindowID; + + const auto original_window = GetSourceId(); + const std::string title = GetWindowTitle(original_window); + + // We can ignore any windows with empty titles cause regardless type of + // application it's impossible to verify that full screen window and + // original window are related to the same document. + if (title.empty()) + return kCGNullWindowID; + + MacDesktopConfiguration desktop_config = + MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + const auto it = std::find_if( + cache_sources_.begin(), cache_sources_.end(), + [&](const DesktopCapturer::Source& src) { + const std::string window_title = GetWindowTitle(src.id); + + if (window_title.empty()) + return false; + + if (title_predicate_ && !title_predicate_(title, window_title)) + return false; + + return IsWindowFullScreen(desktop_config, src.id); + }); + + return it != cache_sources_.end() ? it->id : 0; + } + + DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& source_list, + int64_t timestamp) const override { + return !ignore_original_window_ && IsWindowOnScreen(GetSourceId()) + ? 0 + : FindFullScreenWindowWithSamePid(source_list, timestamp); + } + + protected: + const TitlePredicate title_predicate_; + const int owner_pid_; + const bool ignore_original_window_; + mutable int64_t cache_timestamp_ = 0; + mutable DesktopCapturer::SourceList cache_sources_; +}; + +bool equal_title_predicate(absl::string_view original_title, + absl::string_view title) { + return original_title == title; +} + +bool slide_show_title_predicate(absl::string_view original_title, + absl::string_view title) { + if (title.find(original_title) == absl::string_view::npos) + return false; + + for (const char* pp_slide_title : kPowerPointSlideShowTitles) { + if (absl::StartsWith(title, pp_slide_title)) + return true; + } + return false; +} + +class OpenOfficeApplicationHandler : public FullScreenMacApplicationHandler { + public: + OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId) + : FullScreenMacApplicationHandler(sourceId, nullptr, false) {} + + DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& source_list, + int64_t timestamp) const override { + InvalidateCacheIfNeeded(source_list, timestamp, + [&](const DesktopCapturer::Source& src) { + return GetWindowOwnerPid(src.id) == owner_pid_; + }); + + const auto original_window = GetSourceId(); + const std::string original_title = GetWindowTitle(original_window); + + // Check if we have only one document window, otherwise it's not possible + // to securely match a document window and a slide show window which has + // empty title. + if (std::any_of(cache_sources_.begin(), cache_sources_.end(), + [&original_title](const DesktopCapturer::Source& src) { + return src.title.length() && src.title != original_title; + })) { + return kCGNullWindowID; + } + + MacDesktopConfiguration desktop_config = + MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + // Looking for slide show window, + // it must be a full screen window with empty title + const auto slide_show_window = std::find_if( + cache_sources_.begin(), cache_sources_.end(), [&](const auto& src) { + return src.title.empty() && + IsWindowFullScreen(desktop_config, src.id); + }); + + if (slide_show_window == cache_sources_.end()) { + return kCGNullWindowID; + } + + return slide_show_window->id; + } +}; + +} // namespace + +std::unique_ptr<FullScreenApplicationHandler> +CreateFullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId) { + std::unique_ptr<FullScreenApplicationHandler> result; + int pid = GetWindowOwnerPid(sourceId); + char buffer[PROC_PIDPATHINFO_MAXSIZE]; + int path_length = proc_pidpath(pid, buffer, sizeof(buffer)); + if (path_length > 0) { + const char* last_slash = strrchr(buffer, '/'); + const std::string name{last_slash ? last_slash + 1 : buffer}; + const std::string owner_name = GetWindowOwnerName(sourceId); + FullScreenMacApplicationHandler::TitlePredicate predicate = nullptr; + bool ignore_original_window = false; + if (name.find("Google Chrome") == 0 || name == "Chromium") { + predicate = equal_title_predicate; + } else if (name == "Microsoft PowerPoint") { + predicate = slide_show_title_predicate; + ignore_original_window = true; + } else if (name == "Keynote") { + predicate = equal_title_predicate; + } else if (owner_name == "OpenOffice") { + return std::make_unique<OpenOfficeApplicationHandler>(sourceId); + } + + if (predicate) { + result.reset(new FullScreenMacApplicationHandler(sourceId, predicate, + ignore_original_window)); + } + } + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.h b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.h new file mode 100644 index 0000000000..f795a22030 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_MAC_APPLICATION_HANDLER_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_MAC_APPLICATION_HANDLER_H_ + +#include <memory> +#include "modules/desktop_capture/full_screen_application_handler.h" + +namespace webrtc { + +std::unique_ptr<FullScreenApplicationHandler> +CreateFullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_MAC_APPLICATION_HANDLER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.h b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.h new file mode 100644 index 0000000000..7be05cc639 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_ + +#include <CoreGraphics/CoreGraphics.h> + +#include <memory> +#include <vector> + +#include "api/sequence_checker.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "modules/desktop_capture/mac/desktop_frame_provider.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/screen_capturer_helper.h" +#include "modules/desktop_capture/shared_desktop_frame.h" + +namespace webrtc { + +class DisplayStreamManager; + +// A class to perform video frame capturing for mac. +class ScreenCapturerMac final : public DesktopCapturer { + public: + ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor, + bool detect_updated_region, + bool allow_iosurface); + ~ScreenCapturerMac() override; + + ScreenCapturerMac(const ScreenCapturerMac&) = delete; + ScreenCapturerMac& operator=(const ScreenCapturerMac&) = delete; + + // TODO(julien.isorce): Remove Init() or make it private. + bool Init(); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + + private: + // Returns false if the selected screen is no longer valid. + bool CgBlit(const DesktopFrame& frame, const DesktopRegion& region); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + bool RegisterRefreshAndMoveHandlers(); + void UnregisterRefreshAndMoveHandlers(); + + void ScreenRefresh(CGDirectDisplayID display_id, + CGRectCount count, + const CGRect* rect_array, + DesktopVector display_origin, + IOSurfaceRef io_surface); + void ReleaseBuffers(); + + std::unique_ptr<DesktopFrame> CreateFrame(); + + const bool detect_updated_region_; + + Callback* callback_ = nullptr; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; + + // Current display configuration. + MacDesktopConfiguration desktop_config_; + + // Currently selected display, or 0 if the full desktop is selected. On OS X + // 10.6 and before, this is always 0. + CGDirectDisplayID current_display_ = 0; + + // The physical pixel bounds of the current screen. + DesktopRect screen_pixel_bounds_; + + // The dip to physical pixel scale of the current screen. + float dip_to_pixel_scale_ = 1.0f; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Contains an invalid region from the previous capture. + DesktopRegion last_invalid_region_; + + // Monitoring display reconfiguration. + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor_; + + CGWindowID excluded_window_ = 0; + + // List of streams, one per screen. + std::vector<CGDisplayStreamRef> display_streams_; + + // Container holding latest state of the snapshot per displays. + DesktopFrameProvider desktop_frame_provider_; + + // Start, CaptureFrame and destructor have to called in the same thread. + SequenceChecker thread_checker_; + + // Used to force CaptureFrame to update it's screen configuration + // and reregister event handlers. This ensure that this + // occurs on the ScreenCapture thread. Read and written from + // both the VideoCapture thread and ScreenCapture thread. + // Protected by desktop_config_monitor_. + bool update_screen_configuration_ = false; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm new file mode 100644 index 0000000000..76fec13a39 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <utility> + +#include "modules/desktop_capture/mac/screen_capturer_mac.h" + +#include "modules/desktop_capture/mac/desktop_frame_provider.h" +#include "modules/desktop_capture/mac/window_list_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +// All these symbols have incorrect availability annotations in the 13.3 SDK. +// These have the correct annotation. See https://crbug.com/1431897. +// TODO(thakis): Remove this once FB12109479 is fixed and we updated to an SDK +// with the fix. + +static CGDisplayStreamRef __nullable + wrapCGDisplayStreamCreate(CGDirectDisplayID display, + size_t outputWidth, + size_t outputHeight, + int32_t pixelFormat, + CFDictionaryRef __nullable properties, + CGDisplayStreamFrameAvailableHandler __nullable handler) + CG_AVAILABLE_BUT_DEPRECATED( + 10.8, + 14.0, + "Please use ScreenCaptureKit API's initWithFilter:configuration:delegate: instead") { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + return CGDisplayStreamCreate( + display, outputWidth, outputHeight, pixelFormat, properties, handler); +#pragma clang diagnostic pop +} + +static CFRunLoopSourceRef __nullable + wrapCGDisplayStreamGetRunLoopSource(CGDisplayStreamRef cg_nullable displayStream) + CG_AVAILABLE_BUT_DEPRECATED(10.8, + 14.0, + "There is no direct replacement for this function. Please use " + "ScreenCaptureKit API's SCStream to replace CGDisplayStream") { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + return CGDisplayStreamGetRunLoopSource(displayStream); +#pragma clang diagnostic pop +} + +static CGError wrapCGDisplayStreamStart(CGDisplayStreamRef cg_nullable displayStream) + CG_AVAILABLE_BUT_DEPRECATED(10.8, + 14.0, + "Please use ScreenCaptureKit API's " + "startCaptureWithCompletionHandler: to start a stream instead") { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + return CGDisplayStreamStart(displayStream); +#pragma clang diagnostic pop +} + +static CGError wrapCGDisplayStreamStop(CGDisplayStreamRef cg_nullable displayStream) + CG_AVAILABLE_BUT_DEPRECATED(10.8, + 14.0, + "Please use ScreenCaptureKit API's " + "stopCaptureWithCompletionHandler: to stop a stream instead") { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + return CGDisplayStreamStop(displayStream); +#pragma clang diagnostic pop +} + +static CFStringRef wrapkCGDisplayStreamShowCursor() CG_AVAILABLE_BUT_DEPRECATED( + 10.8, + 14.0, + "Please use ScreenCaptureKit API's SCStreamConfiguration showsCursor property instead") { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + return kCGDisplayStreamShowCursor; +#pragma clang diagnostic pop +} + +static const CGRect* __nullable + wrapCGDisplayStreamUpdateGetRects(CGDisplayStreamUpdateRef __nullable updateRef, + CGDisplayStreamUpdateRectType rectType, + size_t* rectCount) + CG_AVAILABLE_BUT_DEPRECATED(10.8, + 14.0, + "Please use ScreenCaptureKit API's SCStreamFrameInfo with " + "SCStreamFrameInfoContentRect instead") { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + return CGDisplayStreamUpdateGetRects(updateRef, rectType, rectCount); +#pragma clang diagnostic pop +} + +namespace webrtc { + +namespace { + +// Scales all coordinates of a rect by a specified factor. +DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { + return DesktopRect::MakeLTRB(static_cast<int>(floor(rect.origin.x * scale)), + static_cast<int>(floor(rect.origin.y * scale)), + static_cast<int>(ceil((rect.origin.x + rect.size.width) * scale)), + static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale))); +} + +// Copy pixels in the `rect` from `src_place` to `dest_plane`. `rect` should be +// relative to the origin of `src_plane` and `dest_plane`. +void CopyRect(const uint8_t* src_plane, + int src_plane_stride, + uint8_t* dest_plane, + int dest_plane_stride, + int bytes_per_pixel, + const DesktopRect& rect) { + // Get the address of the starting point. + const int src_y_offset = src_plane_stride * rect.top(); + const int dest_y_offset = dest_plane_stride * rect.top(); + const int x_offset = bytes_per_pixel * rect.left(); + src_plane += src_y_offset + x_offset; + dest_plane += dest_y_offset + x_offset; + + // Copy pixels in the rectangle line by line. + const int bytes_per_line = bytes_per_pixel * rect.width(); + const int height = rect.height(); + for (int i = 0; i < height; ++i) { + memcpy(dest_plane, src_plane, bytes_per_line); + src_plane += src_plane_stride; + dest_plane += dest_plane_stride; + } +} + +// Returns an array of CGWindowID for all the on-screen windows except +// `window_to_exclude`, or NULL if the window is not found or it fails. The +// caller should release the returned CFArrayRef. +CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) { + if (!window_to_exclude) return nullptr; + + CFArrayRef all_windows = + CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + if (!all_windows) return nullptr; + + CFMutableArrayRef returned_array = + CFArrayCreateMutable(nullptr, CFArrayGetCount(all_windows), nullptr); + + bool found = false; + for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) { + CFDictionaryRef window = + reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(all_windows, i)); + + CGWindowID id = GetWindowId(window); + if (id == window_to_exclude) { + found = true; + continue; + } + CFArrayAppendValue(returned_array, reinterpret_cast<void*>(id)); + } + CFRelease(all_windows); + + if (!found) { + CFRelease(returned_array); + returned_array = nullptr; + } + return returned_array; +} + +// Returns the bounds of `window` in physical pixels, enlarged by a small amount +// on four edges to take account of the border/shadow effects. +DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, float dip_to_pixel_scale) { + // The amount of pixels to add to the actual window bounds to take into + // account of the border/shadow effects. + static const int kBorderEffectSize = 20; + CGRect rect; + CGWindowID ids[1]; + ids[0] = window; + + CFArrayRef window_id_array = + CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr); + CFArrayRef window_array = CGWindowListCreateDescriptionFromArray(window_id_array); + + if (CFArrayGetCount(window_array) > 0) { + CFDictionaryRef win = + reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(window_array, 0)); + CFDictionaryRef bounds_ref = + reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(win, kCGWindowBounds)); + CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect); + } + + CFRelease(window_id_array); + CFRelease(window_array); + + rect.origin.x -= kBorderEffectSize; + rect.origin.y -= kBorderEffectSize; + rect.size.width += kBorderEffectSize * 2; + rect.size.height += kBorderEffectSize * 2; + // `rect` is in DIP, so convert to physical pixels. + return ScaleAndRoundCGRect(rect, dip_to_pixel_scale); +} + +// Create an image of the given region using the given `window_list`. +// `pixel_bounds` should be in the primary display's coordinate in physical +// pixels. +rtc::ScopedCFTypeRef<CGImageRef> CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds, + float dip_to_pixel_scale, + CFArrayRef window_list) { + CGRect window_bounds; + // The origin is in DIP while the size is in physical pixels. That's what + // CGWindowListCreateImageFromArray expects. + window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale; + window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale; + window_bounds.size.width = pixel_bounds.width(); + window_bounds.size.height = pixel_bounds.height(); + + return rtc::ScopedCFTypeRef<CGImageRef>( + CGWindowListCreateImageFromArray(window_bounds, window_list, kCGWindowImageDefault)); +} + +} // namespace + +ScreenCapturerMac::ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor, + bool detect_updated_region, + bool allow_iosurface) + : detect_updated_region_(detect_updated_region), + desktop_config_monitor_(desktop_config_monitor), + desktop_frame_provider_(allow_iosurface) { + RTC_LOG(LS_INFO) << "Allow IOSurface: " << allow_iosurface; + thread_checker_.Detach(); +} + +ScreenCapturerMac::~ScreenCapturerMac() { + RTC_DCHECK(thread_checker_.IsCurrent()); + ReleaseBuffers(); + UnregisterRefreshAndMoveHandlers(); +} + +bool ScreenCapturerMac::Init() { + TRACE_EVENT0("webrtc", "ScreenCapturerMac::Init"); + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + return true; +} + +void ScreenCapturerMac::ReleaseBuffers() { + // The buffers might be in use by the encoder, so don't delete them here. + // Instead, mark them as "needs update"; next time the buffers are used by + // the capturer, they will be recreated if necessary. + queue_.Reset(); +} + +void ScreenCapturerMac::Start(Callback* callback) { + RTC_DCHECK(thread_checker_.IsCurrent()); + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + TRACE_EVENT_INSTANT1( + "webrtc", "ScreenCapturermac::Start", "target display id ", current_display_); + + callback_ = callback; + update_screen_configuration_ = false; + // Start and operate CGDisplayStream handler all from capture thread. + if (!RegisterRefreshAndMoveHandlers()) { + RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + ScreenConfigurationChanged(); +} + +void ScreenCapturerMac::CaptureFrame() { + RTC_DCHECK(thread_checker_.IsCurrent()); + TRACE_EVENT0("webrtc", "creenCapturerMac::CaptureFrame"); + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + queue_.MoveToNextFrame(); + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared."; + } + + MacDesktopConfiguration new_config = desktop_config_monitor_->desktop_configuration(); + if (update_screen_configuration_ || !desktop_config_.Equals(new_config)) { + update_screen_configuration_ = false; + desktop_config_ = new_config; + // If the display configuraiton has changed then refresh capturer data + // structures. Occasionally, the refresh and move handlers are lost when + // the screen mode changes, so re-register them here. + UnregisterRefreshAndMoveHandlers(); + if (!RegisterRefreshAndMoveHandlers()) { + RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + ScreenConfigurationChanged(); + } + + // When screen is zoomed in/out, OSX only updates the part of Rects currently + // displayed on screen, with relative location to current top-left on screen. + // This will cause problems when we copy the dirty regions to the captured + // image. So we invalidate the whole screen to copy all the screen contents. + // With CGI method, the zooming will be ignored and the whole screen contents + // will be captured as before. + // With IOSurface method, the zoomed screen contents will be captured. + if (UAZoomEnabled()) { + helper_.InvalidateScreen(screen_pixel_bounds_.size()); + } + + DesktopRegion region; + helper_.TakeInvalidRegion(®ion); + + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(CreateFrame())); + + DesktopFrame* current_frame = queue_.current_frame(); + + if (!CgBlit(*current_frame, region)) { + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share(); + if (detect_updated_region_) { + *new_frame->mutable_updated_region() = region; + } else { + new_frame->mutable_updated_region()->AddRect(DesktopRect::MakeSize(new_frame->size())); + } + + if (current_display_) { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + if (config) { + new_frame->set_top_left( + config->bounds.top_left().subtract(desktop_config_.bounds.top_left())); + } + } + + helper_.set_size_most_recent(new_frame->size()); + + new_frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec); + callback_->OnCaptureResult(Result::SUCCESS, std::move(new_frame)); +} + +void ScreenCapturerMac::SetExcludedWindow(WindowId window) { + excluded_window_ = window; +} + +bool ScreenCapturerMac::GetSourceList(SourceList* screens) { + RTC_DCHECK(screens->size() == 0); + + for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin(); + it != desktop_config_.displays.end(); + ++it) { + Source value = {it->id, 0, std::string()}; + screens->push_back(value); + } + return true; +} + +bool ScreenCapturerMac::SelectSource(SourceId id) { + if (id == kFullDesktopScreenId) { + current_display_ = 0; + } else { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(static_cast<CGDirectDisplayID>(id)); + if (!config) return false; + current_display_ = config->id; + } + + ScreenConfigurationChanged(); + return true; +} + +bool ScreenCapturerMac::CgBlit(const DesktopFrame& frame, const DesktopRegion& region) { + // If not all screen region is dirty, copy the entire contents of the previous capture buffer, + // to capture over. + if (queue_.previous_frame() && !region.Equals(DesktopRegion(screen_pixel_bounds_))) { + memcpy(frame.data(), queue_.previous_frame()->data(), frame.stride() * frame.size().height()); + } + + MacDisplayConfigurations displays_to_capture; + if (current_display_) { + // Capturing a single screen. Note that the screen id may change when + // screens are added or removed. + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + if (config) { + displays_to_capture.push_back(*config); + } else { + RTC_LOG(LS_ERROR) << "The selected screen cannot be found for capturing."; + return false; + } + } else { + // Capturing the whole desktop. + displays_to_capture = desktop_config_.displays; + } + + // Create the window list once for all displays. + CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_); + + for (size_t i = 0; i < displays_to_capture.size(); ++i) { + const MacDisplayConfiguration& display_config = displays_to_capture[i]; + + // Capturing mixed-DPI on one surface is hard, so we only return displays + // that match the "primary" display's DPI. The primary display is always + // the first in the list. + if (i > 0 && display_config.dip_to_pixel_scale != displays_to_capture[0].dip_to_pixel_scale) { + continue; + } + // Determine the display's position relative to the desktop, in pixels. + DesktopRect display_bounds = display_config.pixel_bounds; + display_bounds.Translate(-screen_pixel_bounds_.left(), -screen_pixel_bounds_.top()); + + // Determine which parts of the blit region, if any, lay within the monitor. + DesktopRegion copy_region = region; + copy_region.IntersectWith(display_bounds); + if (copy_region.is_empty()) continue; + + // Translate the region to be copied into display-relative coordinates. + copy_region.Translate(-display_bounds.left(), -display_bounds.top()); + + DesktopRect excluded_window_bounds; + rtc::ScopedCFTypeRef<CGImageRef> excluded_image; + if (excluded_window_ && window_list) { + // Get the region of the excluded window relative the primary display. + excluded_window_bounds = + GetExcludedWindowPixelBounds(excluded_window_, display_config.dip_to_pixel_scale); + excluded_window_bounds.IntersectWith(display_config.pixel_bounds); + + // Create the image under the excluded window first, because it's faster + // than captuing the whole display. + if (!excluded_window_bounds.is_empty()) { + excluded_image = CreateExcludedWindowRegionImage( + excluded_window_bounds, display_config.dip_to_pixel_scale, window_list); + } + } + + std::unique_ptr<DesktopFrame> frame_source = + desktop_frame_provider_.TakeLatestFrameForDisplay(display_config.id); + if (!frame_source) { + continue; + } + + const uint8_t* display_base_address = frame_source->data(); + int src_bytes_per_row = frame_source->stride(); + RTC_DCHECK(display_base_address); + + // `frame_source` size may be different from display_bounds in case the screen was + // resized recently. + copy_region.IntersectWith(frame_source->rect()); + + // Copy the dirty region from the display buffer into our desktop buffer. + uint8_t* out_ptr = frame.GetFrameDataAtPos(display_bounds.top_left()); + for (DesktopRegion::Iterator it(copy_region); !it.IsAtEnd(); it.Advance()) { + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + DesktopFrame::kBytesPerPixel, + it.rect()); + } + + if (excluded_image) { + CGDataProviderRef provider = CGImageGetDataProvider(excluded_image.get()); + rtc::ScopedCFTypeRef<CFDataRef> excluded_image_data(CGDataProviderCopyData(provider)); + RTC_DCHECK(excluded_image_data); + display_base_address = CFDataGetBytePtr(excluded_image_data.get()); + src_bytes_per_row = CGImageGetBytesPerRow(excluded_image.get()); + + // Translate the bounds relative to the desktop, because `frame` data + // starts from the desktop top-left corner. + DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds); + window_bounds_relative_to_desktop.Translate(-screen_pixel_bounds_.left(), + -screen_pixel_bounds_.top()); + + DesktopRect rect_to_copy = DesktopRect::MakeSize(excluded_window_bounds.size()); + rect_to_copy.IntersectWith(DesktopRect::MakeWH(CGImageGetWidth(excluded_image.get()), + CGImageGetHeight(excluded_image.get()))); + + if (CGImageGetBitsPerPixel(excluded_image.get()) / 8 == DesktopFrame::kBytesPerPixel) { + CopyRect(display_base_address, + src_bytes_per_row, + frame.GetFrameDataAtPos(window_bounds_relative_to_desktop.top_left()), + frame.stride(), + DesktopFrame::kBytesPerPixel, + rect_to_copy); + } + } + } + if (window_list) CFRelease(window_list); + return true; +} + +void ScreenCapturerMac::ScreenConfigurationChanged() { + if (current_display_) { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + screen_pixel_bounds_ = config ? config->pixel_bounds : DesktopRect(); + dip_to_pixel_scale_ = config ? config->dip_to_pixel_scale : 1.0f; + } else { + screen_pixel_bounds_ = desktop_config_.pixel_bounds; + dip_to_pixel_scale_ = desktop_config_.dip_to_pixel_scale; + } + + // Release existing buffers, which will be of the wrong size. + ReleaseBuffers(); + + // Clear the dirty region, in case the display is down-sizing. + helper_.ClearInvalidRegion(); + + // Re-mark the entire desktop as dirty. + helper_.InvalidateScreen(screen_pixel_bounds_.size()); + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); +} + +bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { + RTC_DCHECK(thread_checker_.IsCurrent()); + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + for (const auto& config : desktop_config_.displays) { + size_t pixel_width = config.pixel_bounds.width(); + size_t pixel_height = config.pixel_bounds.height(); + if (pixel_width == 0 || pixel_height == 0) continue; + CGDirectDisplayID display_id = config.id; + DesktopVector display_origin = config.pixel_bounds.top_left(); + + CGDisplayStreamFrameAvailableHandler handler = ^(CGDisplayStreamFrameStatus status, + uint64_t display_time, + IOSurfaceRef frame_surface, + CGDisplayStreamUpdateRef updateRef) { + RTC_DCHECK(thread_checker_.IsCurrent()); + if (status == kCGDisplayStreamFrameStatusStopped) return; + + // Only pay attention to frame updates. + if (status != kCGDisplayStreamFrameStatusFrameComplete) return; + + size_t count = 0; + const CGRect* rects = + wrapCGDisplayStreamUpdateGetRects(updateRef, kCGDisplayStreamUpdateDirtyRects, &count); + if (count != 0) { + // According to CGDisplayStream.h, it's safe to call + // CGDisplayStreamStop() from within the callback. + ScreenRefresh(display_id, count, rects, display_origin, frame_surface); + } + }; + + rtc::ScopedCFTypeRef<CFDictionaryRef> properties_dict( + CFDictionaryCreate(kCFAllocatorDefault, + (const void*[]){wrapkCGDisplayStreamShowCursor()}, + (const void*[]){kCFBooleanFalse}, + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CGDisplayStreamRef display_stream = wrapCGDisplayStreamCreate( + display_id, pixel_width, pixel_height, 'BGRA', properties_dict.get(), handler); + + if (display_stream) { + CGError error = wrapCGDisplayStreamStart(display_stream); + if (error != kCGErrorSuccess) return false; + + CFRunLoopSourceRef source = wrapCGDisplayStreamGetRunLoopSource(display_stream); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); + display_streams_.push_back(display_stream); + } + } + + return true; +} + +void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { + RTC_DCHECK(thread_checker_.IsCurrent()); + + for (CGDisplayStreamRef stream : display_streams_) { + CFRunLoopSourceRef source = wrapCGDisplayStreamGetRunLoopSource(stream); + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); + wrapCGDisplayStreamStop(stream); + CFRelease(stream); + } + display_streams_.clear(); + + // Release obsolete io surfaces. + desktop_frame_provider_.Release(); +} + +void ScreenCapturerMac::ScreenRefresh(CGDirectDisplayID display_id, + CGRectCount count, + const CGRect* rect_array, + DesktopVector display_origin, + IOSurfaceRef io_surface) { + if (screen_pixel_bounds_.is_empty()) ScreenConfigurationChanged(); + + // The refresh rects are in display coordinates. We want to translate to + // framebuffer coordinates. If a specific display is being captured, then no + // change is necessary. If all displays are being captured, then we want to + // translate by the origin of the display. + DesktopVector translate_vector; + if (!current_display_) translate_vector = display_origin; + + DesktopRegion region; + for (CGRectCount i = 0; i < count; ++i) { + // All rects are already in physical pixel coordinates. + DesktopRect rect = DesktopRect::MakeXYWH(rect_array[i].origin.x, + rect_array[i].origin.y, + rect_array[i].size.width, + rect_array[i].size.height); + + rect.Translate(translate_vector); + + region.AddRect(rect); + } + // Always having the latest iosurface before invalidating a region. + // See https://bugs.chromium.org/p/webrtc/issues/detail?id=8652 for details. + desktop_frame_provider_.InvalidateIOSurface( + display_id, rtc::ScopedCFTypeRef<IOSurfaceRef>(io_surface, rtc::RetainPolicy::RETAIN)); + helper_.InvalidateRegion(region); +} + +std::unique_ptr<DesktopFrame> ScreenCapturerMac::CreateFrame() { + std::unique_ptr<DesktopFrame> frame(new BasicDesktopFrame(screen_pixel_bounds_.size())); + frame->set_dpi( + DesktopVector(kStandardDPI * dip_to_pixel_scale_, kStandardDPI * dip_to_pixel_scale_)); + return frame; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc new file mode 100644 index 0000000000..989ec7ea54 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/window_list_utils.h" + +#include <ApplicationServices/ApplicationServices.h> + +#include <algorithm> +#include <cmath> +#include <iterator> +#include <limits> +#include <list> +#include <map> +#include <memory> +#include <utility> + +#include "rtc_base/checks.h" + +static_assert(static_cast<webrtc::WindowId>(kCGNullWindowID) == + webrtc::kNullWindowId, + "kNullWindowId needs to equal to kCGNullWindowID."); + +namespace webrtc { + +namespace { + +// WindowName of the status indicator dot shown since Monterey in the taskbar. +// Testing on 12.2.1 shows this is independent of system language setting. +const CFStringRef kStatusIndicator = CFSTR("StatusIndicator"); +const CFStringRef kStatusIndicatorOwnerName = CFSTR("Window Server"); + +bool ToUtf8(const CFStringRef str16, std::string* str8) { + size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16), + kCFStringEncodingUTF8) + + 1; + std::unique_ptr<char[]> buffer(new char[maxlen]); + if (!buffer || + !CFStringGetCString(str16, buffer.get(), maxlen, kCFStringEncodingUTF8)) { + return false; + } + str8->assign(buffer.get()); + return true; +} + +// Get CFDictionaryRef from `id` and call `on_window` against it. This function +// returns false if native APIs fail, typically it indicates that the `id` does +// not represent a window. `on_window` will not be called if false is returned +// from this function. +bool GetWindowRef(CGWindowID id, + rtc::FunctionView<void(CFDictionaryRef)> on_window) { + RTC_DCHECK(on_window); + + // TODO(zijiehe): `id` is a 32-bit integer, casting it to an array seems not + // safe enough. Maybe we should create a new + // const void* arr[] = { + // reinterpret_cast<void*>(id) } + // }; + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void**>(&id), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + + bool result = false; + // TODO(zijiehe): CFArrayGetCount(window_array) should always return 1. + // Otherwise, we should treat it as failure. + if (window_array && CFArrayGetCount(window_array)) { + on_window(reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0))); + result = true; + } + + if (window_array) { + CFRelease(window_array); + } + CFRelease(window_id_array); + return result; +} + +} // namespace + +bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window, + bool ignore_minimized, + bool only_zero_layer) { + RTC_DCHECK(on_window); + + // Only get on screen, non-desktop windows. + // According to + // https://developer.apple.com/documentation/coregraphics/cgwindowlistoption/1454105-optiononscreenonly + // , when kCGWindowListOptionOnScreenOnly is used, the order of windows are in + // decreasing z-order. + CFArrayRef window_array = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (!window_array) + return false; + + MacDesktopConfiguration desktop_config = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + // Check windows to make sure they have an id, title, and use window layer + // other than 0. + CFIndex count = CFArrayGetCount(window_array); + for (CFIndex i = 0; i < count; i++) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, i)); + if (!window) { + continue; + } + + CFNumberRef window_id = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + if (!window_id) { + continue; + } + + CFNumberRef window_layer = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (!window_layer) { + continue; + } + + // Skip windows with layer!=0 (menu, dock). + int layer; + if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)) { + continue; + } + if (only_zero_layer && layer != 0) { + continue; + } + + // Skip windows that are minimized and not full screen. + if (ignore_minimized && !IsWindowOnScreen(window) && + !IsWindowFullScreen(desktop_config, window)) { + continue; + } + + // If window title is empty, only consider it if it is either on screen or + // fullscreen. + CFStringRef window_title = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + if (!window_title && !IsWindowOnScreen(window) && + !IsWindowFullScreen(desktop_config, window)) { + continue; + } + + CFStringRef window_owner_name = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowOwnerName)); + // Ignore the red dot status indicator shown in the stats bar. Unlike the + // rest of the system UI it has a window_layer of 0, so was otherwise + // included. See crbug.com/1297731. + if (window_title && CFEqual(window_title, kStatusIndicator) && + window_owner_name && + CFEqual(window_owner_name, kStatusIndicatorOwnerName)) { + continue; + } + + if (!on_window(window)) { + break; + } + } + + CFRelease(window_array); + return true; +} + +bool GetWindowList(DesktopCapturer::SourceList* windows, + bool ignore_minimized, + bool only_zero_layer) { + // Use a std::list so that iterators are preversed upon insertion and + // deletion. + std::list<DesktopCapturer::Source> sources; + std::map<int, std::list<DesktopCapturer::Source>::const_iterator> pid_itr_map; + const bool ret = GetWindowList( + [&sources, &pid_itr_map](CFDictionaryRef window) { + WindowId window_id = GetWindowId(window); + if (window_id != kNullWindowId) { + const std::string title = GetWindowTitle(window); + const int pid = GetWindowOwnerPid(window); + // Check if window for the same pid have been already inserted. + std::map<int, + std::list<DesktopCapturer::Source>::const_iterator>::iterator + itr = pid_itr_map.find(pid); + + // Only consider empty titles if the app has no other window with a + // proper title. + if (title.empty()) { + std::string owner_name = GetWindowOwnerName(window); + + // At this time we do not know if there will be other windows + // for the same pid unless they have been already inserted, hence + // the check in the map. Also skip the window if owner name is + // empty too. + if (!owner_name.empty() && (itr == pid_itr_map.end())) { + sources.push_back(DesktopCapturer::Source{window_id, pid, owner_name}); + RTC_DCHECK(!sources.empty()); + // Get an iterator on the last valid element in the source list. + std::list<DesktopCapturer::Source>::const_iterator last_source = + --sources.end(); + pid_itr_map.insert( + std::pair<int, + std::list<DesktopCapturer::Source>::const_iterator>( + pid, last_source)); + } + } else { + sources.push_back(DesktopCapturer::Source{window_id, pid, title}); + // Once the window with empty title has been removed no other empty + // windows are allowed for the same pid. + if (itr != pid_itr_map.end() && (itr->second != sources.end())) { + sources.erase(itr->second); + // sdt::list::end() never changes during the lifetime of that + // list. + itr->second = sources.end(); + } + } + } + return true; + }, + ignore_minimized, only_zero_layer); + + if (!ret) + return false; + + RTC_DCHECK(windows); + windows->reserve(windows->size() + sources.size()); + std::copy(std::begin(sources), std::end(sources), + std::back_inserter(*windows)); + + return true; +} + +// Returns true if the window is occupying a full screen. +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CFDictionaryRef window) { + bool fullscreen = false; + CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>( + CFDictionaryGetValue(window, kCGWindowBounds)); + + CGRect bounds; + if (bounds_ref && + CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) { + for (MacDisplayConfigurations::const_iterator it = + desktop_config.displays.begin(); + it != desktop_config.displays.end(); it++) { + if (it->bounds.equals( + DesktopRect::MakeXYWH(bounds.origin.x, bounds.origin.y, + bounds.size.width, bounds.size.height))) { + fullscreen = true; + break; + } + } + } + + return fullscreen; +} + +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CGWindowID id) { + bool fullscreen = false; + GetWindowRef(id, [&](CFDictionaryRef window) { + fullscreen = IsWindowFullScreen(desktop_config, window); + }); + return fullscreen; +} + +bool IsWindowOnScreen(CFDictionaryRef window) { + CFBooleanRef on_screen = reinterpret_cast<CFBooleanRef>( + CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + return on_screen != NULL && CFBooleanGetValue(on_screen); +} + +bool IsWindowOnScreen(CGWindowID id) { + bool on_screen; + if (GetWindowRef(id, [&on_screen](CFDictionaryRef window) { + on_screen = IsWindowOnScreen(window); + })) { + return on_screen; + } + return false; +} + +std::string GetWindowTitle(CFDictionaryRef window) { + CFStringRef title = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + std::string result; + if (title && ToUtf8(title, &result)) { + return result; + } + + return std::string(); +} + +std::string GetWindowTitle(CGWindowID id) { + std::string title; + if (GetWindowRef(id, [&title](CFDictionaryRef window) { + title = GetWindowTitle(window); + })) { + return title; + } + return std::string(); +} + +std::string GetWindowOwnerName(CFDictionaryRef window) { + CFStringRef owner_name = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowOwnerName)); + std::string result; + if (owner_name && ToUtf8(owner_name, &result)) { + return result; + } + return std::string(); +} + +std::string GetWindowOwnerName(CGWindowID id) { + std::string owner_name; + if (GetWindowRef(id, [&owner_name](CFDictionaryRef window) { + owner_name = GetWindowOwnerName(window); + })) { + return owner_name; + } + return std::string(); +} + +WindowId GetWindowId(CFDictionaryRef window) { + CFNumberRef window_id = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + if (!window_id) { + return kNullWindowId; + } + + // Note: WindowId is 64-bit on 64-bit system, but CGWindowID is always 32-bit. + // CFNumberGetValue() fills only top 32 bits, so we should use CGWindowID to + // receive the window id. + CGWindowID id; + if (!CFNumberGetValue(window_id, kCFNumberIntType, &id)) { + return kNullWindowId; + } + + return id; +} + +int GetWindowOwnerPid(CFDictionaryRef window) { + CFNumberRef window_pid = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + if (!window_pid) { + return 0; + } + + int pid; + if (!CFNumberGetValue(window_pid, kCFNumberIntType, &pid)) { + return 0; + } + + return pid; +} + +int GetWindowOwnerPid(CGWindowID id) { + int pid; + if (GetWindowRef(id, [&pid](CFDictionaryRef window) { + pid = GetWindowOwnerPid(window); + })) { + return pid; + } + return 0; +} + +float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config, + DesktopVector position) { + // Find the dpi to physical pixel scale for the screen where the mouse cursor + // is. + for (auto it = desktop_config.displays.begin(); + it != desktop_config.displays.end(); ++it) { + if (it->bounds.Contains(position)) { + return it->dip_to_pixel_scale; + } + } + return 1; +} + +float GetWindowScaleFactor(CGWindowID id, DesktopSize size) { + DesktopRect window_bounds = GetWindowBounds(id); + float scale = 1.0f; + + if (!window_bounds.is_empty() && !size.is_empty()) { + float scale_x = size.width() / window_bounds.width(); + float scale_y = size.height() / window_bounds.height(); + // Currently the scale in X and Y directions must be same. + if ((std::fabs(scale_x - scale_y) <= + std::numeric_limits<float>::epsilon() * std::max(scale_x, scale_y)) && + scale_x > 0.0f) { + scale = scale_x; + } + } + + return scale; +} + +DesktopRect GetWindowBounds(CFDictionaryRef window) { + CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>( + CFDictionaryGetValue(window, kCGWindowBounds)); + if (!window_bounds) { + return DesktopRect(); + } + + CGRect gc_window_rect; + if (!CGRectMakeWithDictionaryRepresentation(window_bounds, &gc_window_rect)) { + return DesktopRect(); + } + + return DesktopRect::MakeXYWH(gc_window_rect.origin.x, gc_window_rect.origin.y, + gc_window_rect.size.width, + gc_window_rect.size.height); +} + +DesktopRect GetWindowBounds(CGWindowID id) { + DesktopRect result; + if (GetWindowRef(id, [&result](CFDictionaryRef window) { + result = GetWindowBounds(window); + })) { + return result; + } + return DesktopRect(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.h b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.h new file mode 100644 index 0000000000..a9b1e7007c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <string> +#include "api/function_view.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" + +namespace webrtc { + +// Iterates all on-screen windows in decreasing z-order and sends them +// one-by-one to `on_window` function. If `on_window` returns false, this +// function returns immediately. GetWindowList() returns false if native APIs +// failed. Menus, dock (if `only_zero_layer`), minimized windows (if +// `ignore_minimized` is true) and any windows which do not have a valid window +// id or title will be ignored. +bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window, + bool ignore_minimized, + bool only_zero_layer); + +// Another helper function to get the on-screen windows. +bool GetWindowList(DesktopCapturer::SourceList* windows, + bool ignore_minimized, + bool only_zero_layer); + +// Returns true if the window is occupying a full screen. +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CFDictionaryRef window); + +// Returns true if the window is occupying a full screen. +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CGWindowID id); + +// Returns true if the `window` is on screen. This function returns false if +// native APIs fail. +bool IsWindowOnScreen(CFDictionaryRef window); + +// Returns true if the window is on screen. This function returns false if +// native APIs fail or `id` cannot be found. +bool IsWindowOnScreen(CGWindowID id); + +// Returns utf-8 encoded title of `window`. If `window` is not a window or no +// valid title can be retrieved, this function returns an empty string. +std::string GetWindowTitle(CFDictionaryRef window); + +// Returns utf-8 encoded title of window `id`. If `id` cannot be found or no +// valid title can be retrieved, this function returns an empty string. +std::string GetWindowTitle(CGWindowID id); + +// Returns utf-8 encoded owner name of `window`. If `window` is not a window or +// if no valid owner name can be retrieved, returns an empty string. +std::string GetWindowOwnerName(CFDictionaryRef window); + +// Returns utf-8 encoded owner name of the given window `id`. If `id` cannot be +// found or if no valid owner name can be retrieved, returns an empty string. +std::string GetWindowOwnerName(CGWindowID id); + +// Returns id of `window`. If `window` is not a window or the window id cannot +// be retrieved, this function returns kNullWindowId. +WindowId GetWindowId(CFDictionaryRef window); + +// Returns the pid of the process owning `window`. Return 0 if `window` is not +// a window or no valid owner can be retrieved. +int GetWindowOwnerPid(CFDictionaryRef window); + +// Returns the pid of the process owning the window `id`. Return 0 if `id` +// cannot be found or no valid owner can be retrieved. +int GetWindowOwnerPid(CGWindowID id); + +// Returns the DIP to physical pixel scale at `position`. `position` is in +// *unscaled* system coordinate, i.e. it's device-independent and the primary +// monitor starts from (0, 0). If `position` is out of the system display, this +// function returns 1. +float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config, + DesktopVector position); + +// Returns the DIP to physical pixel scale factor of the window with `id`. +// The bounds of the window with `id` is in DIP coordinates and `size` is the +// CGImage size of the window with `id` in physical coordinates. Comparing them +// can give the current scale factor. +// If the window overlaps multiple monitors, OS will decide on which monitor the +// window is displayed and use its scale factor to the window. So this method +// still works. +float GetWindowScaleFactor(CGWindowID id, DesktopSize size); + +// Returns the bounds of `window`. If `window` is not a window or the bounds +// cannot be retrieved, this function returns an empty DesktopRect. The returned +// DesktopRect is in system coordinate, i.e. the primary monitor always starts +// from (0, 0). +// Deprecated: This function should be avoided in favor of the overload with +// MacDesktopConfiguration. +DesktopRect GetWindowBounds(CFDictionaryRef window); + +// Returns the bounds of window with `id`. If `id` does not represent a window +// or the bounds cannot be retrieved, this function returns an empty +// DesktopRect. The returned DesktopRect is in system coordinates. +// Deprecated: This function should be avoided in favor of the overload with +// MacDesktopConfiguration. +DesktopRect GetWindowBounds(CGWindowID id); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_ |