Implement window capturer for OS X.
R=wez@chromium.org
Review URL: https://webrtc-codereview.appspot.com/2055005
git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@4599 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/modules/desktop_capture/window_capturer_mac.cc b/modules/desktop_capture/window_capturer_mac.cc
index 3cfde7c..e78d95b 100755
--- a/modules/desktop_capture/window_capturer_mac.cc
+++ b/modules/desktop_capture/window_capturer_mac.cc
@@ -11,13 +11,59 @@
#include "webrtc/modules/desktop_capture/window_capturer.h"
#include <assert.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreFoundation/CoreFoundation.h>
#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<UInt8*>(&*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<uint8_t*>(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 @@
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<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, i));
+ CFStringRef window_title = reinterpret_cast<CFStringRef>(
+ CFDictionaryGetValue(window, kCGWindowName));
+ CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowNumber));
+ CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
+ 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<const void **>(&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::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