diff --git a/webrtc/modules/desktop_capture/window_capturer_mac.cc b/webrtc/modules/desktop_capture/window_capturer_mac.cc index 3cfde7c075..e78d95bc2e 100755 --- a/webrtc/modules/desktop_capture/window_capturer_mac.cc +++ b/webrtc/modules/desktop_capture/window_capturer_mac.cc @@ -11,13 +11,59 @@ #include "webrtc/modules/desktop_capture/window_capturer.h" #include +#include +#include #include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { namespace { +bool CFStringRefToUtf8(const CFStringRef string, std::string* str_utf8) { + assert(string); + assert(str_utf8); + CFIndex length = CFStringGetLength(string); + size_t max_length_utf8 = + CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); + str_utf8->resize(max_length_utf8); + CFIndex used_bytes; + int result = CFStringGetBytes( + string, CFRangeMake(0, length), kCFStringEncodingUTF8, 0, false, + reinterpret_cast(&*str_utf8->begin()), max_length_utf8, + &used_bytes); + if (result != length) { + str_utf8->clear(); + return false; + } + str_utf8->resize(used_bytes); + return true; +} + +// DesktopFrame that stores data in CFData. +class CFDataDesktopFrame : public DesktopFrame { + public: + // Consumes |cf_data| reference. + // + // TODO(sergeyu): Here we const_cast<> the buffer used in CFDataRef. CFDataRef + // buffer is immutable, but DesktopFrame is always mutable. This shouldn't be + // a problem because frames generated by WindowCapturers are normally not + // mutated. To avoid this hack consider making DesktopFrame immutable and add + // MutableDesktopFrame. + CFDataDesktopFrame(DesktopSize size, int stride, CFDataRef cf_data) + : DesktopFrame(size, stride, + const_cast(CFDataGetBytePtr(cf_data)), NULL), + cf_data_(cf_data) { + } + virtual ~CFDataDesktopFrame() { + CFRelease(cf_data_); + } + + private: + CFDataRef cf_data_; +}; + class WindowCapturerMac : public WindowCapturer { public: WindowCapturerMac(); @@ -33,25 +79,81 @@ class WindowCapturerMac : public WindowCapturer { private: Callback* callback_; + CGWindowID window_id_; DISALLOW_COPY_AND_ASSIGN(WindowCapturerMac); }; WindowCapturerMac::WindowCapturerMac() - : callback_(NULL) { + : callback_(NULL), + window_id_(0) { } WindowCapturerMac::~WindowCapturerMac() { } bool WindowCapturerMac::GetWindowList(WindowList* windows) { - // Not implemented yet. - return false; + // Only get on screen, non-desktop windows. + CFArrayRef window_array = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (!window_array) + return false; + + // 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( + CFArrayGetValueAtIndex(window_array, i)); + CFStringRef window_title = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef window_id = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowNumber)); + CFNumberRef window_layer = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (window_title && window_id && window_layer) { + // Skip windows with layer=0 (menu, dock). + int layer; + CFNumberGetValue(window_layer, kCFNumberIntType, &layer); + if (layer != 0) + continue; + + int id; + CFNumberGetValue(window_id, kCFNumberIntType, &id); + WindowCapturer::Window window; + window.id = id; + if (!CFStringRefToUtf8(window_title, &(window.title)) || + window.title.empty()) { + continue; + } + windows->push_back(window); + } + } + + CFRelease(window_array); + return true; } bool WindowCapturerMac::SelectWindow(WindowId id) { - // Not implemented yet. - return false; + // Request description for the specified window to make sure |id| is valid. + CGWindowID ids[1]; + ids[0] = id; + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + int results_count = window_array ? CFArrayGetCount(window_array) : 0; + CFRelease(window_id_array); + CFRelease(window_array); + + if (results_count == 0) { + // Could not find the window. It might have been closed. + return false; + } + + window_id_ = id; + return true; } void WindowCapturerMac::Start(Callback* callback) { @@ -62,8 +164,33 @@ void WindowCapturerMac::Start(Callback* callback) { } void WindowCapturerMac::Capture(const DesktopRegion& region) { - // Not implemented yet. - callback_->OnCaptureCompleted(NULL); + CGImageRef window_image = CGWindowListCreateImage( + CGRectNull, kCGWindowListOptionIncludingWindow, + window_id_, kCGWindowImageBoundsIgnoreFraming); + + if (!window_image) { + CFRelease(window_image); + callback_->OnCaptureCompleted(NULL); + return; + } + + int bits_per_pixel = CGImageGetBitsPerPixel(window_image); + if (bits_per_pixel != 32) { + LOG(LS_ERROR) << "Unsupported window image depth: " << bits_per_pixel; + CFRelease(window_image); + callback_->OnCaptureCompleted(NULL); + return; + } + + int width = CGImageGetWidth(window_image); + int height = CGImageGetHeight(window_image); + CGDataProviderRef provider = CGImageGetDataProvider(window_image); + DesktopFrame* frame = new CFDataDesktopFrame( + DesktopSize(width, height), CGImageGetBytesPerRow(window_image), + CGDataProviderCopyData(provider)); + CFRelease(window_image); + + callback_->OnCaptureCompleted(frame); } } // namespace diff --git a/webrtc/modules/desktop_capture/window_capturer_unittest.cc b/webrtc/modules/desktop_capture/window_capturer_unittest.cc index c3b15fedb8..2c11d849d9 100644 --- a/webrtc/modules/desktop_capture/window_capturer_unittest.cc +++ b/webrtc/modules/desktop_capture/window_capturer_unittest.cc @@ -42,16 +42,13 @@ class WindowCapturerTest : public testing::Test, scoped_ptr frame_; }; -#if defined(WEBRTC_WIN) +#if defined(WEBRTC_WIN) || defined(WEBRTC_MAC) // Verify that we can enumerate windows. TEST_F(WindowCapturerTest, Enumerate) { WindowCapturer::WindowList windows; EXPECT_TRUE(capturer_->GetWindowList(&windows)); - // Assume that there is at least one window. - EXPECT_GT(windows.size(), 0U); - // Verify that window titles are set. for (WindowCapturer::WindowList::iterator it = windows.begin(); it != windows.end(); ++it) { @@ -95,6 +92,6 @@ TEST_F(WindowCapturerTest, Capture) { } } -#endif // defined(WEBRTC_WIN) +#endif // defined(WEBRTC_WIN) || defined(WEBRTC_MAC) } // namespace webrtc