Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -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_

View file

@ -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

View file

@ -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

View file

@ -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_

View file

@ -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 RTC_EXPORT 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);
static std::unique_ptr<DesktopFrameCGImage> CreateFromCGImage(
rtc::ScopedCFTypeRef<CGImageRef> cg_image);
~DesktopFrameCGImage() override;
DesktopFrameCGImage(const DesktopFrameCGImage&) = delete;
DesktopFrameCGImage& operator=(const DesktopFrameCGImage&) = delete;
private:
// 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_

View file

@ -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 providers
// 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

View file

@ -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_

View file

@ -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

View file

@ -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.
*/
#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();
bool allow_iosurface() const { return allow_iosurface_; }
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_

View file

@ -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

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 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_utils.h"
#include <memory>
namespace webrtc {
std::unique_ptr<DesktopFrame> CreateDesktopFrameFromCGImage(
rtc::ScopedCFTypeRef<CGImageRef> cg_image) {
return DesktopFrameCGImage::CreateFromCGImage(cg_image);
}
} // namespace webrtc

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 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_UTILS_H_
#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_UTILS_H_
#include <memory>
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/mac/desktop_frame_cgimage.h"
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
std::unique_ptr<DesktopFrame> RTC_EXPORT
CreateDesktopFrameFromCGImage(rtc::ScopedCFTypeRef<CGImageRef> cg_image);
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_UTILS_H_

View file

@ -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

View file

@ -0,0 +1,25 @@
/*
* 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_

View file

@ -0,0 +1,120 @@
/*
* 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_;
};
} // namespace webrtc
#endif // MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_

View file

@ -0,0 +1,553 @@
/*
* 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"
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;
// 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 (!desktop_config_.Equals(new_config)) {
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(&region);
// 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) {
screens->push_back({it->id, std::string()});
}
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());
if (!desktop_frame_provider_.allow_iosurface()) {
return true;
}
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 =
CGDisplayStreamUpdateGetRects(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*[]){kCGDisplayStreamShowCursor},
(const void*[]){kCFBooleanFalse},
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CGDisplayStreamRef display_stream = CGDisplayStreamCreate(
display_id, pixel_width, pixel_height, 'BGRA', properties_dict.get(), handler);
if (display_stream) {
CGError error = CGDisplayStreamStart(display_stream);
if (error != kCGErrorSuccess) return false;
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(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 = CGDisplayStreamGetRunLoopSource(stream);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
CGDisplayStreamStop(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

View file

@ -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, 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, 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

View file

@ -0,0 +1,120 @@
/*
* 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"
#include "rtc_base/system/rtc_export.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 RTC_EXPORT
GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window,
bool ignore_minimized,
bool only_zero_layer);
// Another helper function to get the on-screen windows.
bool RTC_EXPORT 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_