Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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_
|
||||
|
|
@ -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(®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) {
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue