#include <IOKit/pwr_mgt/IOPMLib.h>
#include <OpenGL/CGLMacro.h>
#include <OpenGL/OpenGL.h>
-#include <sys/utsname.h>
#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
#include "webrtc/modules/desktop_capture/desktop_frame.h"
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
#include "webrtc/modules/desktop_capture/desktop_region.h"
#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
+#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h"
+#include "webrtc/modules/desktop_capture/mac/osx_version.h"
#include "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h"
#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h"
#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
-#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/system_wrappers/interface/logging.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/system_wrappers/interface/tick_util.h"
}
}
-int GetDarwinVersion() {
- struct utsname uname_info;
- if (uname(&uname_info) != 0) {
- LOG(LS_ERROR) << "uname failed";
- return 0;
- }
-
- if (strcmp(uname_info.sysname, "Darwin") != 0)
- return 0;
-
- char* dot;
- int result = strtol(uname_info.release, &dot, 10);
- if (*dot != '.') {
- LOG(LS_ERROR) << "Failed to parse version";
- return 0;
- }
-
- return result;
-}
-
-bool IsOSLionOrLater() {
- static int darwin_version = GetDarwinVersion();
-
- // Verify that the version has been parsed correctly.
- if (darwin_version < 6) {
- LOG_F(LS_ERROR) << "Invalid Darwin version: " << darwin_version;
- abort();
- }
-
- // Darwin major version 11 corresponds to OSX 10.7.
- return darwin_version >= 11;
-}
-
-// The amount of time allowed for displays to reconfigure.
-const int64_t kDisplayConfigurationEventTimeoutMs = 10 * 1000;
-
// A class to perform video frame capturing for mac.
class ScreenCapturerMac : public ScreenCapturer {
public:
- ScreenCapturerMac();
+ explicit ScreenCapturerMac(
+ scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor);
virtual ~ScreenCapturerMac();
bool Init();
void GlBlitSlow(const DesktopFrame& frame);
void CgBlitPreLion(const DesktopFrame& frame,
const DesktopRegion& region);
- void CgBlitPostLion(const DesktopFrame& frame,
+ // Returns false if the selected screen is no longer valid.
+ bool CgBlitPostLion(const DesktopFrame& frame,
const DesktopRegion& region);
// Called when the screen configuration is changed.
void ScreenUpdateMove(CGScreenUpdateMoveDelta delta,
size_t count,
const CGRect *rect_array);
- void DisplaysReconfigured(CGDirectDisplayID display,
- CGDisplayChangeSummaryFlags flags);
static void ScreenRefreshCallback(CGRectCount count,
const CGRect *rect_array,
void *user_parameter);
size_t count,
const CGRect *rect_array,
void *user_parameter);
- static void DisplaysReconfiguredCallback(CGDirectDisplayID display,
- CGDisplayChangeSummaryFlags flags,
- void *user_parameter);
-
void ReleaseBuffers();
+ DesktopFrame* CreateFrame();
+
Callback* callback_;
MouseShapeObserver* mouse_shape_observer_;
// 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_;
+
+ // 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_;
+
// 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_;
- // Used to ensure that frame captures do not take place while displays
- // are being reconfigured.
- scoped_ptr<EventWrapper> display_configuration_capture_event_;
-
- // Records the Ids of attached displays which are being reconfigured.
- // Accessed on the thread on which we are notified of display events.
- std::set<CGDirectDisplayID> reconfiguring_displays_;
+ // Monitoring display reconfiguration.
+ scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor_;
// Power management assertion to prevent the screen from sleeping.
IOPMAssertionID power_assertion_id_display_;
DISALLOW_COPY_AND_ASSIGN(InvertedDesktopFrame);
};
-DesktopFrame* CreateFrame(
- const MacDesktopConfiguration& desktop_config) {
-
- DesktopSize size(desktop_config.pixel_bounds.width(),
- desktop_config.pixel_bounds.height());
- scoped_ptr<DesktopFrame> frame(new BasicDesktopFrame(size));
-
- frame->set_dpi(DesktopVector(
- kStandardDPI * desktop_config.dip_to_pixel_scale,
- kStandardDPI * desktop_config.dip_to_pixel_scale));
- return frame.release();
-}
-
-ScreenCapturerMac::ScreenCapturerMac()
+ScreenCapturerMac::ScreenCapturerMac(
+ scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor)
: callback_(NULL),
mouse_shape_observer_(NULL),
cgl_context_(NULL),
- display_configuration_capture_event_(EventWrapper::Create()),
+ current_display_(0),
+ dip_to_pixel_scale_(1.0f),
+ desktop_config_monitor_(desktop_config_monitor),
power_assertion_id_display_(kIOPMNullAssertionID),
power_assertion_id_user_(kIOPMNullAssertionID),
app_services_library_(NULL),
cg_display_bits_per_pixel_(NULL),
opengl_library_(NULL),
cgl_set_full_screen_(NULL) {
- display_configuration_capture_event_->Set();
}
ScreenCapturerMac::~ScreenCapturerMac() {
ReleaseBuffers();
UnregisterRefreshAndMoveHandlers();
- CGError err = CGDisplayRemoveReconfigurationCallback(
- ScreenCapturerMac::DisplaysReconfiguredCallback, this);
- if (err != kCGErrorSuccess)
- LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err;
-
dlclose(app_services_library_);
dlclose(opengl_library_);
}
if (!RegisterRefreshAndMoveHandlers()) {
return false;
}
-
- CGError err = CGDisplayRegisterReconfigurationCallback(
- ScreenCapturerMac::DisplaysReconfiguredCallback, this);
- if (err != kCGErrorSuccess) {
- LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err;
- return false;
- }
-
+ desktop_config_monitor_->Lock();
+ desktop_config_ = desktop_config_monitor_->desktop_configuration();
+ desktop_config_monitor_->Unlock();
ScreenConfigurationChanged();
return true;
}
queue_.MoveToNextFrame();
- // Wait until the display configuration is stable. If one or more displays
- // are reconfiguring then |display_configuration_capture_event_| will not be
- // set until the reconfiguration completes.
- // TODO(wez): Replace this with an early-exit (See crbug.com/104542).
- if (!display_configuration_capture_event_->Wait(
- kDisplayConfigurationEventTimeoutMs)) {
- LOG_F(LS_ERROR) << "Event wait timed out.";
- abort();
+ desktop_config_monitor_->Lock();
+ 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();
+ RegisterRefreshAndMoveHandlers();
+ ScreenConfigurationChanged();
}
DesktopRegion region;
// 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(CreateFrame(desktop_config_));
+ queue_.ReplaceCurrentFrame(CreateFrame());
DesktopFrame* current_frame = queue_.current_frame();
if (IsOSLionOrLater()) {
// Lion requires us to use their new APIs for doing screen capture. These
// APIS currently crash on 10.6.8 if there is no monitor attached.
- CgBlitPostLion(*current_frame, region);
+ if (!CgBlitPostLion(*current_frame, region)) {
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
} else if (cgl_context_) {
flip = true;
if (pixel_buffer_object_.get() != 0) {
// Signal that we are done capturing data from the display framebuffer,
// and accessing display structures.
- display_configuration_capture_event_->Set();
+ desktop_config_monitor_->Unlock();
// Capture the current cursor shape and notify |callback_| if it has changed.
CaptureCursor();
bool ScreenCapturerMac::GetScreenList(ScreenList* screens) {
assert(screens->size() == 0);
- // TODO(jiayl): implement screen enumeration.
- Screen default_screen;
- default_screen.id = 0;
- screens->push_back(default_screen);
+ if (!IsOSLionOrLater()) {
+ // Single monitor cast is not supported on pre OS X 10.7.
+ Screen screen;
+ screen.id = kFullDesktopScreenId;
+ screens->push_back(screen);
+ return true;
+ }
+
+ for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin();
+ it != desktop_config_.displays.end(); ++it) {
+ Screen screen;
+ screen.id = static_cast<ScreenId>(it->id);
+ screens->push_back(screen);
+ }
return true;
}
bool ScreenCapturerMac::SelectScreen(ScreenId id) {
- // TODO(jiayl): implement screen selection.
+ if (!IsOSLionOrLater()) {
+ // Ignore the screen selection on unsupported OS.
+ assert(!current_display_);
+ return id == kFullDesktopScreenId;
+ }
+
+ 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;
}
}
}
-void ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame,
+bool ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame,
const DesktopRegion& region) {
// Copy the entire contents of the previous capture buffer, to capture over.
// TODO(wez): Get rid of this as per crbug.com/145064, or implement
frame.stride() * frame.size().height());
}
- for (size_t i = 0; i < desktop_config_.displays.size(); ++i) {
- const MacDisplayConfiguration& display_config = desktop_config_.displays[i];
+ 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 {
+ LOG(LS_ERROR) << "The selected screen cannot be found for capturing.";
+ return false;
+ }
+ } else {
+ // Capturing the whole desktop.
+ displays_to_capture = desktop_config_.displays;
+ }
+ 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(-desktop_config_.pixel_bounds.left(),
- -desktop_config_.pixel_bounds.top());
+ 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;
CFRelease(data);
CFRelease(image);
}
+ 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();
- // Refresh the cached desktop configuration.
- desktop_config_ = MacDesktopConfiguration::GetCurrent(
- MacDesktopConfiguration::TopLeftOrigin);
-
// Re-mark the entire desktop as dirty.
- helper_.InvalidateScreen(
- DesktopSize(desktop_config_.pixel_bounds.width(),
- desktop_config_.pixel_bounds.height()));
+ helper_.InvalidateScreen(screen_pixel_bounds_.size());
// Make sure the frame buffers will be reallocated.
queue_.Reset();
(*cgl_set_full_screen_)(cgl_context_);
CGLSetCurrentContext(cgl_context_);
- size_t buffer_size = desktop_config_.pixel_bounds.width() *
- desktop_config_.pixel_bounds.height() *
+ size_t buffer_size = screen_pixel_bounds_.width() *
+ screen_pixel_bounds_.height() *
sizeof(uint32_t);
pixel_buffer_object_.Init(cgl_context_, buffer_size);
}
void ScreenCapturerMac::ScreenRefresh(CGRectCount count,
const CGRect* rect_array) {
- if (desktop_config_.pixel_bounds.is_empty())
+ if (screen_pixel_bounds_.is_empty())
return;
DesktopRegion region;
-
+ DesktopVector translate_vector =
+ DesktopVector().subtract(screen_pixel_bounds_.top_left());
for (CGRectCount i = 0; i < count; ++i) {
// Convert from Density-Independent Pixel to physical pixel coordinates.
- DesktopRect rect =
- ScaleAndRoundCGRect(rect_array[i], desktop_config_.dip_to_pixel_scale);
-
+ DesktopRect rect = ScaleAndRoundCGRect(rect_array[i], dip_to_pixel_scale_);
// Translate from local desktop to capturer framebuffer coordinates.
- rect.Translate(-desktop_config_.pixel_bounds.left(),
- -desktop_config_.pixel_bounds.top());
-
+ rect.Translate(translate_vector);
region.AddRect(rect);
}
ScreenRefresh(count, refresh_rects);
}
-void ScreenCapturerMac::DisplaysReconfigured(
- CGDirectDisplayID display,
- CGDisplayChangeSummaryFlags flags) {
- if (flags & kCGDisplayBeginConfigurationFlag) {
- if (reconfiguring_displays_.empty()) {
- // If this is the first display to start reconfiguring then wait on
- // |display_configuration_capture_event_| to block the capture thread
- // from accessing display memory until the reconfiguration completes.
- if (!display_configuration_capture_event_->Wait(
- kDisplayConfigurationEventTimeoutMs)) {
- LOG_F(LS_ERROR) << "Event wait timed out.";
- abort();
- }
- }
-
- reconfiguring_displays_.insert(display);
- } else {
- reconfiguring_displays_.erase(display);
-
- if (reconfiguring_displays_.empty()) {
- // If no other displays are reconfiguring then refresh capturer data
- // structures and un-block the capturer thread. Occasionally, the
- // refresh and move handlers are lost when the screen mode changes,
- // so re-register them here (the same does not appear to be true for
- // the reconfiguration handler itself).
- UnregisterRefreshAndMoveHandlers();
- RegisterRefreshAndMoveHandlers();
- ScreenConfigurationChanged();
- display_configuration_capture_event_->Set();
- }
- }
-}
-
void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count,
const CGRect* rect_array,
void* user_parameter) {
ScreenCapturerMac* capturer =
reinterpret_cast<ScreenCapturerMac*>(user_parameter);
- if (capturer->desktop_config_.pixel_bounds.is_empty())
+ if (capturer->screen_pixel_bounds_.is_empty())
capturer->ScreenConfigurationChanged();
capturer->ScreenRefresh(count, rect_array);
}
capturer->ScreenUpdateMove(delta, count, rect_array);
}
-void ScreenCapturerMac::DisplaysReconfiguredCallback(
- CGDirectDisplayID display,
- CGDisplayChangeSummaryFlags flags,
- void* user_parameter) {
- ScreenCapturerMac* capturer =
- reinterpret_cast<ScreenCapturerMac*>(user_parameter);
- capturer->DisplaysReconfigured(display, flags);
+DesktopFrame* ScreenCapturerMac::CreateFrame() {
+ scoped_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.release();
}
} // namespace
// static
ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
- scoped_ptr<ScreenCapturerMac> capturer(new ScreenCapturerMac());
+ if (!options.configuration_monitor())
+ return NULL;
+
+ scoped_ptr<ScreenCapturerMac> capturer(
+ new ScreenCapturerMac(options.configuration_monitor()));
if (!capturer->Init())
capturer.reset();
return capturer.release();