aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/desktop_capture
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/modules/desktop_capture')
-rw-r--r--webrtc/modules/desktop_capture/BUILD.gn164
-rw-r--r--webrtc/modules/desktop_capture/OWNERS11
-rw-r--r--webrtc/modules/desktop_capture/cropped_desktop_frame.cc45
-rw-r--r--webrtc/modules/desktop_capture/cropped_desktop_frame.h25
-rw-r--r--webrtc/modules/desktop_capture/cropping_window_capturer.cc111
-rw-r--r--webrtc/modules/desktop_capture/cropping_window_capturer.h74
-rw-r--r--webrtc/modules/desktop_capture/cropping_window_capturer_win.cc218
-rw-r--r--webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc174
-rw-r--r--webrtc/modules/desktop_capture/desktop_and_cursor_composer.h63
-rw-r--r--webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc250
-rw-r--r--webrtc/modules/desktop_capture/desktop_capture.gypi160
-rw-r--r--webrtc/modules/desktop_capture/desktop_capture_options.cc44
-rw-r--r--webrtc/modules/desktop_capture/desktop_capture_options.h108
-rw-r--r--webrtc/modules/desktop_capture/desktop_capture_types.h43
-rw-r--r--webrtc/modules/desktop_capture/desktop_capturer.h67
-rw-r--r--webrtc/modules/desktop_capture/desktop_frame.cc94
-rw-r--r--webrtc/modules/desktop_capture/desktop_frame.h126
-rw-r--r--webrtc/modules/desktop_capture/desktop_frame_win.cc63
-rw-r--r--webrtc/modules/desktop_capture/desktop_frame_win.h49
-rw-r--r--webrtc/modules/desktop_capture/desktop_geometry.cc48
-rw-r--r--webrtc/modules/desktop_capture/desktop_geometry.h145
-rw-r--r--webrtc/modules/desktop_capture/desktop_region.cc569
-rw-r--r--webrtc/modules/desktop_capture/desktop_region.h167
-rw-r--r--webrtc/modules/desktop_capture/desktop_region_unittest.cc710
-rw-r--r--webrtc/modules/desktop_capture/differ.cc211
-rw-r--r--webrtc/modules/desktop_capture/differ.h89
-rw-r--r--webrtc/modules/desktop_capture/differ_block.cc61
-rw-r--r--webrtc/modules/desktop_capture/differ_block.h33
-rw-r--r--webrtc/modules/desktop_capture/differ_block_sse2.cc120
-rw-r--r--webrtc/modules/desktop_capture/differ_block_sse2.h33
-rw-r--r--webrtc/modules/desktop_capture/differ_block_unittest.cc87
-rw-r--r--webrtc/modules/desktop_capture/differ_unittest.cc652
-rw-r--r--webrtc/modules/desktop_capture/mac/desktop_configuration.h78
-rw-r--r--webrtc/modules/desktop_capture/mac/desktop_configuration.mm180
-rw-r--r--webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc91
-rw-r--r--webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h66
-rw-r--r--webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc244
-rw-r--r--webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h69
-rw-r--r--webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc55
-rw-r--r--webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h41
-rw-r--r--webrtc/modules/desktop_capture/mac/window_list_utils.cc62
-rw-r--r--webrtc/modules/desktop_capture/mac/window_list_utils.h24
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor.cc38
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor.h48
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_monitor.h90
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm296
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc29
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc131
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc173
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc232
-rw-r--r--webrtc/modules/desktop_capture/mouse_cursor_shape.h17
-rw-r--r--webrtc/modules/desktop_capture/screen_capture_frame_queue.cc44
-rw-r--r--webrtc/modules/desktop_capture/screen_capture_frame_queue.h74
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer.cc36
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer.h93
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_helper.cc104
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_helper.h88
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc188
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_mac.mm982
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc97
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_mock_objects.h47
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_null.cc20
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_unittest.cc142
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_win.cc30
-rw-r--r--webrtc/modules/desktop_capture/screen_capturer_x11.cc444
-rw-r--r--webrtc/modules/desktop_capture/shared_desktop_frame.cc80
-rw-r--r--webrtc/modules/desktop_capture/shared_desktop_frame.h49
-rw-r--r--webrtc/modules/desktop_capture/shared_memory.cc28
-rw-r--r--webrtc/modules/desktop_capture/shared_memory.h68
-rw-r--r--webrtc/modules/desktop_capture/win/cursor.cc248
-rw-r--r--webrtc/modules/desktop_capture/win/cursor.h25
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.curbin0 -> 3262 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.curbin0 -> 4286 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.curbin0 -> 2238 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.curbin0 -> 326 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.curbin0 -> 4286 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.curbin0 -> 4286 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.curbin0 -> 766 bytes
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_unittest.cc89
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_unittest_resources.h24
-rw-r--r--webrtc/modules/desktop_capture/win/cursor_unittest_resources.rc28
-rw-r--r--webrtc/modules/desktop_capture/win/desktop.cc110
-rw-r--r--webrtc/modules/desktop_capture/win/desktop.h64
-rw-r--r--webrtc/modules/desktop_capture/win/scoped_gdi_object.h95
-rw-r--r--webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc57
-rw-r--r--webrtc/modules/desktop_capture/win/scoped_thread_desktop.h53
-rw-r--r--webrtc/modules/desktop_capture/win/screen_capture_utils.cc92
-rw-r--r--webrtc/modules/desktop_capture/win/screen_capture_utils.h35
-rw-r--r--webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc270
-rw-r--r--webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h89
-rw-r--r--webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc449
-rw-r--r--webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h153
-rw-r--r--webrtc/modules/desktop_capture/win/window_capture_utils.cc69
-rw-r--r--webrtc/modules/desktop_capture/win/window_capture_utils.h40
-rw-r--r--webrtc/modules/desktop_capture/window_capturer.cc22
-rw-r--r--webrtc/modules/desktop_capture/window_capturer.h61
-rw-r--r--webrtc/modules/desktop_capture/window_capturer_mac.mm241
-rwxr-xr-xwebrtc/modules/desktop_capture/window_capturer_null.cc82
-rw-r--r--webrtc/modules/desktop_capture/window_capturer_unittest.cc90
-rw-r--r--webrtc/modules/desktop_capture/window_capturer_win.cc257
-rwxr-xr-xwebrtc/modules/desktop_capture/window_capturer_x11.cc436
-rw-r--r--webrtc/modules/desktop_capture/x11/shared_x_display.cc87
-rw-r--r--webrtc/modules/desktop_capture/x11/shared_x_display.h84
-rw-r--r--webrtc/modules/desktop_capture/x11/x_error_trap.cc69
-rw-r--r--webrtc/modules/desktop_capture/x11/x_error_trap.h39
-rw-r--r--webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc337
-rw-r--r--webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h85
107 files changed, 13272 insertions, 0 deletions
diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn
new file mode 100644
index 0000000000..aa33993192
--- /dev/null
+++ b/webrtc/modules/desktop_capture/BUILD.gn
@@ -0,0 +1,164 @@
+# 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.
+
+import("//build/config/ui.gni")
+import("../../build/webrtc.gni")
+
+use_desktop_capture_differ_sse2 =
+ !is_ios && (current_cpu == "x86" || current_cpu == "x64")
+
+source_set("primitives") {
+ sources = [
+ "desktop_capture_types.h",
+ "desktop_frame.cc",
+ "desktop_frame.h",
+ "desktop_geometry.cc",
+ "desktop_geometry.h",
+ "desktop_region.cc",
+ "desktop_region.h",
+ ]
+
+ public_configs = [ "../..:common_inherited_config" ]
+}
+
+source_set("desktop_capture") {
+ sources = [
+ "cropped_desktop_frame.cc",
+ "cropped_desktop_frame.h",
+ "cropping_window_capturer.cc",
+ "cropping_window_capturer.h",
+ "cropping_window_capturer_win.cc",
+ "desktop_and_cursor_composer.cc",
+ "desktop_and_cursor_composer.h",
+ "desktop_capture_options.cc",
+ "desktop_capture_options.h",
+ "desktop_capturer.h",
+ "desktop_capturer.h",
+ "desktop_frame_win.cc",
+ "desktop_frame_win.h",
+ "differ.cc",
+ "differ.h",
+ "differ_block.cc",
+ "differ_block.h",
+ "mac/desktop_configuration.h",
+ "mac/desktop_configuration.mm",
+ "mac/desktop_configuration_monitor.cc",
+ "mac/desktop_configuration_monitor.h",
+ "mac/full_screen_chrome_window_detector.cc",
+ "mac/full_screen_chrome_window_detector.h",
+ "mac/scoped_pixel_buffer_object.cc",
+ "mac/scoped_pixel_buffer_object.h",
+ "mac/window_list_utils.cc",
+ "mac/window_list_utils.h",
+ "mouse_cursor.cc",
+ "mouse_cursor.h",
+ "mouse_cursor_monitor.h",
+ "mouse_cursor_monitor_mac.mm",
+ "mouse_cursor_monitor_win.cc",
+ "screen_capture_frame_queue.cc",
+ "screen_capture_frame_queue.h",
+ "screen_capturer.cc",
+ "screen_capturer.h",
+ "screen_capturer_helper.cc",
+ "screen_capturer_helper.h",
+ "screen_capturer_mac.mm",
+ "screen_capturer_win.cc",
+ "shared_desktop_frame.cc",
+ "shared_desktop_frame.h",
+ "shared_memory.cc",
+ "shared_memory.h",
+ "win/cursor.cc",
+ "win/cursor.h",
+ "win/desktop.cc",
+ "win/desktop.h",
+ "win/scoped_gdi_object.h",
+ "win/scoped_thread_desktop.cc",
+ "win/scoped_thread_desktop.h",
+ "win/screen_capture_utils.cc",
+ "win/screen_capture_utils.h",
+ "win/screen_capturer_win_gdi.cc",
+ "win/screen_capturer_win_gdi.h",
+ "win/screen_capturer_win_magnifier.cc",
+ "win/screen_capturer_win_magnifier.h",
+ "win/window_capture_utils.cc",
+ "win/window_capture_utils.h",
+ "window_capturer.cc",
+ "window_capturer.h",
+ "window_capturer_mac.mm",
+ "window_capturer_win.cc",
+ ]
+
+ if (use_x11) {
+ sources += [
+ "mouse_cursor_monitor_x11.cc",
+ "screen_capturer_x11.cc",
+ "window_capturer_x11.cc",
+ "x11/shared_x_display.cc",
+ "x11/shared_x_display.h",
+ "x11/x_error_trap.cc",
+ "x11/x_error_trap.h",
+ "x11/x_server_pixel_buffer.cc",
+ "x11/x_server_pixel_buffer.h",
+ ]
+ configs += [ "//build/config/linux:x11" ]
+ }
+
+ if (!is_win && !is_mac && !use_x11) {
+ sources += [
+ "mouse_cursor_monitor_null.cc",
+ "screen_capturer_null.cc",
+ "window_capturer_null.cc",
+ ]
+ }
+
+ if (is_mac) {
+ libs = [
+ "AppKit.framework",
+ "IOKit.framework",
+ "OpenGL.framework",
+ ]
+ }
+
+ configs += [ "../..:common_config" ]
+ public_configs = [ "../..:common_inherited_config" ]
+
+ if (is_clang && !is_nacl) {
+ # Suppress warnings from Chrome's Clang plugins.
+ # See http://code.google.com/p/webrtc/issues/detail?id=163 for details.
+ configs -= [ "//build/config/clang:find_bad_constructs" ]
+ }
+
+ deps = [
+ ":primitives",
+ "../../base:rtc_base_approved",
+ "../../system_wrappers",
+ ]
+
+ if (use_desktop_capture_differ_sse2) {
+ deps += [ ":desktop_capture_differ_sse2" ]
+ }
+}
+
+if (use_desktop_capture_differ_sse2) {
+ # Have to be compiled as a separate target because it needs to be compiled
+ # with SSE2 enabled.
+ source_set("desktop_capture_differ_sse2") {
+ visibility = [ ":*" ]
+ sources = [
+ "differ_block_sse2.cc",
+ "differ_block_sse2.h",
+ ]
+
+ configs += [ "../..:common_config" ]
+ public_configs = [ "../..:common_inherited_config" ]
+
+ if (is_posix && !is_mac) {
+ cflags = [ "-msse2" ]
+ }
+ }
+}
diff --git a/webrtc/modules/desktop_capture/OWNERS b/webrtc/modules/desktop_capture/OWNERS
new file mode 100644
index 0000000000..67d2fa1959
--- /dev/null
+++ b/webrtc/modules/desktop_capture/OWNERS
@@ -0,0 +1,11 @@
+alexeypa@chromium.org
+jiayl@webrtc.org
+sergeyu@chromium.org
+wez@chromium.org
+
+# These are for the common case of adding or renaming files. If you're doing
+# structural changes, please get a review from a reviewer in this file.
+per-file *.gyp=*
+per-file *.gypi=*
+
+per-file BUILD.gn=kjellander@webrtc.org
diff --git a/webrtc/modules/desktop_capture/cropped_desktop_frame.cc b/webrtc/modules/desktop_capture/cropped_desktop_frame.cc
new file mode 100644
index 0000000000..2c709733e1
--- /dev/null
+++ b/webrtc/modules/desktop_capture/cropped_desktop_frame.cc
@@ -0,0 +1,45 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/cropped_desktop_frame.h"
+
+namespace webrtc {
+
+// A DesktopFrame that is a sub-rect of another DesktopFrame.
+class CroppedDesktopFrame : public DesktopFrame {
+ public:
+ CroppedDesktopFrame(DesktopFrame* frame, const DesktopRect& rect);
+
+ private:
+ rtc::scoped_ptr<DesktopFrame> frame_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(CroppedDesktopFrame);
+};
+
+DesktopFrame*
+CreateCroppedDesktopFrame(DesktopFrame* frame, const DesktopRect& rect) {
+ if (!DesktopRect::MakeSize(frame->size()).ContainsRect(rect)) {
+ delete frame;
+ return NULL;
+ }
+
+ return new CroppedDesktopFrame(frame, rect);
+}
+
+CroppedDesktopFrame::CroppedDesktopFrame(DesktopFrame* frame,
+ const DesktopRect& rect)
+ : DesktopFrame(rect.size(),
+ frame->stride(),
+ frame->GetFrameDataAtPos(rect.top_left()),
+ frame->shared_memory()),
+ frame_(frame) {
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/cropped_desktop_frame.h b/webrtc/modules/desktop_capture/cropped_desktop_frame.h
new file mode 100644
index 0000000000..29449e27f3
--- /dev/null
+++ b/webrtc/modules/desktop_capture/cropped_desktop_frame.h
@@ -0,0 +1,25 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace webrtc {
+
+// Always takes ownership of |frame|. Returns NULL if |rect| is not contained
+// by the bounds of |frame|.
+DesktopFrame* CreateCroppedDesktopFrame(DesktopFrame* frame,
+ const DesktopRect& rect);
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_
+
diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer.cc b/webrtc/modules/desktop_capture/cropping_window_capturer.cc
new file mode 100644
index 0000000000..1e37e782ff
--- /dev/null
+++ b/webrtc/modules/desktop_capture/cropping_window_capturer.cc
@@ -0,0 +1,111 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/cropping_window_capturer.h"
+
+#include "webrtc/modules/desktop_capture/cropped_desktop_frame.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+CroppingWindowCapturer::CroppingWindowCapturer(
+ const DesktopCaptureOptions& options)
+ : options_(options),
+ callback_(NULL),
+ window_capturer_(WindowCapturer::Create(options)),
+ selected_window_(kNullWindowId),
+ excluded_window_(kNullWindowId) {
+}
+
+CroppingWindowCapturer::~CroppingWindowCapturer() {}
+
+void CroppingWindowCapturer::Start(DesktopCapturer::Callback* callback) {
+ callback_ = callback;
+ window_capturer_->Start(callback);
+}
+
+void CroppingWindowCapturer::Capture(const DesktopRegion& region) {
+ if (ShouldUseScreenCapturer()) {
+ if (!screen_capturer_.get()) {
+ screen_capturer_.reset(ScreenCapturer::Create(options_));
+ if (excluded_window_) {
+ screen_capturer_->SetExcludedWindow(excluded_window_);
+ }
+ screen_capturer_->Start(this);
+ }
+ screen_capturer_->Capture(region);
+ } else {
+ window_capturer_->Capture(region);
+ }
+}
+
+void CroppingWindowCapturer::SetExcludedWindow(WindowId window) {
+ excluded_window_ = window;
+ if (screen_capturer_.get()) {
+ screen_capturer_->SetExcludedWindow(window);
+ }
+}
+
+bool CroppingWindowCapturer::GetWindowList(WindowList* windows) {
+ return window_capturer_->GetWindowList(windows);
+}
+
+bool CroppingWindowCapturer::SelectWindow(WindowId id) {
+ if (window_capturer_->SelectWindow(id)) {
+ selected_window_ = id;
+ return true;
+ }
+ return false;
+}
+
+bool CroppingWindowCapturer::BringSelectedWindowToFront() {
+ return window_capturer_->BringSelectedWindowToFront();
+}
+
+SharedMemory* CroppingWindowCapturer::CreateSharedMemory(size_t size) {
+ return callback_->CreateSharedMemory(size);
+}
+
+void CroppingWindowCapturer::OnCaptureCompleted(DesktopFrame* frame) {
+ rtc::scoped_ptr<DesktopFrame> screen_frame(frame);
+
+ if (!ShouldUseScreenCapturer()) {
+ LOG(LS_INFO) << "Window no longer on top when ScreenCapturer finishes";
+ window_capturer_->Capture(DesktopRegion());
+ return;
+ }
+
+ if (!frame) {
+ LOG(LS_WARNING) << "ScreenCapturer failed to capture a frame";
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ DesktopRect window_rect = GetWindowRectInVirtualScreen();
+ if (window_rect.is_empty()) {
+ LOG(LS_WARNING) << "Window rect is empty";
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ rtc::scoped_ptr<DesktopFrame> window_frame(
+ CreateCroppedDesktopFrame(screen_frame.release(), window_rect));
+ callback_->OnCaptureCompleted(window_frame.release());
+}
+
+#if !defined(WEBRTC_WIN)
+// static
+WindowCapturer*
+CroppingWindowCapturer::Create(const DesktopCaptureOptions& options) {
+ return WindowCapturer::Create(options);
+}
+#endif
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer.h b/webrtc/modules/desktop_capture/cropping_window_capturer.h
new file mode 100644
index 0000000000..e0f598c7ea
--- /dev/null
+++ b/webrtc/modules/desktop_capture/cropping_window_capturer.h
@@ -0,0 +1,74 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/screen_capturer.h"
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+
+namespace webrtc {
+
+// WindowCapturer implementation that uses a screen capturer to capture the
+// whole screen and crops the video frame to the window area when the captured
+// window is on top.
+class CroppingWindowCapturer : public WindowCapturer,
+ public DesktopCapturer::Callback {
+ public:
+ static WindowCapturer* Create(const DesktopCaptureOptions& options);
+ virtual ~CroppingWindowCapturer();
+
+ // DesktopCapturer implementation.
+ void Start(DesktopCapturer::Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+ void SetExcludedWindow(WindowId window) override;
+
+ // WindowCapturer implementation.
+ bool GetWindowList(WindowList* windows) override;
+ bool SelectWindow(WindowId id) override;
+ bool BringSelectedWindowToFront() override;
+
+ // DesktopCapturer::Callback implementation, passed to |screen_capturer_| to
+ // intercept the capture result.
+ SharedMemory* CreateSharedMemory(size_t size) override;
+ void OnCaptureCompleted(DesktopFrame* frame) override;
+
+ protected:
+ explicit CroppingWindowCapturer(const DesktopCaptureOptions& options);
+
+ // The platform implementation should override these methods.
+
+ // Returns true if it is OK to capture the whole screen and crop to the
+ // selected window, i.e. the selected window is opaque, rectangular, and not
+ // occluded.
+ virtual bool ShouldUseScreenCapturer() = 0;
+
+ // Returns the window area relative to the top left of the virtual screen
+ // within the bounds of the virtual screen.
+ virtual DesktopRect GetWindowRectInVirtualScreen() = 0;
+
+ WindowId selected_window() const { return selected_window_; }
+ WindowId excluded_window() const { return excluded_window_; }
+
+ private:
+ DesktopCaptureOptions options_;
+ DesktopCapturer::Callback* callback_;
+ rtc::scoped_ptr<WindowCapturer> window_capturer_;
+ rtc::scoped_ptr<ScreenCapturer> screen_capturer_;
+ WindowId selected_window_;
+ WindowId excluded_window_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_
+
diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc
new file mode 100644
index 0000000000..fe696eba67
--- /dev/null
+++ b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc
@@ -0,0 +1,218 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/cropping_window_capturer.h"
+
+#include "webrtc/base/win32.h"
+#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
+#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
+#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+// Used to pass input/output data during the EnumWindow call for verifying if
+// the selected window is on top.
+struct TopWindowVerifierContext {
+ TopWindowVerifierContext(HWND selected_window, HWND excluded_window)
+ : selected_window(selected_window),
+ excluded_window(excluded_window),
+ is_top_window(false),
+ selected_window_process_id(0) {}
+
+ HWND selected_window;
+ HWND excluded_window;
+ bool is_top_window;
+ DWORD selected_window_process_id;
+ DesktopRect selected_window_rect;
+};
+
+// The function is called during EnumWindow for every window enumerated and is
+// responsible for verifying if the selected window is on top.
+BOOL CALLBACK TopWindowVerifier(HWND hwnd, LPARAM param) {
+ TopWindowVerifierContext* context =
+ reinterpret_cast<TopWindowVerifierContext*>(param);
+
+ if (hwnd == context->selected_window) {
+ context->is_top_window = true;
+ return FALSE;
+ }
+
+ // Ignore the excluded window.
+ if (hwnd == context->excluded_window) {
+ return TRUE;
+ }
+
+ // Ignore hidden or minimized window.
+ if (IsIconic(hwnd) || !IsWindowVisible(hwnd)) {
+ return TRUE;
+ }
+
+ // Ignore descendant/owned windows since we want to capture them.
+ // This check does not work for tooltips and context menus. Drop down menus
+ // and popup windows are fine.
+ if (GetAncestor(hwnd, GA_ROOTOWNER) == context->selected_window) {
+ return TRUE;
+ }
+
+ // If |hwnd| has no title and belongs to the same process, assume it's a
+ // tooltip or context menu from the selected window and ignore it.
+ const size_t kTitleLength = 32;
+ WCHAR window_title[kTitleLength];
+ GetWindowText(hwnd, window_title, kTitleLength);
+ if (wcsnlen_s(window_title, kTitleLength) == 0) {
+ DWORD enumerated_process;
+ GetWindowThreadProcessId(hwnd, &enumerated_process);
+ if (!context->selected_window_process_id) {
+ GetWindowThreadProcessId(context->selected_window,
+ &context->selected_window_process_id);
+ }
+ if (context->selected_window_process_id == enumerated_process) {
+ return TRUE;
+ }
+ }
+
+ // Check if the enumerated window intersects with the selected window.
+ RECT enumerated_rect;
+ if (!GetWindowRect(hwnd, &enumerated_rect)) {
+ // Bail out if failed to get the window area.
+ context->is_top_window = false;
+ return FALSE;
+ }
+
+ DesktopRect intersect_rect = context->selected_window_rect;
+ DesktopRect enumerated_desktop_rect =
+ DesktopRect::MakeLTRB(enumerated_rect.left,
+ enumerated_rect.top,
+ enumerated_rect.right,
+ enumerated_rect.bottom);
+ intersect_rect.IntersectWith(enumerated_desktop_rect);
+
+ // If intersection is not empty, the selected window is not on top.
+ if (!intersect_rect.is_empty()) {
+ context->is_top_window = false;
+ return FALSE;
+ }
+ // Otherwise, keep enumerating.
+ return TRUE;
+}
+
+class CroppingWindowCapturerWin : public CroppingWindowCapturer {
+ public:
+ CroppingWindowCapturerWin(
+ const DesktopCaptureOptions& options)
+ : CroppingWindowCapturer(options) {}
+
+ private:
+ bool ShouldUseScreenCapturer() override;
+ DesktopRect GetWindowRectInVirtualScreen() override;
+
+ // The region from GetWindowRgn in the desktop coordinate if the region is
+ // rectangular, or the rect from GetWindowRect if the region is not set.
+ DesktopRect window_region_rect_;
+
+ AeroChecker aero_checker_;
+};
+
+bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
+ if (!rtc::IsWindows8OrLater() && aero_checker_.IsAeroEnabled())
+ return false;
+
+ // Check if the window is a translucent layered window.
+ HWND selected = reinterpret_cast<HWND>(selected_window());
+ LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
+ if (window_ex_style & WS_EX_LAYERED) {
+ COLORREF color_ref_key = 0;
+ BYTE alpha = 0;
+ DWORD flags = 0;
+
+ // GetLayeredWindowAttributes fails if the window was setup with
+ // UpdateLayeredWindow. We have no way to know the opacity of the window in
+ // that case. This happens for Stiky Note (crbug/412726).
+ if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
+ return false;
+
+ // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
+ // the previous GetLayeredWindowAttributes to fail. So we only need to check
+ // the window wide color key or alpha.
+ if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255)))
+ return false;
+ }
+
+ TopWindowVerifierContext context(
+ selected, reinterpret_cast<HWND>(excluded_window()));
+
+ RECT selected_window_rect;
+ if (!GetWindowRect(selected, &selected_window_rect)) {
+ return false;
+ }
+ context.selected_window_rect = DesktopRect::MakeLTRB(
+ selected_window_rect.left,
+ selected_window_rect.top,
+ selected_window_rect.right,
+ selected_window_rect.bottom);
+
+ // Get the window region and check if it is rectangular.
+ win::ScopedGDIObject<HRGN, win::DeleteObjectTraits<HRGN> >
+ scoped_hrgn(CreateRectRgn(0, 0, 0, 0));
+ int region_type = GetWindowRgn(selected, scoped_hrgn.Get());
+
+ // Do not use the screen capturer if the region is empty or not rectangular.
+ if (region_type == COMPLEXREGION || region_type == NULLREGION)
+ return false;
+
+ if (region_type == SIMPLEREGION) {
+ RECT region_rect;
+ GetRgnBox(scoped_hrgn.Get(), &region_rect);
+ DesktopRect rgn_rect =
+ DesktopRect::MakeLTRB(region_rect.left,
+ region_rect.top,
+ region_rect.right,
+ region_rect.bottom);
+ rgn_rect.Translate(context.selected_window_rect.left(),
+ context.selected_window_rect.top());
+ context.selected_window_rect.IntersectWith(rgn_rect);
+ }
+ window_region_rect_ = context.selected_window_rect;
+
+ // Check if the window is occluded by any other window, excluding the child
+ // windows, context menus, and |excluded_window_|.
+ EnumWindows(&TopWindowVerifier, reinterpret_cast<LPARAM>(&context));
+ return context.is_top_window;
+}
+
+DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
+ DesktopRect original_rect;
+ DesktopRect window_rect;
+ HWND hwnd = reinterpret_cast<HWND>(selected_window());
+ if (!GetCroppedWindowRect(hwnd, &window_rect, &original_rect)) {
+ LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
+ return window_rect;
+ }
+ window_rect.IntersectWith(window_region_rect_);
+
+ // Convert |window_rect| to be relative to the top-left of the virtual screen.
+ DesktopRect screen_rect(GetScreenRect(kFullDesktopScreenId, L""));
+ window_rect.IntersectWith(screen_rect);
+ window_rect.Translate(-screen_rect.left(), -screen_rect.top());
+ return window_rect;
+}
+
+} // namespace
+
+// static
+WindowCapturer*
+CroppingWindowCapturer::Create(const DesktopCaptureOptions& options) {
+ return new CroppingWindowCapturerWin(options);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
new file mode 100644
index 0000000000..74d25d4e1f
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
@@ -0,0 +1,174 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_and_cursor_composer.h"
+
+#include <string.h>
+
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+
+namespace webrtc {
+
+namespace {
+
+// Helper function that blends one image into another. Source image must be
+// pre-multiplied with the alpha channel. Destination is assumed to be opaque.
+void AlphaBlend(uint8_t* dest, int dest_stride,
+ const uint8_t* src, int src_stride,
+ const DesktopSize& size) {
+ for (int y = 0; y < size.height(); ++y) {
+ for (int x = 0; x < size.width(); ++x) {
+ uint32_t base_alpha = 255 - src[x * DesktopFrame::kBytesPerPixel + 3];
+ if (base_alpha == 255) {
+ continue;
+ } else if (base_alpha == 0) {
+ memcpy(dest + x * DesktopFrame::kBytesPerPixel,
+ src + x * DesktopFrame::kBytesPerPixel,
+ DesktopFrame::kBytesPerPixel);
+ } else {
+ dest[x * DesktopFrame::kBytesPerPixel] =
+ dest[x * DesktopFrame::kBytesPerPixel] * base_alpha / 255 +
+ src[x * DesktopFrame::kBytesPerPixel];
+ dest[x * DesktopFrame::kBytesPerPixel + 1] =
+ dest[x * DesktopFrame::kBytesPerPixel + 1] * base_alpha / 255 +
+ src[x * DesktopFrame::kBytesPerPixel + 1];
+ dest[x * DesktopFrame::kBytesPerPixel + 2] =
+ dest[x * DesktopFrame::kBytesPerPixel + 2] * base_alpha / 255 +
+ src[x * DesktopFrame::kBytesPerPixel + 2];
+ }
+ }
+ src += src_stride;
+ dest += dest_stride;
+ }
+}
+
+// DesktopFrame wrapper that draws mouse on a frame and restores original
+// content before releasing the underlying frame.
+class DesktopFrameWithCursor : public DesktopFrame {
+ public:
+ // Takes ownership of |frame|.
+ DesktopFrameWithCursor(DesktopFrame* frame,
+ const MouseCursor& cursor,
+ const DesktopVector& position);
+ virtual ~DesktopFrameWithCursor();
+
+ private:
+ rtc::scoped_ptr<DesktopFrame> original_frame_;
+
+ DesktopVector restore_position_;
+ rtc::scoped_ptr<DesktopFrame> restore_frame_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(DesktopFrameWithCursor);
+};
+
+DesktopFrameWithCursor::DesktopFrameWithCursor(DesktopFrame* frame,
+ const MouseCursor& cursor,
+ const DesktopVector& position)
+ : DesktopFrame(frame->size(), frame->stride(),
+ frame->data(), frame->shared_memory()),
+ original_frame_(frame) {
+ set_dpi(frame->dpi());
+ set_capture_time_ms(frame->capture_time_ms());
+ mutable_updated_region()->Swap(frame->mutable_updated_region());
+
+ DesktopVector image_pos = position.subtract(cursor.hotspot());
+ DesktopRect target_rect = DesktopRect::MakeSize(cursor.image()->size());
+ target_rect.Translate(image_pos);
+ DesktopVector target_origin = target_rect.top_left();
+ target_rect.IntersectWith(DesktopRect::MakeSize(size()));
+
+ if (target_rect.is_empty())
+ return;
+
+ // Copy original screen content under cursor to |restore_frame_|.
+ restore_position_ = target_rect.top_left();
+ restore_frame_.reset(new BasicDesktopFrame(target_rect.size()));
+ restore_frame_->CopyPixelsFrom(*this, target_rect.top_left(),
+ DesktopRect::MakeSize(restore_frame_->size()));
+
+ // Blit the cursor.
+ uint8_t* target_rect_data = reinterpret_cast<uint8_t*>(data()) +
+ target_rect.top() * stride() +
+ target_rect.left() * DesktopFrame::kBytesPerPixel;
+ DesktopVector origin_shift = target_rect.top_left().subtract(target_origin);
+ AlphaBlend(target_rect_data, stride(),
+ cursor.image()->data() +
+ origin_shift.y() * cursor.image()->stride() +
+ origin_shift.x() * DesktopFrame::kBytesPerPixel,
+ cursor.image()->stride(),
+ target_rect.size());
+}
+
+DesktopFrameWithCursor::~DesktopFrameWithCursor() {
+ // Restore original content of the frame.
+ if (restore_frame_.get()) {
+ DesktopRect target_rect = DesktopRect::MakeSize(restore_frame_->size());
+ target_rect.Translate(restore_position_);
+ CopyPixelsFrom(restore_frame_->data(), restore_frame_->stride(),
+ target_rect);
+ }
+}
+
+} // namespace
+
+DesktopAndCursorComposer::DesktopAndCursorComposer(
+ DesktopCapturer* desktop_capturer,
+ MouseCursorMonitor* mouse_monitor)
+ : desktop_capturer_(desktop_capturer),
+ mouse_monitor_(mouse_monitor) {
+}
+
+DesktopAndCursorComposer::~DesktopAndCursorComposer() {}
+
+void DesktopAndCursorComposer::Start(DesktopCapturer::Callback* callback) {
+ callback_ = callback;
+ if (mouse_monitor_.get())
+ mouse_monitor_->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
+ desktop_capturer_->Start(this);
+}
+
+void DesktopAndCursorComposer::Capture(const DesktopRegion& region) {
+ if (mouse_monitor_.get())
+ mouse_monitor_->Capture();
+ desktop_capturer_->Capture(region);
+}
+
+void DesktopAndCursorComposer::SetExcludedWindow(WindowId window) {
+ desktop_capturer_->SetExcludedWindow(window);
+}
+
+SharedMemory* DesktopAndCursorComposer::CreateSharedMemory(size_t size) {
+ return callback_->CreateSharedMemory(size);
+}
+
+void DesktopAndCursorComposer::OnCaptureCompleted(DesktopFrame* frame) {
+ if (frame && cursor_.get() && cursor_state_ == MouseCursorMonitor::INSIDE) {
+ DesktopFrameWithCursor* frame_with_cursor =
+ new DesktopFrameWithCursor(frame, *cursor_, cursor_position_);
+ frame = frame_with_cursor;
+ }
+
+ callback_->OnCaptureCompleted(frame);
+}
+
+void DesktopAndCursorComposer::OnMouseCursor(MouseCursor* cursor) {
+ cursor_.reset(cursor);
+}
+
+void DesktopAndCursorComposer::OnMouseCursorPosition(
+ MouseCursorMonitor::CursorState state,
+ const DesktopVector& position) {
+ cursor_state_ = state;
+ cursor_position_ = position;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
new file mode 100644
index 0000000000..7a72031c79
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
@@ -0,0 +1,63 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+
+namespace webrtc {
+
+// A wrapper for DesktopCapturer that also captures mouse using specified
+// MouseCursorMonitor and renders it on the generated streams.
+class DesktopAndCursorComposer : public DesktopCapturer,
+ public DesktopCapturer::Callback,
+ public MouseCursorMonitor::Callback {
+ public:
+ // Creates a new blender that captures mouse cursor using |mouse_monitor| and
+ // renders it into the frames generated by |desktop_capturer|. If
+ // |mouse_monitor| is NULL the frames are passed unmodified. Takes ownership
+ // of both arguments.
+ DesktopAndCursorComposer(DesktopCapturer* desktop_capturer,
+ MouseCursorMonitor* mouse_monitor);
+ virtual ~DesktopAndCursorComposer();
+
+ // DesktopCapturer interface.
+ void Start(DesktopCapturer::Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+ void SetExcludedWindow(WindowId window) override;
+
+ private:
+ // DesktopCapturer::Callback interface.
+ SharedMemory* CreateSharedMemory(size_t size) override;
+ void OnCaptureCompleted(DesktopFrame* frame) override;
+
+ // MouseCursorMonitor::Callback interface.
+ void OnMouseCursor(MouseCursor* cursor) override;
+ void OnMouseCursorPosition(MouseCursorMonitor::CursorState state,
+ const DesktopVector& position) override;
+
+ rtc::scoped_ptr<DesktopCapturer> desktop_capturer_;
+ rtc::scoped_ptr<MouseCursorMonitor> mouse_monitor_;
+
+ DesktopCapturer::Callback* callback_;
+
+ rtc::scoped_ptr<MouseCursor> cursor_;
+ MouseCursorMonitor::CursorState cursor_state_;
+ DesktopVector cursor_position_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(DesktopAndCursorComposer);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
new file mode 100644
index 0000000000..bee9e087f9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
@@ -0,0 +1,250 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_and_cursor_composer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+const int kScreenWidth = 100;
+const int kScreenHeight = 100;
+const int kCursorWidth = 10;
+const int kCursorHeight = 10;
+
+const int kTestCursorSize = 3;
+const uint32_t kTestCursorData[kTestCursorSize][kTestCursorSize] = {
+ { 0xffffffff, 0x99990000, 0xaa222222, },
+ { 0x88008800, 0xaa0000aa, 0xaa333333, },
+ { 0x00000000, 0xaa0000aa, 0xaa333333, },
+};
+
+uint32_t GetFakeFramePixelValue(const DesktopVector& p) {
+ uint32_t r = 100 + p.x();
+ uint32_t g = 100 + p.y();
+ uint32_t b = 100 + p.x() + p.y();
+ return b + (g << 8) + (r << 16) + 0xff000000;
+}
+
+uint32_t GetFramePixel(const DesktopFrame& frame, const DesktopVector& pos) {
+ return *reinterpret_cast<uint32_t*>(frame.data() + pos.y() * frame.stride() +
+ pos.x() * DesktopFrame::kBytesPerPixel);
+}
+
+// Blends two pixel values taking into account alpha.
+uint32_t BlendPixels(uint32_t dest, uint32_t src) {
+ uint8_t alpha = 255 - ((src & 0xff000000) >> 24);
+ uint32_t r =
+ ((dest & 0x00ff0000) >> 16) * alpha / 255 + ((src & 0x00ff0000) >> 16);
+ uint32_t g =
+ ((dest & 0x0000ff00) >> 8) * alpha / 255 + ((src & 0x0000ff00) >> 8);
+ uint32_t b = (dest & 0x000000ff) * alpha / 255 + (src & 0x000000ff);
+ return b + (g << 8) + (r << 16) + 0xff000000;
+}
+
+DesktopFrame* CreateTestFrame() {
+ DesktopFrame* frame =
+ new BasicDesktopFrame(DesktopSize(kScreenWidth, kScreenHeight));
+ uint32_t* data = reinterpret_cast<uint32_t*>(frame->data());
+ for (int y = 0; y < kScreenHeight; ++y) {
+ for (int x = 0; x < kScreenWidth; ++x) {
+ *(data++) = GetFakeFramePixelValue(DesktopVector(x, y));
+ }
+ }
+ return frame;
+}
+
+class FakeScreenCapturer : public DesktopCapturer {
+ public:
+ FakeScreenCapturer() {}
+
+ void Start(Callback* callback) override { callback_ = callback; }
+
+ void Capture(const DesktopRegion& region) override {
+ callback_->OnCaptureCompleted(next_frame_.release());
+ }
+
+ void SetNextFrame(DesktopFrame* next_frame) {
+ next_frame_.reset(next_frame);
+ }
+
+ private:
+ Callback* callback_;
+
+ rtc::scoped_ptr<DesktopFrame> next_frame_;
+};
+
+class FakeMouseMonitor : public MouseCursorMonitor {
+ public:
+ FakeMouseMonitor() : changed_(true) {}
+
+ void SetState(CursorState state, const DesktopVector& pos) {
+ state_ = state;
+ position_ = pos;
+ }
+
+ void SetHotspot(const DesktopVector& hotspot) {
+ if (!hotspot_.equals(hotspot))
+ changed_ = true;
+ hotspot_ = hotspot;
+ }
+
+ void Init(Callback* callback, Mode mode) override { callback_ = callback; }
+
+ void Capture() override {
+ if (changed_) {
+ rtc::scoped_ptr<DesktopFrame> image(
+ new BasicDesktopFrame(DesktopSize(kCursorWidth, kCursorHeight)));
+ uint32_t* data = reinterpret_cast<uint32_t*>(image->data());
+ memset(data, 0, image->stride() * kCursorHeight);
+
+ // Set four pixels near the hotspot and leave all other blank.
+ for (int y = 0; y < kTestCursorSize; ++y) {
+ for (int x = 0; x < kTestCursorSize; ++x) {
+ data[(hotspot_.y() + y) * kCursorWidth + (hotspot_.x() + x)] =
+ kTestCursorData[y][x];
+ }
+ }
+
+ callback_->OnMouseCursor(new MouseCursor(image.release(), hotspot_));
+ }
+
+ callback_->OnMouseCursorPosition(state_, position_);
+ }
+
+ private:
+ Callback* callback_;
+ CursorState state_;
+ DesktopVector position_;
+ DesktopVector hotspot_;
+ bool changed_;
+};
+
+void VerifyFrame(const DesktopFrame& frame,
+ MouseCursorMonitor::CursorState state,
+ const DesktopVector& pos) {
+ // Verify that all other pixels are set to their original values.
+ DesktopRect image_rect =
+ DesktopRect::MakeWH(kTestCursorSize, kTestCursorSize);
+ image_rect.Translate(pos);
+
+ for (int y = 0; y < kScreenHeight; ++y) {
+ for (int x = 0; x < kScreenWidth; ++x) {
+ DesktopVector p(x, y);
+ if (state == MouseCursorMonitor::INSIDE && image_rect.Contains(p)) {
+ EXPECT_EQ(BlendPixels(GetFakeFramePixelValue(p),
+ kTestCursorData[y - pos.y()][x - pos.x()]),
+ GetFramePixel(frame, p));
+ } else {
+ EXPECT_EQ(GetFakeFramePixelValue(p), GetFramePixel(frame, p));
+ }
+ }
+ }
+}
+
+class DesktopAndCursorComposerTest : public testing::Test,
+ public DesktopCapturer::Callback {
+ public:
+ DesktopAndCursorComposerTest()
+ : fake_screen_(new FakeScreenCapturer()),
+ fake_cursor_(new FakeMouseMonitor()),
+ blender_(fake_screen_, fake_cursor_) {
+ }
+
+ // DesktopCapturer::Callback interface
+ SharedMemory* CreateSharedMemory(size_t size) override { return NULL; }
+
+ void OnCaptureCompleted(DesktopFrame* frame) override { frame_.reset(frame); }
+
+ protected:
+ // Owned by |blender_|.
+ FakeScreenCapturer* fake_screen_;
+ FakeMouseMonitor* fake_cursor_;
+
+ DesktopAndCursorComposer blender_;
+ rtc::scoped_ptr<DesktopFrame> frame_;
+};
+
+// Verify DesktopAndCursorComposer can handle the case when the screen capturer
+// fails.
+TEST_F(DesktopAndCursorComposerTest, Error) {
+ blender_.Start(this);
+
+ fake_cursor_->SetHotspot(DesktopVector());
+ fake_cursor_->SetState(MouseCursorMonitor::INSIDE, DesktopVector());
+ fake_screen_->SetNextFrame(NULL);
+
+ blender_.Capture(DesktopRegion());
+
+ EXPECT_EQ(frame_, static_cast<DesktopFrame*>(NULL));
+}
+
+TEST_F(DesktopAndCursorComposerTest, Blend) {
+ struct {
+ int x, y;
+ int hotspot_x, hotspot_y;
+ bool inside;
+ } tests[] = {
+ {0, 0, 0, 0, true},
+ {50, 50, 0, 0, true},
+ {100, 50, 0, 0, true},
+ {50, 100, 0, 0, true},
+ {100, 100, 0, 0, true},
+ {0, 0, 2, 5, true},
+ {1, 1, 2, 5, true},
+ {50, 50, 2, 5, true},
+ {100, 100, 2, 5, true},
+ {0, 0, 5, 2, true},
+ {50, 50, 5, 2, true},
+ {100, 100, 5, 2, true},
+ {0, 0, 0, 0, false},
+ };
+
+ blender_.Start(this);
+
+ for (size_t i = 0; i < (sizeof(tests) / sizeof(tests[0])); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopVector hotspot(tests[i].hotspot_x, tests[i].hotspot_y);
+ fake_cursor_->SetHotspot(hotspot);
+
+ MouseCursorMonitor::CursorState state = tests[i].inside
+ ? MouseCursorMonitor::INSIDE
+ : MouseCursorMonitor::OUTSIDE;
+ DesktopVector pos(tests[i].x, tests[i].y);
+ fake_cursor_->SetState(state, pos);
+
+ rtc::scoped_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ fake_screen_->SetNextFrame(frame->Share());
+
+ blender_.Capture(DesktopRegion());
+
+ VerifyFrame(*frame_, state, pos);
+
+ // Verify that the cursor is erased before the frame buffer is returned to
+ // the screen capturer.
+ frame_.reset();
+ VerifyFrame(*frame, MouseCursorMonitor::OUTSIDE, DesktopVector());
+ }
+}
+
+} // namespace
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi
new file mode 100644
index 0000000000..b92447c349
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_capture.gypi
@@ -0,0 +1,160 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'desktop_capture',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ '<(webrtc_root)/base/base.gyp:rtc_base',
+ ],
+ 'sources': [
+ 'cropped_desktop_frame.cc',
+ 'cropped_desktop_frame.h',
+ 'cropping_window_capturer.cc',
+ 'cropping_window_capturer.h',
+ 'cropping_window_capturer_win.cc',
+ "desktop_and_cursor_composer.cc",
+ "desktop_and_cursor_composer.h",
+ "desktop_capture_types.h",
+ "desktop_capturer.h",
+ "desktop_frame.cc",
+ "desktop_frame.h",
+ "desktop_frame_win.cc",
+ "desktop_frame_win.h",
+ "desktop_geometry.cc",
+ "desktop_geometry.h",
+ "desktop_capture_options.h",
+ "desktop_capture_options.cc",
+ "desktop_capturer.h",
+ "desktop_region.cc",
+ "desktop_region.h",
+ "differ.cc",
+ "differ.h",
+ "differ_block.cc",
+ "differ_block.h",
+ "mac/desktop_configuration.h",
+ "mac/desktop_configuration.mm",
+ "mac/desktop_configuration_monitor.h",
+ "mac/desktop_configuration_monitor.cc",
+ "mac/full_screen_chrome_window_detector.cc",
+ "mac/full_screen_chrome_window_detector.h",
+ "mac/scoped_pixel_buffer_object.cc",
+ "mac/scoped_pixel_buffer_object.h",
+ "mac/window_list_utils.cc",
+ "mac/window_list_utils.h",
+ "mouse_cursor.cc",
+ "mouse_cursor.h",
+ "mouse_cursor_monitor.h",
+ "mouse_cursor_monitor_mac.mm",
+ "mouse_cursor_monitor_win.cc",
+ "mouse_cursor_monitor_x11.cc",
+ "screen_capture_frame_queue.cc",
+ "screen_capture_frame_queue.h",
+ "screen_capturer.cc",
+ "screen_capturer.h",
+ "screen_capturer_helper.cc",
+ "screen_capturer_helper.h",
+ "screen_capturer_mac.mm",
+ "screen_capturer_win.cc",
+ "screen_capturer_x11.cc",
+ "shared_desktop_frame.cc",
+ "shared_desktop_frame.h",
+ "shared_memory.cc",
+ "shared_memory.h",
+ "win/cursor.cc",
+ "win/cursor.h",
+ "win/desktop.cc",
+ "win/desktop.h",
+ "win/scoped_gdi_object.h",
+ "win/scoped_thread_desktop.cc",
+ "win/scoped_thread_desktop.h",
+ "win/screen_capturer_win_gdi.cc",
+ "win/screen_capturer_win_gdi.h",
+ "win/screen_capturer_win_magnifier.cc",
+ "win/screen_capturer_win_magnifier.h",
+ "win/screen_capture_utils.cc",
+ "win/screen_capture_utils.h",
+ "win/window_capture_utils.cc",
+ "win/window_capture_utils.h",
+ "window_capturer.cc",
+ "window_capturer.h",
+ "window_capturer_mac.mm",
+ "window_capturer_win.cc",
+ "window_capturer_x11.cc",
+ "x11/shared_x_display.h",
+ "x11/shared_x_display.cc",
+ "x11/x_error_trap.cc",
+ "x11/x_error_trap.h",
+ "x11/x_server_pixel_buffer.cc",
+ "x11/x_server_pixel_buffer.h",
+ ],
+ 'conditions': [
+ ['OS!="ios" and (target_arch=="ia32" or target_arch=="x64")', {
+ 'dependencies': [
+ 'desktop_capture_differ_sse2',
+ ],
+ }],
+ ['use_x11 == 1', {
+ 'link_settings': {
+ 'libraries': [
+ '-lX11',
+ '-lXcomposite',
+ '-lXdamage',
+ '-lXext',
+ '-lXfixes',
+ '-lXrender',
+ ],
+ },
+ }],
+ ['OS!="win" and OS!="mac" and use_x11==0', {
+ 'sources': [
+ "mouse_cursor_monitor_null.cc",
+ "screen_capturer_null.cc",
+ "window_capturer_null.cc",
+ ],
+ }],
+ ['OS=="mac"', {
+ 'link_settings': {
+ 'libraries': [
+ '$(SDKROOT)/System/Library/Frameworks/AppKit.framework',
+ '$(SDKROOT)/System/Library/Frameworks/IOKit.framework',
+ '$(SDKROOT)/System/Library/Frameworks/OpenGL.framework',
+ ],
+ },
+ }],
+ ],
+ },
+ ], # targets
+ 'conditions': [
+ ['OS!="ios" and (target_arch=="ia32" or target_arch=="x64")', {
+ 'targets': [
+ {
+ # Have to be compiled as a separate target because it needs to be
+ # compiled with SSE2 enabled.
+ 'target_name': 'desktop_capture_differ_sse2',
+ 'type': 'static_library',
+ 'sources': [
+ "differ_block_sse2.cc",
+ "differ_block_sse2.h",
+ ],
+ 'conditions': [
+ ['os_posix==1', {
+ 'cflags': [ '-msse2', ],
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [ '-msse2', ],
+ },
+ }],
+ ],
+ },
+ ], # targets
+ }],
+ ],
+}
diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.cc b/webrtc/modules/desktop_capture/desktop_capture_options.cc
new file mode 100644
index 0000000000..b7f123b9d1
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_capture_options.cc
@@ -0,0 +1,44 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_capture_options.h"
+
+namespace webrtc {
+
+DesktopCaptureOptions::DesktopCaptureOptions()
+ : use_update_notifications_(true),
+ disable_effects_(true) {
+#if defined(USE_X11)
+ // XDamage is often broken, so don't use it by default.
+ use_update_notifications_ = false;
+#endif
+
+#if defined(WEBRTC_WIN)
+ allow_use_magnification_api_ = false;
+#endif
+}
+
+DesktopCaptureOptions::~DesktopCaptureOptions() {}
+
+// static
+DesktopCaptureOptions DesktopCaptureOptions::CreateDefault() {
+ DesktopCaptureOptions result;
+#if defined(USE_X11)
+ result.set_x_display(SharedXDisplay::CreateDefault());
+#endif
+#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
+ result.set_configuration_monitor(new DesktopConfigurationMonitor());
+ result.set_full_screen_chrome_window_detector(
+ new FullScreenChromeWindowDetector());
+#endif
+ return result;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.h b/webrtc/modules/desktop_capture/desktop_capture_options.h
new file mode 100644
index 0000000000..68bb588445
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_capture_options.h
@@ -0,0 +1,108 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+
+#if defined(USE_X11)
+#include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
+#endif
+
+#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
+#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h"
+#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h"
+#endif
+
+namespace webrtc {
+
+// An object that stores initialization parameters for screen and window
+// capturers.
+class DesktopCaptureOptions {
+ public:
+ // Creates an empty Options instance (e.g. without X display).
+ DesktopCaptureOptions();
+ ~DesktopCaptureOptions();
+
+ // Returns instance of DesktopCaptureOptions with default parameters. On Linux
+ // also initializes X window connection. x_display() will be set to null if
+ // X11 connection failed (e.g. DISPLAY isn't set).
+ static DesktopCaptureOptions CreateDefault();
+
+#if defined(USE_X11)
+ SharedXDisplay* x_display() const { return x_display_; }
+ void set_x_display(rtc::scoped_refptr<SharedXDisplay> x_display) {
+ x_display_ = x_display;
+ }
+#endif
+
+#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
+ DesktopConfigurationMonitor* configuration_monitor() const {
+ return configuration_monitor_;
+ }
+ void set_configuration_monitor(
+ rtc::scoped_refptr<DesktopConfigurationMonitor> m) {
+ configuration_monitor_ = m;
+ }
+
+ FullScreenChromeWindowDetector* full_screen_chrome_window_detector() const {
+ return full_screen_window_detector_;
+ }
+ void set_full_screen_chrome_window_detector(
+ rtc::scoped_refptr<FullScreenChromeWindowDetector> detector) {
+ full_screen_window_detector_ = detector;
+ }
+#endif
+
+ // Flag indicating that the capturer should use screen change notifications.
+ // Enables/disables use of XDAMAGE in the X11 capturer.
+ bool use_update_notifications() const { return use_update_notifications_; }
+ void set_use_update_notifications(bool use_update_notifications) {
+ use_update_notifications_ = use_update_notifications;
+ }
+
+ // Flag indicating if desktop effects (e.g. Aero) should be disabled when the
+ // capturer is active. Currently used only on Windows.
+ bool disable_effects() const { return disable_effects_; }
+ void set_disable_effects(bool disable_effects) {
+ disable_effects_ = disable_effects;
+ }
+
+#if defined(WEBRTC_WIN)
+ bool allow_use_magnification_api() const {
+ return allow_use_magnification_api_;
+ }
+ void set_allow_use_magnification_api(bool allow) {
+ allow_use_magnification_api_ = allow;
+ }
+#endif
+
+ private:
+#if defined(USE_X11)
+ rtc::scoped_refptr<SharedXDisplay> x_display_;
+#endif
+
+#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
+ rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
+ rtc::scoped_refptr<FullScreenChromeWindowDetector>
+ full_screen_window_detector_;
+#endif
+
+#if defined(WEBRTC_WIN)
+ bool allow_use_magnification_api_;
+#endif
+ bool use_update_notifications_;
+ bool disable_effects_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
diff --git a/webrtc/modules/desktop_capture/desktop_capture_types.h b/webrtc/modules/desktop_capture/desktop_capture_types.h
new file mode 100644
index 0000000000..3e41796553
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_capture_types.h
@@ -0,0 +1,43 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_
+
+#include <stdint.h>
+
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// Type used to identify windows on the desktop. Values are platform-specific:
+// - On Windows: HWND cast to intptr_t.
+// - On Linux (with X11): X11 Window (unsigned long) type cast to intptr_t.
+// - On OSX: integer window number.
+typedef intptr_t WindowId;
+
+const WindowId kNullWindowId = 0;
+
+// Type used to identify screens on the desktop. Values are platform-specific:
+// - On Windows: integer display device index.
+// - On OSX: CGDirectDisplayID cast to intptr_t.
+// - On Linux (with X11): TBD.
+typedef intptr_t ScreenId;
+
+// The screen id corresponds to all screen combined together.
+const ScreenId kFullDesktopScreenId = -1;
+
+const ScreenId kInvalidScreenId = -2;
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_
+
diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h
new file mode 100644
index 0000000000..7ad1636497
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_capturer.h
@@ -0,0 +1,67 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_
+
+#include <stddef.h>
+
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
+
+namespace webrtc {
+
+class DesktopFrame;
+class DesktopRegion;
+class SharedMemory;
+
+// Abstract interface for screen and window capturers.
+class DesktopCapturer {
+ public:
+ // Interface that must be implemented by the DesktopCapturer consumers.
+ class Callback {
+ public:
+ // Creates a new shared memory buffer for a frame create by the capturer.
+ // Should return null shared memory is not used for captured frames (in that
+ // case the capturer will allocate memory on the heap).
+ virtual SharedMemory* CreateSharedMemory(size_t size) = 0;
+
+ // Called after a frame has been captured. Handler must take ownership of
+ // |frame|. If capture has failed for any reason |frame| is set to NULL
+ // (e.g. the window has been closed).
+ virtual void OnCaptureCompleted(DesktopFrame* frame) = 0;
+
+ protected:
+ virtual ~Callback() {}
+ };
+
+ virtual ~DesktopCapturer() {}
+
+ // Called at the beginning of a capturing session. |callback| must remain
+ // valid until capturer is destroyed.
+ virtual void Start(Callback* callback) = 0;
+
+ // Captures next frame. |region| specifies region of the capture target that
+ // should be fresh in the resulting frame. The frame may also include fresh
+ // data for areas outside |region|. In that case capturer will include these
+ // areas in updated_region() of the frame. |region| is specified relative to
+ // the top left corner of the capture target. Pending capture operations are
+ // canceled when DesktopCapturer is deleted.
+ virtual void Capture(const DesktopRegion& region) = 0;
+
+ // Sets the window to be excluded from the captured image in the future
+ // Capture calls. Used to exclude the screenshare notification window for
+ // screen capturing.
+ virtual void SetExcludedWindow(WindowId window) {}
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_
+
diff --git a/webrtc/modules/desktop_capture/desktop_frame.cc b/webrtc/modules/desktop_capture/desktop_frame.cc
new file mode 100644
index 0000000000..cb32ef7826
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_frame.cc
@@ -0,0 +1,94 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_frame.h"
+
+#include <assert.h>
+#include <string.h>
+
+namespace webrtc {
+
+DesktopFrame::DesktopFrame(DesktopSize size,
+ int stride,
+ uint8_t* data,
+ SharedMemory* shared_memory)
+ : size_(size),
+ stride_(stride),
+ data_(data),
+ shared_memory_(shared_memory),
+ capture_time_ms_(0) {
+}
+
+DesktopFrame::~DesktopFrame() {}
+
+void DesktopFrame::CopyPixelsFrom(uint8_t* src_buffer, int src_stride,
+ const DesktopRect& dest_rect) {
+ assert(DesktopRect::MakeSize(size()).ContainsRect(dest_rect));
+
+ uint8_t* dest = GetFrameDataAtPos(dest_rect.top_left());
+ for (int y = 0; y < dest_rect.height(); ++y) {
+ memcpy(dest, src_buffer, DesktopFrame::kBytesPerPixel * dest_rect.width());
+ src_buffer += src_stride;
+ dest += stride();
+ }
+}
+
+void DesktopFrame::CopyPixelsFrom(const DesktopFrame& src_frame,
+ const DesktopVector& src_pos,
+ const DesktopRect& dest_rect) {
+ assert(DesktopRect::MakeSize(src_frame.size()).ContainsRect(
+ DesktopRect::MakeOriginSize(src_pos, dest_rect.size())));
+
+ CopyPixelsFrom(src_frame.GetFrameDataAtPos(src_pos),
+ src_frame.stride(), dest_rect);
+}
+
+uint8_t* DesktopFrame::GetFrameDataAtPos(const DesktopVector& pos) const {
+ return data() + stride() * pos.y() + DesktopFrame::kBytesPerPixel * pos.x();
+}
+
+BasicDesktopFrame::BasicDesktopFrame(DesktopSize size)
+ : DesktopFrame(size, kBytesPerPixel * size.width(),
+ new uint8_t[kBytesPerPixel * size.width() * size.height()],
+ NULL) {
+}
+
+BasicDesktopFrame::~BasicDesktopFrame() {
+ delete[] data_;
+}
+
+DesktopFrame* BasicDesktopFrame::CopyOf(const DesktopFrame& frame) {
+ DesktopFrame* result = new BasicDesktopFrame(frame.size());
+ for (int y = 0; y < frame.size().height(); ++y) {
+ memcpy(result->data() + y * result->stride(),
+ frame.data() + y * frame.stride(),
+ frame.size().width() * kBytesPerPixel);
+ }
+ result->set_dpi(frame.dpi());
+ result->set_capture_time_ms(frame.capture_time_ms());
+ *result->mutable_updated_region() = frame.updated_region();
+ return result;
+}
+
+
+SharedMemoryDesktopFrame::SharedMemoryDesktopFrame(
+ DesktopSize size,
+ int stride,
+ SharedMemory* shared_memory)
+ : DesktopFrame(size, stride,
+ reinterpret_cast<uint8_t*>(shared_memory->data()),
+ shared_memory) {
+}
+
+SharedMemoryDesktopFrame::~SharedMemoryDesktopFrame() {
+ delete shared_memory_;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_frame.h b/webrtc/modules/desktop_capture/desktop_frame.h
new file mode 100644
index 0000000000..49b964630c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_frame.h
@@ -0,0 +1,126 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/modules/desktop_capture/shared_memory.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// DesktopFrame represents a video frame captured from the screen.
+class DesktopFrame {
+ public:
+ // DesktopFrame objects always hold RGBA data.
+ static const int kBytesPerPixel = 4;
+
+ virtual ~DesktopFrame();
+
+ // Size of the frame.
+ const DesktopSize& size() const { return size_; }
+
+ // Distance in the buffer between two neighboring rows in bytes.
+ int stride() const { return stride_; }
+
+ // Data buffer used for the frame.
+ uint8_t* data() const { return data_; }
+
+ // SharedMemory used for the buffer or NULL if memory is allocated on the
+ // heap. The result is guaranteed to be deleted only after the frame is
+ // deleted (classes that inherit from DesktopFrame must ensure it).
+ SharedMemory* shared_memory() const { return shared_memory_; }
+
+ // Indicates region of the screen that has changed since the previous frame.
+ const DesktopRegion& updated_region() const { return updated_region_; }
+ DesktopRegion* mutable_updated_region() { return &updated_region_; }
+
+ // DPI of the screen being captured. May be set to zero, e.g. if DPI is
+ // unknown.
+ const DesktopVector& dpi() const { return dpi_; }
+ void set_dpi(const DesktopVector& dpi) { dpi_ = dpi; }
+
+ // Time taken to capture the frame in milliseconds.
+ int64_t capture_time_ms() const { return capture_time_ms_; }
+ void set_capture_time_ms(int64_t time_ms) { capture_time_ms_ = time_ms; }
+
+ // Optional shape for the frame. Frames may be shaped e.g. if
+ // capturing the contents of a shaped window.
+ const DesktopRegion* shape() const { return shape_.get(); }
+ void set_shape(DesktopRegion* shape) { shape_.reset(shape); }
+
+ // Copies pixels from a buffer or another frame. |dest_rect| rect must lay
+ // within bounds of this frame.
+ void CopyPixelsFrom(uint8_t* src_buffer, int src_stride,
+ const DesktopRect& dest_rect);
+ void CopyPixelsFrom(const DesktopFrame& src_frame,
+ const DesktopVector& src_pos,
+ const DesktopRect& dest_rect);
+
+ // A helper to return the data pointer of a frame at the specified position.
+ uint8_t* GetFrameDataAtPos(const DesktopVector& pos) const;
+
+ protected:
+ DesktopFrame(DesktopSize size,
+ int stride,
+ uint8_t* data,
+ SharedMemory* shared_memory);
+
+ const DesktopSize size_;
+ const int stride_;
+
+ // Ownership of the buffers is defined by the classes that inherit from this
+ // class. They must guarantee that the buffer is not deleted before the frame
+ // is deleted.
+ uint8_t* const data_;
+ SharedMemory* const shared_memory_;
+
+ DesktopRegion updated_region_;
+ DesktopVector dpi_;
+ int64_t capture_time_ms_;
+ rtc::scoped_ptr<DesktopRegion> shape_;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(DesktopFrame);
+};
+
+// A DesktopFrame that stores data in the heap.
+class BasicDesktopFrame : public DesktopFrame {
+ public:
+ explicit BasicDesktopFrame(DesktopSize size);
+ ~BasicDesktopFrame() override;
+
+ // Creates a BasicDesktopFrame that contains copy of |frame|.
+ static DesktopFrame* CopyOf(const DesktopFrame& frame);
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(BasicDesktopFrame);
+};
+
+// A DesktopFrame that stores data in shared memory.
+class SharedMemoryDesktopFrame : public DesktopFrame {
+ public:
+ // Takes ownership of |shared_memory|.
+ SharedMemoryDesktopFrame(DesktopSize size,
+ int stride,
+ SharedMemory* shared_memory);
+ ~SharedMemoryDesktopFrame() override;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(SharedMemoryDesktopFrame);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_
+
diff --git a/webrtc/modules/desktop_capture/desktop_frame_win.cc b/webrtc/modules/desktop_capture/desktop_frame_win.cc
new file mode 100644
index 0000000000..6b97b132d8
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_frame_win.cc
@@ -0,0 +1,63 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_frame_win.h"
+
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+DesktopFrameWin::DesktopFrameWin(DesktopSize size,
+ int stride,
+ uint8_t* data,
+ SharedMemory* shared_memory,
+ HBITMAP bitmap)
+ : DesktopFrame(size, stride, data, shared_memory),
+ bitmap_(bitmap),
+ owned_shared_memory_(shared_memory_) {
+}
+
+DesktopFrameWin::~DesktopFrameWin() {
+ DeleteObject(bitmap_);
+}
+
+// static
+DesktopFrameWin* DesktopFrameWin::Create(DesktopSize size,
+ SharedMemory* shared_memory,
+ HDC hdc) {
+ int bytes_per_row = size.width() * kBytesPerPixel;
+
+ // Describe a device independent bitmap (DIB) that is the size of the desktop.
+ BITMAPINFO bmi = {};
+ bmi.bmiHeader.biHeight = -size.height();
+ bmi.bmiHeader.biWidth = size.width();
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = DesktopFrameWin::kBytesPerPixel * 8;
+ bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
+ bmi.bmiHeader.biSizeImage = bytes_per_row * size.height();
+
+ HANDLE section_handle = NULL;
+ if (shared_memory)
+ section_handle = shared_memory->handle();
+ void* data = NULL;
+ HBITMAP bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &data,
+ section_handle, 0);
+ if (!bitmap) {
+ LOG(LS_WARNING) << "Failed to allocate new window frame " << GetLastError();
+ delete shared_memory;
+ return NULL;
+ }
+
+ return new DesktopFrameWin(size, bytes_per_row,
+ reinterpret_cast<uint8_t*>(data),
+ shared_memory, bitmap);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_frame_win.h b/webrtc/modules/desktop_capture/desktop_frame_win.h
new file mode 100644
index 0000000000..15b5883c36
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_frame_win.h
@@ -0,0 +1,49 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_
+
+#include <windows.h>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// DesktopFrame implementation used by screen and window captures on Windows.
+// Frame data is stored in a GDI bitmap.
+class DesktopFrameWin : public DesktopFrame {
+ public:
+ virtual ~DesktopFrameWin();
+ static DesktopFrameWin* Create(DesktopSize size,
+ SharedMemory* shared_memory,
+ HDC hdc);
+
+ HBITMAP bitmap() { return bitmap_; }
+
+ private:
+ DesktopFrameWin(DesktopSize size,
+ int stride,
+ uint8_t* data,
+ SharedMemory* shared_memory,
+ HBITMAP bitmap);
+
+ HBITMAP bitmap_;
+ rtc::scoped_ptr<SharedMemory> owned_shared_memory_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(DesktopFrameWin);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_
+
diff --git a/webrtc/modules/desktop_capture/desktop_geometry.cc b/webrtc/modules/desktop_capture/desktop_geometry.cc
new file mode 100644
index 0000000000..1ff7c683c7
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_geometry.cc
@@ -0,0 +1,48 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_geometry.h"
+
+#include <algorithm>
+
+namespace webrtc {
+
+bool DesktopRect::Contains(const DesktopVector& point) const {
+ return point.x() >= left() && point.x() < right() &&
+ point.y() >= top() && point.y() < bottom();
+}
+
+bool DesktopRect::ContainsRect(const DesktopRect& rect) const {
+ return rect.left() >= left() && rect.right() <= right() &&
+ rect.top() >= top() && rect.bottom() <= bottom();
+}
+
+void DesktopRect::IntersectWith(const DesktopRect& rect) {
+ left_ = std::max(left(), rect.left());
+ top_ = std::max(top(), rect.top());
+ right_ = std::min(right(), rect.right());
+ bottom_ = std::min(bottom(), rect.bottom());
+ if (is_empty()) {
+ left_ = 0;
+ top_ = 0;
+ right_ = 0;
+ bottom_ = 0;
+ }
+}
+
+void DesktopRect::Translate(int32_t dx, int32_t dy) {
+ left_ += dx;
+ top_ += dy;
+ right_ += dx;
+ bottom_ += dy;
+}
+
+} // namespace webrtc
+
diff --git a/webrtc/modules/desktop_capture/desktop_geometry.h b/webrtc/modules/desktop_capture/desktop_geometry.h
new file mode 100644
index 0000000000..047eeec3d9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_geometry.h
@@ -0,0 +1,145 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// A vector in the 2D integer space. E.g. can be used to represent screen DPI.
+class DesktopVector {
+ public:
+ DesktopVector() : x_(0), y_(0) {}
+ DesktopVector(int32_t x, int32_t y) : x_(x), y_(y) {}
+
+ int32_t x() const { return x_; }
+ int32_t y() const { return y_; }
+ bool is_zero() const { return x_ == 0 && y_ == 0; }
+
+ bool equals(const DesktopVector& other) const {
+ return x_ == other.x_ && y_ == other.y_;
+ }
+
+ void set(int32_t x, int32_t y) {
+ x_ = x;
+ y_ = y;
+ }
+
+ DesktopVector add(const DesktopVector& other) const {
+ return DesktopVector(x() + other.x(), y() + other.y());
+ }
+ DesktopVector subtract(const DesktopVector& other) const {
+ return DesktopVector(x() - other.x(), y() - other.y());
+ }
+
+ private:
+ int32_t x_;
+ int32_t y_;
+};
+
+// Type used to represent screen/window size.
+class DesktopSize {
+ public:
+ DesktopSize() : width_(0), height_(0) {}
+ DesktopSize(int32_t width, int32_t height)
+ : width_(width), height_(height) {
+ }
+
+ int32_t width() const { return width_; }
+ int32_t height() const { return height_; }
+
+ bool is_empty() const { return width_ <= 0 || height_ <= 0; }
+
+ bool equals(const DesktopSize& other) const {
+ return width_ == other.width_ && height_ == other.height_;
+ }
+
+ void set(int32_t width, int32_t height) {
+ width_ = width;
+ height_ = height;
+ }
+
+ private:
+ int32_t width_;
+ int32_t height_;
+};
+
+// Represents a rectangle on the screen.
+class DesktopRect {
+ public:
+ static DesktopRect MakeSize(const DesktopSize& size) {
+ return DesktopRect(0, 0, size.width(), size.height());
+ }
+ static DesktopRect MakeWH(int32_t width, int32_t height) {
+ return DesktopRect(0, 0, width, height);
+ }
+ static DesktopRect MakeXYWH(int32_t x, int32_t y,
+ int32_t width, int32_t height) {
+ return DesktopRect(x, y, x + width, y + height);
+ }
+ static DesktopRect MakeLTRB(int32_t left, int32_t top,
+ int32_t right, int32_t bottom) {
+ return DesktopRect(left, top, right, bottom);
+ }
+ static DesktopRect MakeOriginSize(const DesktopVector& origin,
+ const DesktopSize& size) {
+ return MakeXYWH(origin.x(), origin.y(), size.width(), size.height());
+ }
+
+ DesktopRect() : left_(0), top_(0), right_(0), bottom_(0) {}
+
+ int32_t left() const { return left_; }
+ int32_t top() const { return top_; }
+ int32_t right() const { return right_; }
+ int32_t bottom() const { return bottom_; }
+ int32_t width() const { return right_ - left_; }
+ int32_t height() const { return bottom_ - top_; }
+
+ DesktopVector top_left() const { return DesktopVector(left_, top_); }
+ DesktopSize size() const { return DesktopSize(width(), height()); }
+
+ bool is_empty() const { return left_ >= right_ || top_ >= bottom_; }
+
+ bool equals(const DesktopRect& other) const {
+ return left_ == other.left_ && top_ == other.top_ &&
+ right_ == other.right_ && bottom_ == other.bottom_;
+ }
+
+ // Returns true if |point| lies within the rectangle boundaries.
+ bool Contains(const DesktopVector& point) const;
+
+ // Returns true if |rect| lies within the boundaries of this rectangle.
+ bool ContainsRect(const DesktopRect& rect) const;
+
+ // Finds intersection with |rect|.
+ void IntersectWith(const DesktopRect& rect);
+
+ // Adds (dx, dy) to the position of the rectangle.
+ void Translate(int32_t dx, int32_t dy);
+ void Translate(DesktopVector d) { Translate(d.x(), d.y()); };
+
+ private:
+ DesktopRect(int32_t left, int32_t top, int32_t right, int32_t bottom)
+ : left_(left), top_(top), right_(right), bottom_(bottom) {
+ }
+
+ int32_t left_;
+ int32_t top_;
+ int32_t right_;
+ int32_t bottom_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_
+
diff --git a/webrtc/modules/desktop_capture/desktop_region.cc b/webrtc/modules/desktop_capture/desktop_region.cc
new file mode 100644
index 0000000000..bc9972660a
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_region.cc
@@ -0,0 +1,569 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_region.h"
+
+#include <assert.h>
+
+#include <algorithm>
+
+namespace webrtc {
+
+DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right)
+ : left(left), right(right) {
+}
+
+DesktopRegion::Row::Row(int32_t top, int32_t bottom)
+ : top(top), bottom(bottom) {
+}
+
+DesktopRegion::Row::~Row() {}
+
+DesktopRegion::DesktopRegion() {}
+
+DesktopRegion::DesktopRegion(const DesktopRect& rect) {
+ AddRect(rect);
+}
+
+DesktopRegion::DesktopRegion(const DesktopRect* rects, int count) {
+ AddRects(rects, count);
+}
+
+DesktopRegion::DesktopRegion(const DesktopRegion& other) {
+ *this = other;
+}
+
+DesktopRegion::~DesktopRegion() {
+ Clear();
+}
+
+DesktopRegion& DesktopRegion::operator=(const DesktopRegion& other) {
+ Clear();
+ rows_ = other.rows_;
+ for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) {
+ // Copy each row.
+ Row* row = it->second;
+ it->second = new Row(*row);
+ }
+ return *this;
+}
+
+bool DesktopRegion::Equals(const DesktopRegion& region) const {
+ // Iterate over rows of the tow regions and compare each row.
+ Rows::const_iterator it1 = rows_.begin();
+ Rows::const_iterator it2 = region.rows_.begin();
+ while (it1 != rows_.end()) {
+ if (it2 == region.rows_.end() ||
+ it1->first != it2->first ||
+ it1->second->top != it2->second->top ||
+ it1->second->bottom != it2->second->bottom ||
+ it1->second->spans != it2->second->spans) {
+ return false;
+ }
+ ++it1;
+ ++it2;
+ }
+ return it2 == region.rows_.end();
+}
+
+void DesktopRegion::Clear() {
+ for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) {
+ delete row->second;
+ }
+ rows_.clear();
+}
+
+void DesktopRegion::SetRect(const DesktopRect& rect) {
+ Clear();
+ AddRect(rect);
+}
+
+void DesktopRegion::AddRect(const DesktopRect& rect) {
+ if (rect.is_empty())
+ return;
+
+ // Top of the part of the |rect| that hasn't been inserted yet. Increased as
+ // we iterate over the rows until it reaches |rect.bottom()|.
+ int top = rect.top();
+
+ // Iterate over all rows that may intersect with |rect| and add new rows when
+ // necessary.
+ Rows::iterator row = rows_.upper_bound(top);
+ while (top < rect.bottom()) {
+ if (row == rows_.end() || top < row->second->top) {
+ // If |top| is above the top of the current |row| then add a new row above
+ // the current one.
+ int32_t bottom = rect.bottom();
+ if (row != rows_.end() && row->second->top < bottom)
+ bottom = row->second->top;
+ row = rows_.insert(
+ row, Rows::value_type(bottom, new Row(top, bottom)));
+ } else if (top > row->second->top) {
+ // If the |top| falls in the middle of the |row| then split |row| into
+ // two, at |top|, and leave |row| referring to the lower of the two,
+ // ready to insert a new span into.
+ assert(top <= row->second->bottom);
+ Rows::iterator new_row = rows_.insert(
+ row, Rows::value_type(top, new Row(row->second->top, top)));
+ row->second->top = top;
+ new_row->second->spans = row->second->spans;
+ }
+
+ if (rect.bottom() < row->second->bottom) {
+ // If the bottom of the |rect| falls in the middle of the |row| split
+ // |row| into two, at |top|, and leave |row| referring to the upper of
+ // the two, ready to insert a new span into.
+ Rows::iterator new_row = rows_.insert(
+ row, Rows::value_type(rect.bottom(), new Row(top, rect.bottom())));
+ row->second->top = rect.bottom();
+ new_row->second->spans = row->second->spans;
+ row = new_row;
+ }
+
+ // Add a new span to the current row.
+ AddSpanToRow(row->second, rect.left(), rect.right());
+ top = row->second->bottom;
+
+ MergeWithPrecedingRow(row);
+
+ // Move to the next row.
+ ++row;
+ }
+
+ if (row != rows_.end())
+ MergeWithPrecedingRow(row);
+}
+
+void DesktopRegion::AddRects(const DesktopRect* rects, int count) {
+ for (int i = 0; i < count; ++i) {
+ AddRect(rects[i]);
+ }
+}
+
+void DesktopRegion::MergeWithPrecedingRow(Rows::iterator row) {
+ assert(row != rows_.end());
+
+ if (row != rows_.begin()) {
+ Rows::iterator previous_row = row;
+ previous_row--;
+
+ // If |row| and |previous_row| are next to each other and contain the same
+ // set of spans then they can be merged.
+ if (previous_row->second->bottom == row->second->top &&
+ previous_row->second->spans == row->second->spans) {
+ row->second->top = previous_row->second->top;
+ delete previous_row->second;
+ rows_.erase(previous_row);
+ }
+ }
+}
+
+void DesktopRegion::AddRegion(const DesktopRegion& region) {
+ // TODO(sergeyu): This function is not optimized - potentially it can iterate
+ // over rows of the two regions similar to how it works in Intersect().
+ for (Iterator it(region); !it.IsAtEnd(); it.Advance()) {
+ AddRect(it.rect());
+ }
+}
+
+void DesktopRegion::Intersect(const DesktopRegion& region1,
+ const DesktopRegion& region2) {
+ Clear();
+
+ Rows::const_iterator it1 = region1.rows_.begin();
+ Rows::const_iterator end1 = region1.rows_.end();
+ Rows::const_iterator it2 = region2.rows_.begin();
+ Rows::const_iterator end2 = region2.rows_.end();
+ if (it1 == end1 || it2 == end2)
+ return;
+
+ while (it1 != end1 && it2 != end2) {
+ // Arrange for |it1| to always be the top-most of the rows.
+ if (it2->second->top < it1->second->top) {
+ std::swap(it1, it2);
+ std::swap(end1, end2);
+ }
+
+ // Skip |it1| if it doesn't intersect |it2| at all.
+ if (it1->second->bottom <= it2->second->top) {
+ ++it1;
+ continue;
+ }
+
+ // Top of the |it1| row is above the top of |it2|, so top of the
+ // intersection is always the top of |it2|.
+ int32_t top = it2->second->top;
+ int32_t bottom = std::min(it1->second->bottom, it2->second->bottom);
+
+ Rows::iterator new_row = rows_.insert(
+ rows_.end(), Rows::value_type(bottom, new Row(top, bottom)));
+ IntersectRows(it1->second->spans, it2->second->spans,
+ &new_row->second->spans);
+ if (new_row->second->spans.empty()) {
+ delete new_row->second;
+ rows_.erase(new_row);
+ } else {
+ MergeWithPrecedingRow(new_row);
+ }
+
+ // If |it1| was completely consumed, move to the next one.
+ if (it1->second->bottom == bottom)
+ ++it1;
+ // If |it2| was completely consumed, move to the next one.
+ if (it2->second->bottom == bottom)
+ ++it2;
+ }
+}
+
+// static
+void DesktopRegion::IntersectRows(const RowSpanSet& set1,
+ const RowSpanSet& set2,
+ RowSpanSet* output) {
+ RowSpanSet::const_iterator it1 = set1.begin();
+ RowSpanSet::const_iterator end1 = set1.end();
+ RowSpanSet::const_iterator it2 = set2.begin();
+ RowSpanSet::const_iterator end2 = set2.end();
+ assert(it1 != end1 && it2 != end2);
+
+ do {
+ // Arrange for |it1| to always be the left-most of the spans.
+ if (it2->left < it1->left) {
+ std::swap(it1, it2);
+ std::swap(end1, end2);
+ }
+
+ // Skip |it1| if it doesn't intersect |it2| at all.
+ if (it1->right <= it2->left) {
+ ++it1;
+ continue;
+ }
+
+ int32_t left = it2->left;
+ int32_t right = std::min(it1->right, it2->right);
+ assert(left < right);
+
+ output->push_back(RowSpan(left, right));
+
+ // If |it1| was completely consumed, move to the next one.
+ if (it1->right == right)
+ ++it1;
+ // If |it2| was completely consumed, move to the next one.
+ if (it2->right == right)
+ ++it2;
+ } while (it1 != end1 && it2 != end2);
+}
+
+void DesktopRegion::IntersectWith(const DesktopRegion& region) {
+ DesktopRegion old_region;
+ Swap(&old_region);
+ Intersect(old_region, region);
+}
+
+void DesktopRegion::IntersectWith(const DesktopRect& rect) {
+ DesktopRegion region;
+ region.AddRect(rect);
+ IntersectWith(region);
+}
+
+void DesktopRegion::Subtract(const DesktopRegion& region) {
+ if (region.rows_.empty())
+ return;
+
+ // |row_b| refers to the current row being subtracted.
+ Rows::const_iterator row_b = region.rows_.begin();
+
+ // Current vertical position at which subtraction is happening.
+ int top = row_b->second->top;
+
+ // |row_a| refers to the current row we are subtracting from. Skip all rows
+ // above |top|.
+ Rows::iterator row_a = rows_.upper_bound(top);
+
+ // Step through rows of the both regions subtracting content of |row_b| from
+ // |row_a|.
+ while (row_a != rows_.end() && row_b != region.rows_.end()) {
+ // Skip |row_a| if it doesn't intersect with the |row_b|.
+ if (row_a->second->bottom <= top) {
+ // Each output row is merged with previously-processed rows before further
+ // rows are processed.
+ MergeWithPrecedingRow(row_a);
+ ++row_a;
+ continue;
+ }
+
+ if (top > row_a->second->top) {
+ // If |top| falls in the middle of |row_a| then split |row_a| into two, at
+ // |top|, and leave |row_a| referring to the lower of the two, ready to
+ // subtract spans from.
+ assert(top <= row_a->second->bottom);
+ Rows::iterator new_row = rows_.insert(
+ row_a, Rows::value_type(top, new Row(row_a->second->top, top)));
+ row_a->second->top = top;
+ new_row->second->spans = row_a->second->spans;
+ } else if (top < row_a->second->top) {
+ // If the |top| is above |row_a| then skip the range between |top| and
+ // top of |row_a| because it's empty.
+ top = row_a->second->top;
+ if (top >= row_b->second->bottom) {
+ ++row_b;
+ if (row_b != region.rows_.end())
+ top = row_b->second->top;
+ continue;
+ }
+ }
+
+ if (row_b->second->bottom < row_a->second->bottom) {
+ // If the bottom of |row_b| falls in the middle of the |row_a| split
+ // |row_a| into two, at |top|, and leave |row_a| referring to the upper of
+ // the two, ready to subtract spans from.
+ int bottom = row_b->second->bottom;
+ Rows::iterator new_row =
+ rows_.insert(row_a, Rows::value_type(bottom, new Row(top, bottom)));
+ row_a->second->top = bottom;
+ new_row->second->spans = row_a->second->spans;
+ row_a = new_row;
+ }
+
+ // At this point the vertical range covered by |row_a| lays within the
+ // range covered by |row_b|. Subtract |row_b| spans from |row_a|.
+ RowSpanSet new_spans;
+ SubtractRows(row_a->second->spans, row_b->second->spans, &new_spans);
+ new_spans.swap(row_a->second->spans);
+ top = row_a->second->bottom;
+
+ if (top >= row_b->second->bottom) {
+ ++row_b;
+ if (row_b != region.rows_.end())
+ top = row_b->second->top;
+ }
+
+ // Check if the row is empty after subtraction and delete it. Otherwise move
+ // to the next one.
+ if (row_a->second->spans.empty()) {
+ Rows::iterator row_to_delete = row_a;
+ ++row_a;
+ delete row_to_delete->second;
+ rows_.erase(row_to_delete);
+ } else {
+ MergeWithPrecedingRow(row_a);
+ ++row_a;
+ }
+ }
+
+ if (row_a != rows_.end())
+ MergeWithPrecedingRow(row_a);
+}
+
+void DesktopRegion::Subtract(const DesktopRect& rect) {
+ DesktopRegion region;
+ region.AddRect(rect);
+ Subtract(region);
+}
+
+void DesktopRegion::Translate(int32_t dx, int32_t dy) {
+ Rows new_rows;
+
+ for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) {
+ Row* row = it->second;
+
+ row->top += dy;
+ row->bottom += dy;
+
+ if (dx != 0) {
+ // Translate each span.
+ for (RowSpanSet::iterator span = row->spans.begin();
+ span != row->spans.end(); ++span) {
+ span->left += dx;
+ span->right += dx;
+ }
+ }
+
+ if (dy != 0)
+ new_rows.insert(new_rows.end(), Rows::value_type(row->bottom, row));
+ }
+
+ if (dy != 0)
+ new_rows.swap(rows_);
+}
+
+void DesktopRegion::Swap(DesktopRegion* region) {
+ rows_.swap(region->rows_);
+}
+
+// static
+bool DesktopRegion::CompareSpanRight(const RowSpan& r, int32_t value) {
+ return r.right < value;
+}
+
+// static
+bool DesktopRegion::CompareSpanLeft(const RowSpan& r, int32_t value) {
+ return r.left < value;
+}
+
+// static
+void DesktopRegion::AddSpanToRow(Row* row, int left, int right) {
+ // First check if the new span is located to the right of all existing spans.
+ // This is an optimization to avoid binary search in the case when rectangles
+ // are inserted sequentially from left to right.
+ if (row->spans.empty() || left > row->spans.back().right) {
+ row->spans.push_back(RowSpan(left, right));
+ return;
+ }
+
+ // Find the first span that ends at or after |left|.
+ RowSpanSet::iterator start =
+ std::lower_bound(row->spans.begin(), row->spans.end(), left,
+ CompareSpanRight);
+ assert(start < row->spans.end());
+
+ // Find the first span that starts after |right|.
+ RowSpanSet::iterator end =
+ std::lower_bound(start, row->spans.end(), right + 1, CompareSpanLeft);
+ if (end == row->spans.begin()) {
+ // There are no overlaps. Just insert the new span at the beginning.
+ row->spans.insert(row->spans.begin(), RowSpan(left, right));
+ return;
+ }
+
+ // Move end to the left, so that it points the last span that ends at or
+ // before |right|.
+ end--;
+
+ // At this point [start, end] is the range of spans that intersect with the
+ // new one.
+ if (end < start) {
+ // There are no overlaps. Just insert the new span at the correct position.
+ row->spans.insert(start, RowSpan(left, right));
+ return;
+ }
+
+ left = std::min(left, start->left);
+ right = std::max(right, end->right);
+
+ // Replace range [start, end] with the new span.
+ *start = RowSpan(left, right);
+ ++start;
+ ++end;
+ if (start < end)
+ row->spans.erase(start, end);
+}
+
+// static
+bool DesktopRegion::IsSpanInRow(const Row& row, const RowSpan& span) {
+ // Find the first span that starts at or after |span.left| and then check if
+ // it's the same span.
+ RowSpanSet::const_iterator it =
+ std::lower_bound(row.spans.begin(), row.spans.end(), span.left,
+ CompareSpanLeft);
+ return it != row.spans.end() && *it == span;
+}
+
+// static
+void DesktopRegion::SubtractRows(const RowSpanSet& set_a,
+ const RowSpanSet& set_b,
+ RowSpanSet* output) {
+ assert(!set_a.empty() && !set_b.empty());
+
+ RowSpanSet::const_iterator it_b = set_b.begin();
+
+ // Iterate over all spans in |set_a| adding parts of it that do not intersect
+ // with |set_b| to the |output|.
+ for (RowSpanSet::const_iterator it_a = set_a.begin(); it_a != set_a.end();
+ ++it_a) {
+ // If there is no intersection then append the current span and continue.
+ if (it_b == set_b.end() || it_a->right < it_b->left) {
+ output->push_back(*it_a);
+ continue;
+ }
+
+ // Iterate over |set_b| spans that may intersect with |it_a|.
+ int pos = it_a->left;
+ while (it_b != set_b.end() && it_b->left < it_a->right) {
+ if (it_b->left > pos)
+ output->push_back(RowSpan(pos, it_b->left));
+ if (it_b->right > pos) {
+ pos = it_b->right;
+ if (pos >= it_a->right)
+ break;
+ }
+ ++it_b;
+ }
+ if (pos < it_a->right)
+ output->push_back(RowSpan(pos, it_a->right));
+ }
+}
+
+DesktopRegion::Iterator::Iterator(const DesktopRegion& region)
+ : region_(region),
+ row_(region.rows_.begin()),
+ previous_row_(region.rows_.end()) {
+ if (!IsAtEnd()) {
+ assert(row_->second->spans.size() > 0);
+ row_span_ = row_->second->spans.begin();
+ UpdateCurrentRect();
+ }
+}
+
+DesktopRegion::Iterator::~Iterator() {}
+
+bool DesktopRegion::Iterator::IsAtEnd() const {
+ return row_ == region_.rows_.end();
+}
+
+void DesktopRegion::Iterator::Advance() {
+ assert(!IsAtEnd());
+
+ while (true) {
+ ++row_span_;
+ if (row_span_ == row_->second->spans.end()) {
+ previous_row_ = row_;
+ ++row_;
+ if (row_ != region_.rows_.end()) {
+ assert(row_->second->spans.size() > 0);
+ row_span_ = row_->second->spans.begin();
+ }
+ }
+
+ if (IsAtEnd())
+ return;
+
+ // If the same span exists on the previous row then skip it, as we've
+ // already returned this span merged into the previous one, via
+ // UpdateCurrentRect().
+ if (previous_row_ != region_.rows_.end() &&
+ previous_row_->second->bottom == row_->second->top &&
+ IsSpanInRow(*previous_row_->second, *row_span_)) {
+ continue;
+ }
+
+ break;
+ }
+
+ assert(!IsAtEnd());
+ UpdateCurrentRect();
+}
+
+void DesktopRegion::Iterator::UpdateCurrentRect() {
+ // Merge the current rectangle with the matching spans from later rows.
+ int bottom;
+ Rows::const_iterator bottom_row = row_;
+ Rows::const_iterator previous;
+ do {
+ bottom = bottom_row->second->bottom;
+ previous = bottom_row;
+ ++bottom_row;
+ } while (bottom_row != region_.rows_.end() &&
+ previous->second->bottom == bottom_row->second->top &&
+ IsSpanInRow(*bottom_row->second, *row_span_));
+ rect_ = DesktopRect::MakeLTRB(row_span_->left, row_->second->top,
+ row_span_->right, bottom);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/desktop_region.h b/webrtc/modules/desktop_capture/desktop_region.h
new file mode 100644
index 0000000000..c86da56e17
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_region.h
@@ -0,0 +1,167 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_
+
+#include <map>
+#include <vector>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// DesktopRegion represents a region of the screen or window.
+//
+// Internally each region is stored as a set of rows where each row contains one
+// or more rectangles aligned vertically.
+class DesktopRegion {
+ private:
+ // The following private types need to be declared first because they are used
+ // in the public Iterator.
+
+ // RowSpan represents a horizontal span withing a single row.
+ struct RowSpan {
+ RowSpan(int32_t left, int32_t right);
+
+ // Used by std::vector<>.
+ bool operator==(const RowSpan& that) const {
+ return left == that.left && right == that.right;
+ }
+
+ int32_t left;
+ int32_t right;
+ };
+
+ typedef std::vector<RowSpan> RowSpanSet;
+
+ // Row represents a single row of a region. A row is set of rectangles that
+ // have the same vertical position.
+ struct Row {
+ Row(int32_t top, int32_t bottom);
+ ~Row();
+
+ int32_t top;
+ int32_t bottom;
+
+ RowSpanSet spans;
+ };
+
+ // Type used to store list of rows in the region. The bottom position of row
+ // is used as the key so that rows are always ordered by their position. The
+ // map stores pointers to make Translate() more efficient.
+ typedef std::map<int, Row*> Rows;
+
+ public:
+ // Iterator that can be used to iterate over rectangles of a DesktopRegion.
+ // The region must not be mutated while the iterator is used.
+ class Iterator {
+ public:
+ explicit Iterator(const DesktopRegion& target);
+ ~Iterator();
+
+ bool IsAtEnd() const;
+ void Advance();
+
+ const DesktopRect& rect() const { return rect_; }
+
+ private:
+ const DesktopRegion& region_;
+
+ // Updates |rect_| based on the current |row_| and |row_span_|. If
+ // |row_span_| matches spans on consecutive rows then they are also merged
+ // into |rect_|, to generate more efficient output.
+ void UpdateCurrentRect();
+
+ Rows::const_iterator row_;
+ Rows::const_iterator previous_row_;
+ RowSpanSet::const_iterator row_span_;
+ DesktopRect rect_;
+ };
+
+ DesktopRegion();
+ explicit DesktopRegion(const DesktopRect& rect);
+ DesktopRegion(const DesktopRect* rects, int count);
+ DesktopRegion(const DesktopRegion& other);
+ ~DesktopRegion();
+
+ DesktopRegion& operator=(const DesktopRegion& other);
+
+ bool is_empty() const { return rows_.empty(); }
+
+ bool Equals(const DesktopRegion& region) const;
+
+ // Reset the region to be empty.
+ void Clear();
+
+ // Reset region to contain just |rect|.
+ void SetRect(const DesktopRect& rect);
+
+ // Adds specified rect(s) or region to the region.
+ void AddRect(const DesktopRect& rect);
+ void AddRects(const DesktopRect* rects, int count);
+ void AddRegion(const DesktopRegion& region);
+
+ // Finds intersection of two regions and stores them in the current region.
+ void Intersect(const DesktopRegion& region1, const DesktopRegion& region2);
+
+ // Same as above but intersects content of the current region with |region|.
+ void IntersectWith(const DesktopRegion& region);
+
+ // Clips the region by the |rect|.
+ void IntersectWith(const DesktopRect& rect);
+
+ // Subtracts |region| from the current content of the region.
+ void Subtract(const DesktopRegion& region);
+
+ // Subtracts |rect| from the current content of the region.
+ void Subtract(const DesktopRect& rect);
+
+ // Adds (dx, dy) to the position of the region.
+ void Translate(int32_t dx, int32_t dy);
+
+ void Swap(DesktopRegion* region);
+
+ private:
+ // Comparison functions used for std::lower_bound(). Compare left or right
+ // edges withs a given |value|.
+ static bool CompareSpanLeft(const RowSpan& r, int32_t value);
+ static bool CompareSpanRight(const RowSpan& r, int32_t value);
+
+ // Adds a new span to the row, coalescing spans if necessary.
+ static void AddSpanToRow(Row* row, int32_t left, int32_t right);
+
+ // Returns true if the |span| exists in the given |row|.
+ static bool IsSpanInRow(const Row& row, const RowSpan& rect);
+
+ // Calculates the intersection of two sets of spans.
+ static void IntersectRows(const RowSpanSet& set1,
+ const RowSpanSet& set2,
+ RowSpanSet* output);
+
+ static void SubtractRows(const RowSpanSet& set_a,
+ const RowSpanSet& set_b,
+ RowSpanSet* output);
+
+ // Merges |row| with the row above it if they contain the same spans. Doesn't
+ // do anything if called with |row| set to rows_.begin() (i.e. first row of
+ // the region). If the rows were merged |row| remains a valid iterator to the
+ // merged row.
+ void MergeWithPrecedingRow(Rows::iterator row);
+
+ Rows rows_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_
+
diff --git a/webrtc/modules/desktop_capture/desktop_region_unittest.cc b/webrtc/modules/desktop_capture/desktop_region_unittest.cc
new file mode 100644
index 0000000000..3062f0b93c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/desktop_region_unittest.cc
@@ -0,0 +1,710 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/desktop_region.h"
+
+#include <algorithm>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+
+namespace {
+
+int RadmonInt(int max) {
+ return (rand() / 256) % max;
+}
+
+void CompareRegion(const DesktopRegion& region,
+ const DesktopRect rects[], int rects_size) {
+ DesktopRegion::Iterator it(region);
+ for (int i = 0; i < rects_size; ++i) {
+ SCOPED_TRACE(i);
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_TRUE(it.rect().equals(rects[i]))
+ << it.rect().left() << "-" << it.rect().right() << "."
+ << it.rect().top() << "-" << it.rect().bottom() << " "
+ << rects[i].left() << "-" << rects[i].right() << "."
+ << rects[i].top() << "-" << rects[i].bottom();
+ it.Advance();
+ }
+ EXPECT_TRUE(it.IsAtEnd());
+}
+
+} // namespace
+
+// Verify that regions are empty when created.
+TEST(DesktopRegionTest, Empty) {
+ DesktopRegion r;
+ CompareRegion(r, NULL, 0);
+}
+
+// Verify that empty rectangles are ignored.
+TEST(DesktopRegionTest, AddEmpty) {
+ DesktopRegion r;
+ DesktopRect rect = DesktopRect::MakeXYWH(1, 2, 0, 0);
+ r.AddRect(rect);
+ CompareRegion(r, NULL, 0);
+}
+
+// Verify that regions with a single rectangles are handled properly.
+TEST(DesktopRegionTest, SingleRect) {
+ DesktopRegion r;
+ DesktopRect rect = DesktopRect::MakeXYWH(1, 2, 3, 4);
+ r.AddRect(rect);
+ CompareRegion(r, &rect, 1);
+}
+
+// Verify that non-overlapping rectangles are not merged.
+TEST(DesktopRegionTest, NonOverlappingRects) {
+ struct Case {
+ int count;
+ DesktopRect rects[4];
+ } cases[] = {
+ { 1, { DesktopRect::MakeXYWH(10, 10, 10, 10) } },
+ { 2, { DesktopRect::MakeXYWH(10, 10, 10, 10),
+ DesktopRect::MakeXYWH(30, 10, 10, 15) } },
+ { 2, { DesktopRect::MakeXYWH(10, 10, 10, 10),
+ DesktopRect::MakeXYWH(10, 30, 10, 5) } },
+ { 3, { DesktopRect::MakeXYWH(10, 10, 10, 9),
+ DesktopRect::MakeXYWH(30, 10, 15, 10),
+ DesktopRect::MakeXYWH(10, 30, 8, 10) } },
+ { 4, { DesktopRect::MakeXYWH(0, 0, 30, 10),
+ DesktopRect::MakeXYWH(40, 0, 10, 30),
+ DesktopRect::MakeXYWH(0, 20, 10, 30),
+ DesktopRect::MakeXYWH(20, 40, 30, 10) } },
+ { 4, { DesktopRect::MakeXYWH(0, 0, 10, 100),
+ DesktopRect::MakeXYWH(20, 10, 30, 10),
+ DesktopRect::MakeXYWH(20, 30, 30, 10),
+ DesktopRect::MakeXYWH(20, 50, 30, 10) } },
+ };
+
+ for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r;
+
+ for (int j = 0; j < cases[i].count; ++j) {
+ r.AddRect(cases[i].rects[j]);
+ }
+ CompareRegion(r, cases[i].rects, cases[i].count);
+
+ SCOPED_TRACE("Reverse");
+
+ // Try inserting rects in reverse order.
+ r.Clear();
+ for (int j = cases[i].count - 1; j >= 0; --j) {
+ r.AddRect(cases[i].rects[j]);
+ }
+ CompareRegion(r, cases[i].rects, cases[i].count);
+ }
+}
+
+TEST(DesktopRegionTest, TwoRects) {
+ struct Case {
+ DesktopRect input_rect1;
+ DesktopRect input_rect2;
+ int expected_count;
+ DesktopRect expected_rects[3];
+ } cases[] = {
+ // Touching rectangles that merge into one.
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(0, 100, 100, 200),
+ 1, { DesktopRect::MakeLTRB(0, 100, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(100, 0, 200, 100),
+ 1, { DesktopRect::MakeLTRB(100, 0, 200, 200) } },
+
+ // Rectangles touching on the vertical edge.
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(0, 150, 100, 250),
+ 3, { DesktopRect::MakeLTRB(100, 100, 200, 150),
+ DesktopRect::MakeLTRB(0, 150, 200, 200),
+ DesktopRect::MakeLTRB(0, 200, 100, 250) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(0, 50, 100, 150),
+ 3, { DesktopRect::MakeLTRB(0, 50, 100, 100),
+ DesktopRect::MakeLTRB(0, 100, 200, 150),
+ DesktopRect::MakeLTRB(100, 150, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(0, 120, 100, 180),
+ 3, { DesktopRect::MakeLTRB(100, 100, 200, 120),
+ DesktopRect::MakeLTRB(0, 120, 200, 180),
+ DesktopRect::MakeLTRB(100, 180, 200, 200) } },
+
+ // Rectangles touching on the horizontal edge.
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(150, 0, 250, 100),
+ 2, { DesktopRect::MakeLTRB(150, 0, 250, 100),
+ DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(50, 0, 150, 100),
+ 2, { DesktopRect::MakeLTRB(50, 0, 150, 100),
+ DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(120, 0, 180, 100),
+ 2, { DesktopRect::MakeLTRB(120, 0, 180, 100),
+ DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+
+ // Overlapping rectangles.
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(50, 50, 150, 150),
+ 3, { DesktopRect::MakeLTRB(50, 50, 150, 100),
+ DesktopRect::MakeLTRB(50, 100, 200, 150),
+ DesktopRect::MakeLTRB(100, 150, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(150, 50, 250, 150),
+ 3, { DesktopRect::MakeLTRB(150, 50, 250, 100),
+ DesktopRect::MakeLTRB(100, 100, 250, 150),
+ DesktopRect::MakeLTRB(100, 150, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(0, 120, 150, 180),
+ 3, { DesktopRect::MakeLTRB(100, 100, 200, 120),
+ DesktopRect::MakeLTRB(0, 120, 200, 180),
+ DesktopRect::MakeLTRB(100, 180, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(120, 0, 180, 150),
+ 2, { DesktopRect::MakeLTRB(120, 0, 180, 100),
+ DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 0, 200, 300),
+ DesktopRect::MakeLTRB(0, 100, 300, 200),
+ 3, { DesktopRect::MakeLTRB(100, 0, 200, 100),
+ DesktopRect::MakeLTRB(0, 100, 300, 200),
+ DesktopRect::MakeLTRB(100, 200, 200, 300)} },
+
+ // One rectangle enclosing another.
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(150, 150, 180, 180),
+ 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(100, 100, 180, 180),
+ 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+ { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(150, 150, 200, 200),
+ 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+ };
+
+ for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r;
+
+ r.AddRect(cases[i].input_rect1);
+ r.AddRect(cases[i].input_rect2);
+ CompareRegion(r, cases[i].expected_rects, cases[i].expected_count);
+
+ SCOPED_TRACE("Reverse");
+
+ // Run the same test with rectangles inserted in reverse order.
+ r.Clear();
+ r.AddRect(cases[i].input_rect2);
+ r.AddRect(cases[i].input_rect1);
+ CompareRegion(r, cases[i].expected_rects, cases[i].expected_count);
+ }
+}
+
+// Verify that DesktopRegion::AddRectToRow() works correctly by creating a row
+// of not overlapping rectangles and insert an overlapping rectangle into the
+// row at different positions. Result is verified by building a map of the
+// region in an array and comparing it with the expected values.
+TEST(DesktopRegionTest, SameRow) {
+ const int kMapWidth = 50;
+ const int kLastRectSizes[] = {3, 27};
+
+ DesktopRegion base_region;
+ bool base_map[kMapWidth] = { false, };
+
+ base_region.AddRect(DesktopRect::MakeXYWH(5, 0, 5, 1));
+ std::fill_n(base_map + 5, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(15, 0, 5, 1));
+ std::fill_n(base_map + 15, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(25, 0, 5, 1));
+ std::fill_n(base_map + 25, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(35, 0, 5, 1));
+ std::fill_n(base_map + 35, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(45, 0, 5, 1));
+ std::fill_n(base_map + 45, 5, true);
+
+ for (size_t i = 0; i < sizeof(kLastRectSizes) / sizeof(kLastRectSizes[0]);
+ i++) {
+ int last_rect_size = kLastRectSizes[i];
+ for (int x = 0; x < kMapWidth - last_rect_size; x++) {
+ SCOPED_TRACE(x);
+
+ DesktopRegion r = base_region;
+ r.AddRect(DesktopRect::MakeXYWH(x, 0, last_rect_size, 1));
+
+ bool expected_map[kMapWidth];
+ std::copy(base_map, base_map + kMapWidth, expected_map);
+ std::fill_n(expected_map + x, last_rect_size, true);
+
+ bool map[kMapWidth] = { false, };
+
+ int pos = -1;
+ for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) {
+ EXPECT_GT(it.rect().left(), pos);
+ pos = it.rect().right();
+ std::fill_n(map + it.rect().left(), it.rect().width(), true);
+ }
+
+ EXPECT_TRUE(std::equal(map, map + kMapWidth, expected_map));
+ }
+ }
+}
+
+TEST(DesktopRegionTest, ComplexRegions) {
+ struct Case {
+ int input_count;
+ DesktopRect input_rects[4];
+ int expected_count;
+ DesktopRect expected_rects[6];
+ } cases[] = {
+ { 3, { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(0, 100, 100, 200),
+ DesktopRect::MakeLTRB(310, 110, 320, 120), },
+ 2, { DesktopRect::MakeLTRB(0, 100, 200, 200),
+ DesktopRect::MakeLTRB(310, 110, 320, 120) } },
+ { 3, { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(50, 50, 150, 150),
+ DesktopRect::MakeLTRB(300, 125, 350, 175) },
+ 4, { DesktopRect::MakeLTRB(50, 50, 150, 100),
+ DesktopRect::MakeLTRB(50, 100, 200, 150),
+ DesktopRect::MakeLTRB(300, 125, 350, 175),
+ DesktopRect::MakeLTRB(100, 150, 200, 200) } },
+ { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30),
+ DesktopRect::MakeLTRB(10, 10, 40, 40),
+ DesktopRect::MakeLTRB(20, 20, 50, 50),
+ DesktopRect::MakeLTRB(50, 0, 65, 15) },
+ 6, { DesktopRect::MakeLTRB(0, 0, 30, 10),
+ DesktopRect::MakeLTRB(50, 0, 65, 15),
+ DesktopRect::MakeLTRB(0, 10, 40, 20),
+ DesktopRect::MakeLTRB(0, 20, 50, 30),
+ DesktopRect::MakeLTRB(10, 30, 50, 40),
+ DesktopRect::MakeLTRB(20, 40, 50, 50) } },
+ { 3, { DesktopRect::MakeLTRB(10, 10, 40, 20),
+ DesktopRect::MakeLTRB(10, 30, 40, 40),
+ DesktopRect::MakeLTRB(10, 20, 40, 30) },
+ 1, { DesktopRect::MakeLTRB(10, 10, 40, 40) } },
+ };
+
+ for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r;
+ r.AddRects(cases[i].input_rects, cases[i].input_count);
+ CompareRegion(r, cases[i].expected_rects, cases[i].expected_count);
+
+ // Try inserting rectangles in reverse order.
+ r.Clear();
+ for (int j = cases[i].input_count - 1; j >= 0; --j) {
+ r.AddRect(cases[i].input_rects[j]);
+ }
+ CompareRegion(r, cases[i].expected_rects, cases[i].expected_count);
+ }
+}
+
+TEST(DesktopRegionTest, Equals) {
+ struct Region {
+ int count;
+ DesktopRect rects[4];
+ int id;
+ } regions[] = {
+ // Same region with one of the rectangles 1 pixel wider/taller.
+ { 2, { DesktopRect::MakeLTRB(0, 100, 200, 200),
+ DesktopRect::MakeLTRB(310, 110, 320, 120) }, 0 },
+ { 2, { DesktopRect::MakeLTRB(0, 100, 201, 200),
+ DesktopRect::MakeLTRB(310, 110, 320, 120) }, 1 },
+ { 2, { DesktopRect::MakeLTRB(0, 100, 200, 201),
+ DesktopRect::MakeLTRB(310, 110, 320, 120) }, 2 },
+
+ // Same region with one of the rectangles shifted horizontally and
+ // vertically.
+ { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30),
+ DesktopRect::MakeLTRB(10, 10, 40, 40),
+ DesktopRect::MakeLTRB(20, 20, 50, 50),
+ DesktopRect::MakeLTRB(50, 0, 65, 15) }, 3 },
+ { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30),
+ DesktopRect::MakeLTRB(10, 10, 40, 40),
+ DesktopRect::MakeLTRB(20, 20, 50, 50),
+ DesktopRect::MakeLTRB(50, 1, 65, 16) }, 4 },
+ { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30),
+ DesktopRect::MakeLTRB(10, 10, 40, 40),
+ DesktopRect::MakeLTRB(20, 20, 50, 50),
+ DesktopRect::MakeLTRB(51, 0, 66, 15) }, 5 },
+
+ // Same region defined by a different set of rectangles - one of the
+ // rectangle is split horizontally into two.
+ { 3, { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(50, 50, 150, 150),
+ DesktopRect::MakeLTRB(300, 125, 350, 175) }, 6 },
+ { 4, { DesktopRect::MakeLTRB(100, 100, 200, 200),
+ DesktopRect::MakeLTRB(50, 50, 100, 150),
+ DesktopRect::MakeLTRB(100, 50, 150, 150),
+ DesktopRect::MakeLTRB(300, 125, 350, 175) }, 6 },
+
+ // Rectangle region defined by a set of rectangles that merge into one.
+ { 3, { DesktopRect::MakeLTRB(10, 10, 40, 20),
+ DesktopRect::MakeLTRB(10, 30, 40, 40),
+ DesktopRect::MakeLTRB(10, 20, 40, 30) }, 7 },
+ { 1, { DesktopRect::MakeLTRB(10, 10, 40, 40) }, 7 },
+ };
+ int kTotalRegions = sizeof(regions) / sizeof(Region);
+
+ for (int i = 0; i < kTotalRegions; ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r1(regions[i].rects, regions[i].count);
+ for (int j = 0; j < kTotalRegions; ++j) {
+ SCOPED_TRACE(j);
+
+ DesktopRegion r2(regions[j].rects, regions[j].count);
+ EXPECT_EQ(regions[i].id == regions[j].id, r1.Equals(r2));
+ }
+ }
+}
+
+TEST(DesktopRegionTest, Translate) {
+ struct Case {
+ int input_count;
+ DesktopRect input_rects[4];
+ int dx;
+ int dy;
+ int expected_count;
+ DesktopRect expected_rects[5];
+ } cases[] = {
+ { 3, { DesktopRect::MakeLTRB(0, 0, 30, 30),
+ DesktopRect::MakeLTRB(10, 10, 40, 40),
+ DesktopRect::MakeLTRB(20, 20, 50, 50) },
+ 3, 5,
+ 5, { DesktopRect::MakeLTRB(3, 5, 33, 15),
+ DesktopRect::MakeLTRB(3, 15, 43, 25),
+ DesktopRect::MakeLTRB(3, 25, 53, 35),
+ DesktopRect::MakeLTRB(13, 35, 53, 45),
+ DesktopRect::MakeLTRB(23, 45, 53, 55) } },
+ };
+
+ for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r(cases[i].input_rects, cases[i].input_count);
+ r.Translate(cases[i].dx, cases[i].dy);
+ CompareRegion(r, cases[i].expected_rects, cases[i].expected_count);
+ }
+}
+
+TEST(DesktopRegionTest, Intersect) {
+ struct Case {
+ int input1_count;
+ DesktopRect input1_rects[4];
+ int input2_count;
+ DesktopRect input2_rects[4];
+ int expected_count;
+ DesktopRect expected_rects[5];
+ } cases[] = {
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(50, 50, 150, 150) },
+ 1, { DesktopRect::MakeLTRB(50, 50, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(100, 0, 200, 300) },
+ 1, { DesktopRect::MakeLTRB(0, 100, 300, 200) },
+ 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 2, { DesktopRect::MakeLTRB(50, 10, 150, 30),
+ DesktopRect::MakeLTRB(50, 30, 160, 50) },
+ 1, { DesktopRect::MakeLTRB(50, 10, 100, 50) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 2, { DesktopRect::MakeLTRB(50, 10, 150, 30),
+ DesktopRect::MakeLTRB(50, 30, 90, 50) },
+ 2, { DesktopRect::MakeLTRB(50, 10, 100, 30),
+ DesktopRect::MakeLTRB(50, 30, 90, 50) } },
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(100, 50, 200, 200) },
+ 0, {} },
+ };
+
+ for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r1(cases[i].input1_rects, cases[i].input1_count);
+ DesktopRegion r2(cases[i].input2_rects, cases[i].input2_count);
+
+ DesktopRegion r;
+ r.Intersect(r1, r2);
+
+ CompareRegion(r, cases[i].expected_rects, cases[i].expected_count);
+ }
+}
+
+TEST(DesktopRegionTest, Subtract) {
+ struct Case {
+ int input1_count;
+ DesktopRect input1_rects[4];
+ int input2_count;
+ DesktopRect input2_rects[4];
+ int expected_count;
+ DesktopRect expected_rects[5];
+ } cases[] = {
+ // Subtract one rect from another.
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(50, 50, 150, 150) },
+ 2, { DesktopRect::MakeLTRB(0, 0, 100, 50),
+ DesktopRect::MakeLTRB(0, 50, 50, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(-50, -50, 50, 50) },
+ 2, { DesktopRect::MakeLTRB(50, 0, 100, 50),
+ DesktopRect::MakeLTRB(0, 50, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(-50, 50, 50, 150) },
+ 2, { DesktopRect::MakeLTRB(0, 0, 100, 50),
+ DesktopRect::MakeLTRB(50, 50, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(50, 50, 150, 70) },
+ 3, { DesktopRect::MakeLTRB(0, 0, 100, 50),
+ DesktopRect::MakeLTRB(0, 50, 50, 70),
+ DesktopRect::MakeLTRB(0, 70, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(50, 50, 70, 70) },
+ 4, { DesktopRect::MakeLTRB(0, 0, 100, 50),
+ DesktopRect::MakeLTRB(0, 50, 50, 70),
+ DesktopRect::MakeLTRB(70, 50, 100, 70),
+ DesktopRect::MakeLTRB(0, 70, 100, 100) } },
+
+ // Empty result.
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 0, {} },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(-10, -10, 110, 110) },
+ 0, {} },
+
+ { 2, { DesktopRect::MakeLTRB(0, 0, 100, 100),
+ DesktopRect::MakeLTRB(50, 50, 150, 150) },
+ 2, { DesktopRect::MakeLTRB(0, 0, 100, 100),
+ DesktopRect::MakeLTRB(50, 50, 150, 150) },
+ 0, {} },
+
+ // One rect out of disjoint set.
+ { 3, { DesktopRect::MakeLTRB(0, 0, 10, 10),
+ DesktopRect::MakeLTRB(20, 20, 30, 30),
+ DesktopRect::MakeLTRB(40, 0, 50, 10) },
+ 1, { DesktopRect::MakeLTRB(20, 20, 30, 30) },
+ 2, { DesktopRect::MakeLTRB(0, 0, 10, 10),
+ DesktopRect::MakeLTRB(40, 0, 50, 10) } },
+
+ // Row merging.
+ { 3, { DesktopRect::MakeLTRB(0, 0, 100, 50),
+ DesktopRect::MakeLTRB(0, 50, 150, 70),
+ DesktopRect::MakeLTRB(0, 70, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(100, 50, 150, 70) },
+ 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) } },
+
+ // No-op subtraction.
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(100, 0, 200, 100) },
+ 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(-100, 0, 0, 100) },
+ 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(0, 100, 0, 200) },
+ 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) } },
+
+ { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) },
+ 1, { DesktopRect::MakeLTRB(0, -100, 100, 0) },
+ 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) } },
+ };
+
+ for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) {
+ SCOPED_TRACE(i);
+
+ DesktopRegion r1(cases[i].input1_rects, cases[i].input1_count);
+ DesktopRegion r2(cases[i].input2_rects, cases[i].input2_count);
+
+ r1.Subtract(r2);
+
+ CompareRegion(r1, cases[i].expected_rects, cases[i].expected_count);
+ }
+}
+
+// Verify that DesktopRegion::SubtractRows() works correctly by creating a row
+// of not overlapping rectangles and subtracting a set of rectangle. Result
+// is verified by building a map of the region in an array and comparing it with
+// the expected values.
+TEST(DesktopRegionTest, SubtractRectOnSameRow) {
+ const int kMapWidth = 50;
+
+ struct SpanSet {
+ int count;
+ struct Range {
+ int start;
+ int end;
+ } spans[3];
+ } span_sets[] = {
+ {1, { {0, 3} } },
+ {1, { {0, 5} } },
+ {1, { {0, 7} } },
+ {1, { {0, 12} } },
+ {2, { {0, 3}, {4, 5}, {6, 16} } },
+ };
+
+ DesktopRegion base_region;
+ bool base_map[kMapWidth] = { false, };
+
+ base_region.AddRect(DesktopRect::MakeXYWH(5, 0, 5, 1));
+ std::fill_n(base_map + 5, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(15, 0, 5, 1));
+ std::fill_n(base_map + 15, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(25, 0, 5, 1));
+ std::fill_n(base_map + 25, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(35, 0, 5, 1));
+ std::fill_n(base_map + 35, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(45, 0, 5, 1));
+ std::fill_n(base_map + 45, 5, true);
+
+ for (size_t i = 0; i < sizeof(span_sets) / sizeof(span_sets[0]); i++) {
+ SCOPED_TRACE(i);
+ SpanSet& span_set = span_sets[i];
+ int span_set_end = span_set.spans[span_set.count - 1].end;
+ for (int x = 0; x < kMapWidth - span_set_end; ++x) {
+ SCOPED_TRACE(x);
+
+ DesktopRegion r = base_region;
+
+ bool expected_map[kMapWidth];
+ std::copy(base_map, base_map + kMapWidth, expected_map);
+
+ DesktopRegion region2;
+ for (int span = 0; span < span_set.count; span++) {
+ std::fill_n(x + expected_map + span_set.spans[span].start,
+ span_set.spans[span].end - span_set.spans[span].start,
+ false);
+ region2.AddRect(DesktopRect::MakeLTRB(x + span_set.spans[span].start, 0,
+ x + span_set.spans[span].end, 1));
+ }
+ r.Subtract(region2);
+
+ bool map[kMapWidth] = { false, };
+
+ int pos = -1;
+ for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) {
+ EXPECT_GT(it.rect().left(), pos);
+ pos = it.rect().right();
+ std::fill_n(map + it.rect().left(), it.rect().width(), true);
+ }
+
+ EXPECT_TRUE(std::equal(map, map + kMapWidth, expected_map));
+ }
+ }
+}
+
+// Verify that DesktopRegion::Subtract() works correctly by creating a column of
+// not overlapping rectangles and subtracting a set of rectangle on the same
+// column. Result is verified by building a map of the region in an array and
+// comparing it with the expected values.
+TEST(DesktopRegionTest, SubtractRectOnSameCol) {
+ const int kMapHeight = 50;
+
+ struct SpanSet {
+ int count;
+ struct Range {
+ int start;
+ int end;
+ } spans[3];
+ } span_sets[] = {
+ {1, { {0, 3} } },
+ {1, { {0, 5} } },
+ {1, { {0, 7} } },
+ {1, { {0, 12} } },
+ {2, { {0, 3}, {4, 5}, {6, 16} } },
+ };
+
+ DesktopRegion base_region;
+ bool base_map[kMapHeight] = { false, };
+
+ base_region.AddRect(DesktopRect::MakeXYWH(0, 5, 1, 5));
+ std::fill_n(base_map + 5, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(0, 15, 1, 5));
+ std::fill_n(base_map + 15, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(0, 25, 1, 5));
+ std::fill_n(base_map + 25, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(0, 35, 1, 5));
+ std::fill_n(base_map + 35, 5, true);
+ base_region.AddRect(DesktopRect::MakeXYWH(0, 45, 1, 5));
+ std::fill_n(base_map + 45, 5, true);
+
+ for (size_t i = 0; i < sizeof(span_sets) / sizeof(span_sets[0]); i++) {
+ SCOPED_TRACE(i);
+ SpanSet& span_set = span_sets[i];
+ int span_set_end = span_set.spans[span_set.count - 1].end;
+ for (int y = 0; y < kMapHeight - span_set_end; ++y) {
+ SCOPED_TRACE(y);
+
+ DesktopRegion r = base_region;
+
+ bool expected_map[kMapHeight];
+ std::copy(base_map, base_map + kMapHeight, expected_map);
+
+ DesktopRegion region2;
+ for (int span = 0; span < span_set.count; span++) {
+ std::fill_n(y + expected_map + span_set.spans[span].start,
+ span_set.spans[span].end - span_set.spans[span].start,
+ false);
+ region2.AddRect(DesktopRect::MakeLTRB(0, y + span_set.spans[span].start,
+ 1, y + span_set.spans[span].end));
+ }
+ r.Subtract(region2);
+
+ bool map[kMapHeight] = { false, };
+
+ int pos = -1;
+ for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) {
+ EXPECT_GT(it.rect().top(), pos);
+ pos = it.rect().bottom();
+ std::fill_n(map + it.rect().top(), it.rect().height(), true);
+ }
+
+ for (int j = 0; j < kMapHeight; j++) {
+ EXPECT_EQ(expected_map[j], map[j]) << "j = " << j;
+ }
+ }
+ }
+}
+
+
+TEST(DesktopRegionTest, DISABLED_Performance) {
+ for (int c = 0; c < 1000; ++c) {
+ DesktopRegion r;
+ for (int i = 0; i < 10; ++i) {
+ r.AddRect(DesktopRect::MakeXYWH(
+ RadmonInt(1000), RadmonInt(1000), 200, 200));
+ }
+
+ for (int i = 0; i < 1000; ++i) {
+ r.AddRect(DesktopRect::MakeXYWH(
+ RadmonInt(1000), RadmonInt(1000),
+ 5 + RadmonInt(10) * 5, 5 + RadmonInt(10) * 5));
+ }
+
+ // Iterate over the rectangles.
+ for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) {
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/differ.cc b/webrtc/modules/desktop_capture/differ.cc
new file mode 100644
index 0000000000..8140e612a1
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ.cc
@@ -0,0 +1,211 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/differ.h"
+
+#include "string.h"
+
+#include "webrtc/modules/desktop_capture/differ_block.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+Differ::Differ(int width, int height, int bpp, int stride) {
+ // Dimensions of screen.
+ width_ = width;
+ height_ = height;
+ bytes_per_pixel_ = bpp;
+ bytes_per_row_ = stride;
+
+ // Calc number of blocks (full and partial) required to cover entire image.
+ // One additional row/column is added as a boundary on the right & bottom.
+ diff_info_width_ = ((width_ + kBlockSize - 1) / kBlockSize) + 1;
+ diff_info_height_ = ((height_ + kBlockSize - 1) / kBlockSize) + 1;
+ diff_info_size_ = diff_info_width_ * diff_info_height_ * sizeof(bool);
+ diff_info_.reset(new bool[diff_info_size_]);
+}
+
+Differ::~Differ() {}
+
+void Differ::CalcDirtyRegion(const uint8_t* prev_buffer,
+ const uint8_t* curr_buffer,
+ DesktopRegion* region) {
+ // Identify all the blocks that contain changed pixels.
+ MarkDirtyBlocks(prev_buffer, curr_buffer);
+
+ // Now that we've identified the blocks that have changed, merge adjacent
+ // blocks to minimize the number of rects that we return.
+ MergeBlocks(region);
+}
+
+void Differ::MarkDirtyBlocks(const uint8_t* prev_buffer,
+ const uint8_t* curr_buffer) {
+ memset(diff_info_.get(), 0, diff_info_size_);
+
+ // Calc number of full blocks.
+ int x_full_blocks = width_ / kBlockSize;
+ int y_full_blocks = height_ / kBlockSize;
+
+ // Calc size of partial blocks which may be present on right and bottom edge.
+ int partial_column_width = width_ - (x_full_blocks * kBlockSize);
+ int partial_row_height = height_ - (y_full_blocks * kBlockSize);
+
+ // Offset from the start of one block-column to the next.
+ int block_x_offset = bytes_per_pixel_ * kBlockSize;
+ // Offset from the start of one block-row to the next.
+ int block_y_stride = (width_ * bytes_per_pixel_) * kBlockSize;
+ // Offset from the start of one diff_info row to the next.
+ int diff_info_stride = diff_info_width_ * sizeof(bool);
+
+ const uint8_t* prev_block_row_start = prev_buffer;
+ const uint8_t* curr_block_row_start = curr_buffer;
+ bool* diff_info_row_start = diff_info_.get();
+
+ for (int y = 0; y < y_full_blocks; y++) {
+ const uint8_t* prev_block = prev_block_row_start;
+ const uint8_t* curr_block = curr_block_row_start;
+ bool* diff_info = diff_info_row_start;
+
+ for (int x = 0; x < x_full_blocks; x++) {
+ // Mark this block as being modified so that it gets incorporated into
+ // a dirty rect.
+ *diff_info = BlockDifference(prev_block, curr_block, bytes_per_row_);
+ prev_block += block_x_offset;
+ curr_block += block_x_offset;
+ diff_info += sizeof(bool);
+ }
+
+ // If there is a partial column at the end, handle it.
+ // This condition should rarely, if ever, occur.
+ if (partial_column_width != 0) {
+ *diff_info = !PartialBlocksEqual(prev_block, curr_block, bytes_per_row_,
+ partial_column_width, kBlockSize);
+ diff_info += sizeof(bool);
+ }
+
+ // Update pointers for next row.
+ prev_block_row_start += block_y_stride;
+ curr_block_row_start += block_y_stride;
+ diff_info_row_start += diff_info_stride;
+ }
+
+ // If the screen height is not a multiple of the block size, then this
+ // handles the last partial row. This situation is far more common than the
+ // 'partial column' case.
+ if (partial_row_height != 0) {
+ const uint8_t* prev_block = prev_block_row_start;
+ const uint8_t* curr_block = curr_block_row_start;
+ bool* diff_info = diff_info_row_start;
+ for (int x = 0; x < x_full_blocks; x++) {
+ *diff_info = !PartialBlocksEqual(prev_block, curr_block,
+ bytes_per_row_,
+ kBlockSize, partial_row_height);
+ prev_block += block_x_offset;
+ curr_block += block_x_offset;
+ diff_info += sizeof(bool);
+ }
+ if (partial_column_width != 0) {
+ *diff_info = !PartialBlocksEqual(prev_block, curr_block, bytes_per_row_,
+ partial_column_width,
+ partial_row_height);
+ diff_info += sizeof(bool);
+ }
+ }
+}
+
+bool Differ::PartialBlocksEqual(const uint8_t* prev_buffer,
+ const uint8_t* curr_buffer,
+ int stride, int width, int height) {
+ int width_bytes = width * bytes_per_pixel_;
+ for (int y = 0; y < height; y++) {
+ if (memcmp(prev_buffer, curr_buffer, width_bytes) != 0)
+ return false;
+ prev_buffer += bytes_per_row_;
+ curr_buffer += bytes_per_row_;
+ }
+ return true;
+}
+
+void Differ::MergeBlocks(DesktopRegion* region) {
+ region->Clear();
+
+ bool* diff_info_row_start = diff_info_.get();
+ int diff_info_stride = diff_info_width_ * sizeof(bool);
+
+ for (int y = 0; y < diff_info_height_; y++) {
+ bool* diff_info = diff_info_row_start;
+ for (int x = 0; x < diff_info_width_; x++) {
+ if (*diff_info) {
+ // We've found a modified block. Look at blocks to the right and below
+ // to group this block with as many others as we can.
+ int left = x * kBlockSize;
+ int top = y * kBlockSize;
+ int width = 1;
+ int height = 1;
+ *diff_info = false;
+
+ // Group with blocks to the right.
+ // We can keep looking until we find an unchanged block because we
+ // have a boundary block which is never marked as having diffs.
+ bool* right = diff_info + 1;
+ while (*right) {
+ *right++ = false;
+ width++;
+ }
+
+ // Group with blocks below.
+ // The entire width of blocks that we matched above much match for
+ // each row that we add.
+ bool* bottom = diff_info;
+ bool found_new_row;
+ do {
+ found_new_row = true;
+ bottom += diff_info_stride;
+ right = bottom;
+ for (int x2 = 0; x2 < width; x2++) {
+ if (!*right++) {
+ found_new_row = false;
+ }
+ }
+
+ if (found_new_row) {
+ height++;
+
+ // We need to go back and erase the diff markers so that we don't
+ // try to add these blocks a second time.
+ right = bottom;
+ for (int x2 = 0; x2 < width; x2++) {
+ *right++ = false;
+ }
+ }
+ } while (found_new_row);
+
+ // Add rect to list of dirty rects.
+ width *= kBlockSize;
+ if (left + width > width_) {
+ width = width_ - left;
+ }
+ height *= kBlockSize;
+ if (top + height > height_) {
+ height = height_ - top;
+ }
+ region->AddRect(DesktopRect::MakeXYWH(left, top, width, height));
+ }
+
+ // Increment to next block in this row.
+ diff_info++;
+ }
+
+ // Go to start of next row.
+ diff_info_row_start += diff_info_stride;
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/differ.h b/webrtc/modules/desktop_capture/differ.h
new file mode 100644
index 0000000000..b3b0e7c244
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ.h
@@ -0,0 +1,89 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_
+
+#include <vector>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+
+namespace webrtc {
+
+// TODO(sergeyu): Simplify differ now that we are working with DesktopRegion.
+// diff_info_ should no longer be needed, as we can put our data directly into
+// the region that we are calculating.
+// http://crbug.com/92379
+// TODO(sergeyu): Rename this class to something more sensible, e.g.
+// ScreenCaptureFrameDifferencer.
+class Differ {
+ public:
+ // Create a differ that operates on bitmaps with the specified width, height
+ // and bytes_per_pixel.
+ Differ(int width, int height, int bytes_per_pixel, int stride);
+ ~Differ();
+
+ int width() { return width_; }
+ int height() { return height_; }
+ int bytes_per_pixel() { return bytes_per_pixel_; }
+ int bytes_per_row() { return bytes_per_row_; }
+
+ // Given the previous and current screen buffer, calculate the dirty region
+ // that encloses all of the changed pixels in the new screen.
+ void CalcDirtyRegion(const uint8_t* prev_buffer, const uint8_t* curr_buffer,
+ DesktopRegion* region);
+
+ private:
+ // Allow tests to access our private parts.
+ friend class DifferTest;
+
+ // Identify all of the blocks that contain changed pixels.
+ void MarkDirtyBlocks(const uint8_t* prev_buffer, const uint8_t* curr_buffer);
+
+ // After the dirty blocks have been identified, this routine merges adjacent
+ // blocks into a region.
+ // The goal is to minimize the region that covers the dirty blocks.
+ void MergeBlocks(DesktopRegion* region);
+
+ // Checks whether the upper-left portions of the buffers are equal. The size
+ // of the portion to check is specified by the |width| and |height| values.
+ // Note that if we force the capturer to always return images whose width and
+ // height are multiples of kBlockSize, then this will never be called.
+ bool PartialBlocksEqual(const uint8_t* prev_buffer,
+ const uint8_t* curr_buffer,
+ int stride,
+ int width, int height);
+
+ // Dimensions of screen.
+ int width_;
+ int height_;
+
+ // Number of bytes for each pixel in source and dest bitmap.
+ // (Yes, they must match.)
+ int bytes_per_pixel_;
+
+ // Number of bytes in each row of the image (AKA: stride).
+ int bytes_per_row_;
+
+ // Diff information for each block in the image.
+ rtc::scoped_ptr<bool[]> diff_info_;
+
+ // Dimensions and total size of diff info array.
+ int diff_info_width_;
+ int diff_info_height_;
+ int diff_info_size_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(Differ);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_
diff --git a/webrtc/modules/desktop_capture/differ_block.cc b/webrtc/modules/desktop_capture/differ_block.cc
new file mode 100644
index 0000000000..0dcb377411
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ_block.cc
@@ -0,0 +1,61 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/differ_block.h"
+
+#include <string.h>
+
+#include "build/build_config.h"
+#include "webrtc/modules/desktop_capture/differ_block_sse2.h"
+#include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
+
+namespace webrtc {
+
+bool BlockDifference_C(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride) {
+ int width_bytes = kBlockSize * kBytesPerPixel;
+
+ for (int y = 0; y < kBlockSize; y++) {
+ if (memcmp(image1, image2, width_bytes) != 0)
+ return true;
+ image1 += stride;
+ image2 += stride;
+ }
+ return false;
+}
+
+bool BlockDifference(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride) {
+ static bool (*diff_proc)(const uint8_t*, const uint8_t*, int) = NULL;
+
+ if (!diff_proc) {
+#if defined(ARCH_CPU_ARM_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY)
+ // For ARM and MIPS processors, always use C version.
+ // TODO(hclam): Implement a NEON version.
+ diff_proc = &BlockDifference_C;
+#else
+ bool have_sse2 = WebRtc_GetCPUInfo(kSSE2) != 0;
+ // For x86 processors, check if SSE2 is supported.
+ if (have_sse2 && kBlockSize == 32) {
+ diff_proc = &BlockDifference_SSE2_W32;
+ } else if (have_sse2 && kBlockSize == 16) {
+ diff_proc = &BlockDifference_SSE2_W16;
+ } else {
+ diff_proc = &BlockDifference_C;
+ }
+#endif
+ }
+
+ return diff_proc(image1, image2, stride);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/differ_block.h b/webrtc/modules/desktop_capture/differ_block.h
new file mode 100644
index 0000000000..e1d487d68b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ_block.h
@@ -0,0 +1,33 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_
+
+#include <stdint.h>
+
+namespace webrtc {
+
+// Size (in pixels) of each square block used for diffing. This must be a
+// multiple of sizeof(uint64)/8.
+const int kBlockSize = 32;
+
+// Format: BGRA 32 bit.
+const int kBytesPerPixel = 4;
+
+// Low level function to compare 2 blocks of pixels of size
+// (kBlockSize, kBlockSize). Returns whether the blocks differ.
+bool BlockDifference(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride);
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_
diff --git a/webrtc/modules/desktop_capture/differ_block_sse2.cc b/webrtc/modules/desktop_capture/differ_block_sse2.cc
new file mode 100644
index 0000000000..8d35df2226
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ_block_sse2.cc
@@ -0,0 +1,120 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/differ_block_sse2.h"
+
+#if defined(_MSC_VER)
+#include <intrin.h>
+#else
+#include <mmintrin.h>
+#include <emmintrin.h>
+#endif
+
+#include "webrtc/modules/desktop_capture/differ_block.h"
+
+namespace webrtc {
+
+extern bool BlockDifference_SSE2_W16(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride) {
+ __m128i acc = _mm_setzero_si128();
+ __m128i v0;
+ __m128i v1;
+ __m128i sad;
+ for (int y = 0; y < kBlockSize; ++y) {
+ const __m128i* i1 = reinterpret_cast<const __m128i*>(image1);
+ const __m128i* i2 = reinterpret_cast<const __m128i*>(image2);
+ v0 = _mm_loadu_si128(i1);
+ v1 = _mm_loadu_si128(i2);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 1);
+ v1 = _mm_loadu_si128(i2 + 1);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 2);
+ v1 = _mm_loadu_si128(i2 + 2);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 3);
+ v1 = _mm_loadu_si128(i2 + 3);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+
+ // This essential means sad = acc >> 64. We only care about the lower 16
+ // bits.
+ sad = _mm_shuffle_epi32(acc, 0xEE);
+ sad = _mm_adds_epu16(sad, acc);
+ int diff = _mm_cvtsi128_si32(sad);
+ if (diff)
+ return true;
+ image1 += stride;
+ image2 += stride;
+ }
+ return false;
+}
+
+extern bool BlockDifference_SSE2_W32(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride) {
+ __m128i acc = _mm_setzero_si128();
+ __m128i v0;
+ __m128i v1;
+ __m128i sad;
+ for (int y = 0; y < kBlockSize; ++y) {
+ const __m128i* i1 = reinterpret_cast<const __m128i*>(image1);
+ const __m128i* i2 = reinterpret_cast<const __m128i*>(image2);
+ v0 = _mm_loadu_si128(i1);
+ v1 = _mm_loadu_si128(i2);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 1);
+ v1 = _mm_loadu_si128(i2 + 1);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 2);
+ v1 = _mm_loadu_si128(i2 + 2);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 3);
+ v1 = _mm_loadu_si128(i2 + 3);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 4);
+ v1 = _mm_loadu_si128(i2 + 4);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 5);
+ v1 = _mm_loadu_si128(i2 + 5);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 6);
+ v1 = _mm_loadu_si128(i2 + 6);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+ v0 = _mm_loadu_si128(i1 + 7);
+ v1 = _mm_loadu_si128(i2 + 7);
+ sad = _mm_sad_epu8(v0, v1);
+ acc = _mm_adds_epu16(acc, sad);
+
+ // This essential means sad = acc >> 64. We only care about the lower 16
+ // bits.
+ sad = _mm_shuffle_epi32(acc, 0xEE);
+ sad = _mm_adds_epu16(sad, acc);
+ int diff = _mm_cvtsi128_si32(sad);
+ if (diff)
+ return true;
+ image1 += stride;
+ image2 += stride;
+ }
+ return false;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/differ_block_sse2.h b/webrtc/modules/desktop_capture/differ_block_sse2.h
new file mode 100644
index 0000000000..90426dafab
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ_block_sse2.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+// This header file is used only differ_block.h. It defines the SSE2 rountines
+// for finding block difference.
+
+#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_
+
+#include <stdint.h>
+
+namespace webrtc {
+
+// Find block difference of dimension 16x16.
+extern bool BlockDifference_SSE2_W16(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride);
+
+// Find block difference of dimension 32x32.
+extern bool BlockDifference_SSE2_W32(const uint8_t* image1,
+ const uint8_t* image2,
+ int stride);
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_
diff --git a/webrtc/modules/desktop_capture/differ_block_unittest.cc b/webrtc/modules/desktop_capture/differ_block_unittest.cc
new file mode 100644
index 0000000000..df9f4d517a
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ_block_unittest.cc
@@ -0,0 +1,87 @@
+/*
+ * 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 "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/modules/desktop_capture/differ_block.h"
+#include "webrtc/system_wrappers/include/ref_count.h"
+
+namespace webrtc {
+
+// Run 900 times to mimic 1280x720.
+// TODO(fbarchard): Remove benchmark once performance is non-issue.
+static const int kTimesToRun = 900;
+
+static void GenerateData(uint8_t* data, int size) {
+ for (int i = 0; i < size; ++i) {
+ data[i] = i;
+ }
+}
+
+// Memory buffer large enough for 2 blocks aligned to 16 bytes.
+static const int kSizeOfBlock = kBlockSize * kBlockSize * kBytesPerPixel;
+uint8_t block_buffer[kSizeOfBlock * 2 + 16];
+
+void PrepareBuffers(uint8_t* &block1, uint8_t* &block2) {
+ block1 = reinterpret_cast<uint8_t*>
+ ((reinterpret_cast<uintptr_t>(&block_buffer[0]) + 15) & ~15);
+ GenerateData(block1, kSizeOfBlock);
+ block2 = block1 + kSizeOfBlock;
+ memcpy(block2, block1, kSizeOfBlock);
+}
+
+TEST(BlockDifferenceTestSame, BlockDifference) {
+ uint8_t* block1;
+ uint8_t* block2;
+ PrepareBuffers(block1, block2);
+
+ // These blocks should match.
+ for (int i = 0; i < kTimesToRun; ++i) {
+ int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel);
+ EXPECT_EQ(0, result);
+ }
+}
+
+TEST(BlockDifferenceTestLast, BlockDifference) {
+ uint8_t* block1;
+ uint8_t* block2;
+ PrepareBuffers(block1, block2);
+ block2[kSizeOfBlock-2] += 1;
+
+ for (int i = 0; i < kTimesToRun; ++i) {
+ int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel);
+ EXPECT_EQ(1, result);
+ }
+}
+
+TEST(BlockDifferenceTestMid, BlockDifference) {
+ uint8_t* block1;
+ uint8_t* block2;
+ PrepareBuffers(block1, block2);
+ block2[kSizeOfBlock/2+1] += 1;
+
+ for (int i = 0; i < kTimesToRun; ++i) {
+ int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel);
+ EXPECT_EQ(1, result);
+ }
+}
+
+TEST(BlockDifferenceTestFirst, BlockDifference) {
+ uint8_t* block1;
+ uint8_t* block2;
+ PrepareBuffers(block1, block2);
+ block2[0] += 1;
+
+ for (int i = 0; i < kTimesToRun; ++i) {
+ int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel);
+ EXPECT_EQ(1, result);
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/differ_unittest.cc b/webrtc/modules/desktop_capture/differ_unittest.cc
new file mode 100644
index 0000000000..642cb37448
--- /dev/null
+++ b/webrtc/modules/desktop_capture/differ_unittest.cc
@@ -0,0 +1,652 @@
+/*
+ * 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 "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/differ.h"
+#include "webrtc/modules/desktop_capture/differ_block.h"
+
+namespace webrtc {
+
+// 96x96 screen gives a 4x4 grid of blocks.
+const int kScreenWidth= 96;
+const int kScreenHeight = 96;
+
+// To test partial blocks, we need a width and height that are not multiples
+// of 16 (or 32, depending on current block size).
+const int kPartialScreenWidth = 70;
+const int kPartialScreenHeight = 70;
+
+class DifferTest : public testing::Test {
+ public:
+ DifferTest() {
+ }
+
+ protected:
+ void InitDiffer(int width, int height) {
+ width_ = width;
+ height_ = height;
+ bytes_per_pixel_ = kBytesPerPixel;
+ stride_ = (kBytesPerPixel * width);
+ buffer_size_ = width_ * height_ * bytes_per_pixel_;
+
+ differ_.reset(new Differ(width_, height_, bytes_per_pixel_, stride_));
+
+ prev_.reset(new uint8_t[buffer_size_]);
+ memset(prev_.get(), 0, buffer_size_);
+
+ curr_.reset(new uint8_t[buffer_size_]);
+ memset(curr_.get(), 0, buffer_size_);
+ }
+
+ void ClearBuffer(uint8_t* buffer) {
+ memset(buffer, 0, buffer_size_);
+ }
+
+ // Here in DifferTest so that tests can access private methods of Differ.
+ void MarkDirtyBlocks(const uint8_t* prev_buffer, const uint8_t* curr_buffer) {
+ differ_->MarkDirtyBlocks(prev_buffer, curr_buffer);
+ }
+
+ void MergeBlocks(DesktopRegion* dirty) {
+ differ_->MergeBlocks(dirty);
+ }
+
+ // Convenience method to count rectangles in a region.
+ int RegionRectCount(const DesktopRegion& region) {
+ int count = 0;
+ for (DesktopRegion::Iterator iter(region);
+ !iter.IsAtEnd(); iter.Advance()) {
+ ++count;
+ }
+ return count;
+ }
+
+ // Convenience wrapper for Differ's DiffBlock that calculates the appropriate
+ // offset to the start of the desired block.
+ bool DiffBlock(int block_x, int block_y) {
+ // Offset from upper-left of buffer to upper-left of requested block.
+ int block_offset = ((block_y * stride_) + (block_x * bytes_per_pixel_))
+ * kBlockSize;
+ return BlockDifference(prev_.get() + block_offset,
+ curr_.get() + block_offset,
+ stride_);
+ }
+
+ // Write the pixel |value| into the specified block in the |buffer|.
+ // This is a convenience wrapper around WritePixel().
+ void WriteBlockPixel(uint8_t* buffer, int block_x, int block_y,
+ int pixel_x, int pixel_y, uint32_t value) {
+ WritePixel(buffer, (block_x * kBlockSize) + pixel_x,
+ (block_y * kBlockSize) + pixel_y, value);
+ }
+
+ // Write the test pixel |value| into the |buffer| at the specified |x|,|y|
+ // location.
+ // Only the low-order bytes from |value| are written (assuming little-endian).
+ // So, for |value| = 0xaabbccdd:
+ // If bytes_per_pixel = 4, then ddccbbaa will be written as the pixel value.
+ // If = 3, ddccbb
+ // If = 2, ddcc
+ // If = 1, dd
+ void WritePixel(uint8_t* buffer, int x, int y, uint32_t value) {
+ uint8_t* pixel = reinterpret_cast<uint8_t*>(&value);
+ buffer += (y * stride_) + (x * bytes_per_pixel_);
+ for (int b = bytes_per_pixel_ - 1; b >= 0; b--) {
+ *buffer++ = pixel[b];
+ }
+ }
+
+ // DiffInfo utility routines.
+ // These are here so that we don't have to make each DifferText_Xxx_Test
+ // class a friend class to Differ.
+
+ // Clear out the entire |diff_info_| buffer.
+ void ClearDiffInfo() {
+ memset(differ_->diff_info_.get(), 0, differ_->diff_info_size_);
+ }
+
+ // Get the value in the |diff_info_| array at (x,y).
+ bool GetDiffInfo(int x, int y) {
+ bool* diff_info = differ_->diff_info_.get();
+ return diff_info[(y * GetDiffInfoWidth()) + x];
+ }
+
+ // Width of |diff_info_| array.
+ int GetDiffInfoWidth() {
+ return differ_->diff_info_width_;
+ }
+
+ // Height of |diff_info_| array.
+ int GetDiffInfoHeight() {
+ return differ_->diff_info_height_;
+ }
+
+ // Size of |diff_info_| array.
+ int GetDiffInfoSize() {
+ return differ_->diff_info_size_;
+ }
+
+ void SetDiffInfo(int x, int y, bool value) {
+ bool* diff_info = differ_->diff_info_.get();
+ diff_info[(y * GetDiffInfoWidth()) + x] = value;
+ }
+
+ // Mark the range of blocks specified.
+ void MarkBlocks(int x_origin, int y_origin, int width, int height) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ SetDiffInfo(x_origin + x, y_origin + y, true);
+ }
+ }
+ }
+
+ // Verify that |region| contains a rectangle defined by |x|, |y|, |width| and
+ // |height|.
+ // |x|, |y|, |width| and |height| are specified in block (not pixel) units.
+ bool CheckDirtyRegionContainsRect(const DesktopRegion& region,
+ int x, int y,
+ int width, int height) {
+ DesktopRect r =
+ DesktopRect::MakeXYWH(x * kBlockSize, y * kBlockSize,
+ width * kBlockSize, height * kBlockSize);
+ for (DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) {
+ if (i.rect().equals(r))
+ return true;
+ }
+ return false;
+ }
+
+ // Mark the range of blocks specified and then verify that they are
+ // merged correctly.
+ // Only one rectangular region of blocks can be checked with this routine.
+ bool MarkBlocksAndCheckMerge(int x_origin, int y_origin,
+ int width, int height) {
+ ClearDiffInfo();
+ MarkBlocks(x_origin, y_origin, width, height);
+
+ DesktopRegion dirty;
+ MergeBlocks(&dirty);
+
+
+ DesktopRect expected_rect = DesktopRect::MakeXYWH(
+ x_origin * kBlockSize, y_origin * kBlockSize,
+ width * kBlockSize, height * kBlockSize);
+
+ // Verify that the region contains expected_rect and it's the only
+ // rectangle.
+ DesktopRegion::Iterator it(dirty);
+ return !it.IsAtEnd() && expected_rect.equals(it.rect()) &&
+ (it.Advance(), it.IsAtEnd());
+ }
+
+ // The differ class we're testing.
+ rtc::scoped_ptr<Differ> differ_;
+
+ // Screen/buffer info.
+ int width_;
+ int height_;
+ int bytes_per_pixel_;
+ int stride_;
+
+ // Size of each screen buffer.
+ int buffer_size_;
+
+ // Previous and current screen buffers.
+ rtc::scoped_ptr<uint8_t[]> prev_;
+ rtc::scoped_ptr<uint8_t[]> curr_;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(DifferTest);
+};
+
+TEST_F(DifferTest, Setup) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+ // 96x96 pixels results in 3x3 array. Add 1 to each dimension as boundary.
+ // +---+---+---+---+
+ // | o | o | o | _ |
+ // +---+---+---+---+ o = blocks mapped to screen pixels
+ // | o | o | o | _ |
+ // +---+---+---+---+ _ = boundary blocks
+ // | o | o | o | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ EXPECT_EQ(4, GetDiffInfoWidth());
+ EXPECT_EQ(4, GetDiffInfoHeight());
+ EXPECT_EQ(16, GetDiffInfoSize());
+}
+
+TEST_F(DifferTest, MarkDirtyBlocks_All) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+ ClearDiffInfo();
+
+ // Update a pixel in each block.
+ for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
+ for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
+ WriteBlockPixel(curr_.get(), x, y, 10, 10, 0xff00ff);
+ }
+ }
+
+ MarkDirtyBlocks(prev_.get(), curr_.get());
+
+ // Make sure each block is marked as dirty.
+ for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
+ for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
+ EXPECT_TRUE(GetDiffInfo(x, y))
+ << "when x = " << x << ", and y = " << y;
+ }
+ }
+}
+
+TEST_F(DifferTest, MarkDirtyBlocks_Sampling) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+ ClearDiffInfo();
+
+ // Update some pixels in image.
+ WriteBlockPixel(curr_.get(), 1, 0, 10, 10, 0xff00ff);
+ WriteBlockPixel(curr_.get(), 2, 1, 10, 10, 0xff00ff);
+ WriteBlockPixel(curr_.get(), 0, 2, 10, 10, 0xff00ff);
+
+ MarkDirtyBlocks(prev_.get(), curr_.get());
+
+ // Make sure corresponding blocks are updated.
+ EXPECT_FALSE(GetDiffInfo(0, 0));
+ EXPECT_FALSE(GetDiffInfo(0, 1));
+ EXPECT_TRUE(GetDiffInfo(0, 2));
+ EXPECT_TRUE(GetDiffInfo(1, 0));
+ EXPECT_FALSE(GetDiffInfo(1, 1));
+ EXPECT_FALSE(GetDiffInfo(1, 2));
+ EXPECT_FALSE(GetDiffInfo(2, 0));
+ EXPECT_TRUE(GetDiffInfo(2, 1));
+ EXPECT_FALSE(GetDiffInfo(2, 2));
+}
+
+TEST_F(DifferTest, DiffBlock) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+
+ // Verify no differences at start.
+ EXPECT_FALSE(DiffBlock(0, 0));
+ EXPECT_FALSE(DiffBlock(1, 1));
+
+ // Write new data into the 4 corners of the middle block and verify that
+ // neighboring blocks are not affected.
+ int max = kBlockSize - 1;
+ WriteBlockPixel(curr_.get(), 1, 1, 0, 0, 0xffffff);
+ WriteBlockPixel(curr_.get(), 1, 1, 0, max, 0xffffff);
+ WriteBlockPixel(curr_.get(), 1, 1, max, 0, 0xffffff);
+ WriteBlockPixel(curr_.get(), 1, 1, max, max, 0xffffff);
+ EXPECT_FALSE(DiffBlock(0, 0));
+ EXPECT_FALSE(DiffBlock(0, 1));
+ EXPECT_FALSE(DiffBlock(0, 2));
+ EXPECT_FALSE(DiffBlock(1, 0));
+ EXPECT_TRUE(DiffBlock(1, 1)); // Only this block should change.
+ EXPECT_FALSE(DiffBlock(1, 2));
+ EXPECT_FALSE(DiffBlock(2, 0));
+ EXPECT_FALSE(DiffBlock(2, 1));
+ EXPECT_FALSE(DiffBlock(2, 2));
+}
+
+TEST_F(DifferTest, Partial_Setup) {
+ InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
+ // 70x70 pixels results in 3x3 array: 2x2 full blocks + partials around
+ // the edge. One more is added to each dimension as a boundary.
+ // +---+---+---+---+
+ // | o | o | + | _ |
+ // +---+---+---+---+ o = blocks mapped to screen pixels
+ // | o | o | + | _ |
+ // +---+---+---+---+ + = partial blocks (top/left mapped to screen pixels)
+ // | + | + | + | _ |
+ // +---+---+---+---+ _ = boundary blocks
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ EXPECT_EQ(4, GetDiffInfoWidth());
+ EXPECT_EQ(4, GetDiffInfoHeight());
+ EXPECT_EQ(16, GetDiffInfoSize());
+}
+
+TEST_F(DifferTest, Partial_FirstPixel) {
+ InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
+ ClearDiffInfo();
+
+ // Update the first pixel in each block.
+ for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
+ for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
+ WriteBlockPixel(curr_.get(), x, y, 0, 0, 0xff00ff);
+ }
+ }
+
+ MarkDirtyBlocks(prev_.get(), curr_.get());
+
+ // Make sure each block is marked as dirty.
+ for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
+ for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
+ EXPECT_TRUE(GetDiffInfo(x, y))
+ << "when x = " << x << ", and y = " << y;
+ }
+ }
+}
+
+TEST_F(DifferTest, Partial_BorderPixel) {
+ InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
+ ClearDiffInfo();
+
+ // Update the right/bottom border pixels.
+ for (int y = 0; y < height_; y++) {
+ WritePixel(curr_.get(), width_ - 1, y, 0xff00ff);
+ }
+ for (int x = 0; x < width_; x++) {
+ WritePixel(curr_.get(), x, height_ - 1, 0xff00ff);
+ }
+
+ MarkDirtyBlocks(prev_.get(), curr_.get());
+
+ // Make sure last (partial) block in each row/column is marked as dirty.
+ int x_last = GetDiffInfoWidth() - 2;
+ for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
+ EXPECT_TRUE(GetDiffInfo(x_last, y))
+ << "when x = " << x_last << ", and y = " << y;
+ }
+ int y_last = GetDiffInfoHeight() - 2;
+ for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
+ EXPECT_TRUE(GetDiffInfo(x, y_last))
+ << "when x = " << x << ", and y = " << y_last;
+ }
+ // All other blocks are clean.
+ for (int y = 0; y < GetDiffInfoHeight() - 2; y++) {
+ for (int x = 0; x < GetDiffInfoWidth() - 2; x++) {
+ EXPECT_FALSE(GetDiffInfo(x, y)) << "when x = " << x << ", and y = " << y;
+ }
+ }
+}
+
+TEST_F(DifferTest, MergeBlocks_Empty) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+
+ // No blocks marked:
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+
+ DesktopRegion dirty;
+ MergeBlocks(&dirty);
+
+ EXPECT_TRUE(dirty.is_empty());
+}
+
+TEST_F(DifferTest, MergeBlocks_SingleBlock) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+ // Mark a single block and make sure that there is a single merged
+ // rect with the correct bounds.
+ for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
+ for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(x, y, 1, 1)) << "x: " << x
+ << "y: " << y;
+ }
+ }
+}
+
+TEST_F(DifferTest, MergeBlocks_BlockRow) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+
+ // +---+---+---+---+
+ // | X | X | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 1));
+
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | X | X | X | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 1));
+
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | X | X | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 2, 2, 1));
+}
+
+TEST_F(DifferTest, MergeBlocks_BlockColumn) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+
+ // +---+---+---+---+
+ // | X | | | _ |
+ // +---+---+---+---+
+ // | X | | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 1, 2));
+
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | X | | _ |
+ // +---+---+---+---+
+ // | | X | | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 1, 2));
+
+ // +---+---+---+---+
+ // | | | X | _ |
+ // +---+---+---+---+
+ // | | | X | _ |
+ // +---+---+---+---+
+ // | | | X | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(2, 0, 1, 3));
+}
+
+TEST_F(DifferTest, MergeBlocks_BlockRect) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+
+ // +---+---+---+---+
+ // | X | X | | _ |
+ // +---+---+---+---+
+ // | X | X | | _ |
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 2));
+
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | | X | X | _ |
+ // +---+---+---+---+
+ // | | X | X | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 2, 2));
+
+ // +---+---+---+---+
+ // | | X | X | _ |
+ // +---+---+---+---+
+ // | | X | X | _ |
+ // +---+---+---+---+
+ // | | X | X | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 0, 2, 3));
+
+ // +---+---+---+---+
+ // | | | | _ |
+ // +---+---+---+---+
+ // | X | X | X | _ |
+ // +---+---+---+---+
+ // | X | X | X | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 2));
+
+ // +---+---+---+---+
+ // | X | X | X | _ |
+ // +---+---+---+---+
+ // | X | X | X | _ |
+ // +---+---+---+---+
+ // | X | X | X | _ |
+ // +---+---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 3, 3));
+}
+
+// This tests marked regions that require more than 1 single dirty rect.
+// The exact rects returned depend on the current implementation, so these
+// may need to be updated if we modify how we merge blocks.
+TEST_F(DifferTest, MergeBlocks_MultiRect) {
+ InitDiffer(kScreenWidth, kScreenHeight);
+ DesktopRegion dirty;
+
+ // +---+---+---+---+ +---+---+---+
+ // | | X | | _ | | | 0 | |
+ // +---+---+---+---+ +---+---+---+
+ // | X | | | _ | | 1 | | |
+ // +---+---+---+---+ => +---+---+---+
+ // | | | X | _ | | | | 2 |
+ // +---+---+---+---+ +---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+ MarkBlocks(1, 0, 1, 1);
+ MarkBlocks(0, 1, 1, 1);
+ MarkBlocks(2, 2, 1, 1);
+
+ dirty.Clear();
+ MergeBlocks(&dirty);
+
+ ASSERT_EQ(3, RegionRectCount(dirty));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 0, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 2, 1, 1));
+
+ // +---+---+---+---+ +---+---+---+
+ // | | | X | _ | | | | 0 |
+ // +---+---+---+---+ +---+---+---+
+ // | X | X | X | _ | | 1 1 1 |
+ // +---+---+---+---+ => + +
+ // | X | X | X | _ | | 1 1 1 |
+ // +---+---+---+---+ +---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+ MarkBlocks(2, 0, 1, 1);
+ MarkBlocks(0, 1, 3, 2);
+
+ dirty.Clear();
+ MergeBlocks(&dirty);
+
+ ASSERT_EQ(2, RegionRectCount(dirty));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 0, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 3, 2));
+
+ // +---+---+---+---+ +---+---+---+
+ // | | | | _ | | | | |
+ // +---+---+---+---+ +---+---+---+
+ // | X | | X | _ | | 0 | | 1 |
+ // +---+---+---+---+ => +---+---+---+
+ // | X | X | X | _ | | 2 2 2 |
+ // +---+---+---+---+ +---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+ MarkBlocks(0, 1, 1, 1);
+ MarkBlocks(2, 1, 1, 1);
+ MarkBlocks(0, 2, 3, 1);
+
+ dirty.Clear();
+ MergeBlocks(&dirty);
+
+ ASSERT_EQ(3, RegionRectCount(dirty));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1));
+
+ // +---+---+---+---+ +---+---+---+
+ // | X | X | X | _ | | 0 0 0 |
+ // +---+---+---+---+ +---+---+---+
+ // | X | | X | _ | | 1 | | 2 |
+ // +---+---+---+---+ => +---+---+---+
+ // | X | X | X | _ | | 3 3 3 |
+ // +---+---+---+---+ +---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+ MarkBlocks(0, 0, 3, 1);
+ MarkBlocks(0, 1, 1, 1);
+ MarkBlocks(2, 1, 1, 1);
+ MarkBlocks(0, 2, 3, 1);
+
+ dirty.Clear();
+ MergeBlocks(&dirty);
+
+ ASSERT_EQ(4, RegionRectCount(dirty));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 3, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1));
+
+ // +---+---+---+---+ +---+---+---+
+ // | X | X | | _ | | 0 0 | |
+ // +---+---+---+---+ + +---+
+ // | X | X | | _ | | 0 0 | |
+ // +---+---+---+---+ => +---+---+---+
+ // | | X | | _ | | | 1 | |
+ // +---+---+---+---+ +---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+ MarkBlocks(0, 0, 2, 2);
+ MarkBlocks(1, 2, 1, 1);
+
+ dirty.Clear();
+ MergeBlocks(&dirty);
+
+ ASSERT_EQ(2, RegionRectCount(dirty));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 2, 2));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 2, 1, 1));
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.h b/webrtc/modules/desktop_capture/mac/desktop_configuration.h
new file mode 100644
index 0000000000..bb2339bb0f
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.h
@@ -0,0 +1,78 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Carbon/Carbon.h>
+#include <vector>
+
+#include "webrtc/typedefs.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+
+namespace webrtc {
+
+// Describes the configuration of a specific display.
+struct MacDisplayConfiguration {
+ MacDisplayConfiguration();
+
+ // Cocoa identifier for this display.
+ CGDirectDisplayID id;
+
+ // 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;
+};
+
+typedef std::vector<MacDisplayConfiguration> MacDisplayConfigurations;
+
+// Describes the configuration of the whole desktop.
+struct MacDesktopConfiguration {
+ // Used to request bottom-up or top-down coordinates.
+ enum Origin { BottomLeftOrigin, TopLeftOrigin };
+
+ MacDesktopConfiguration();
+ ~MacDesktopConfiguration();
+
+ // Returns the desktop & display configurations in Cocoa-style "bottom-up"
+ // (the origin is the bottom-left of the primary monitor, and coordinates
+ // increase as you move up the screen) or Carbon-style "top-down" coordinates.
+ static MacDesktopConfiguration GetCurrent(Origin origin);
+
+ // Returns true if the given desktop configuration equals this one.
+ bool Equals(const MacDesktopConfiguration& other);
+
+ // Returns the pointer to the display configuration with the specified id.
+ 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;
+
+ // Configurations of the displays making up the desktop area.
+ MacDisplayConfigurations displays;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_
diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.mm b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm
new file mode 100644
index 0000000000..9e483e5b81
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm
@@ -0,0 +1,180 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
+
+#include <math.h>
+#include <algorithm>
+#include <Cocoa/Cocoa.h>
+
+#include "webrtc/system_wrappers/include/logging.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)));
+}
+
+DesktopRect JoinRects(const DesktopRect& a,
+ const DesktopRect& b) {
+ return DesktopRect::MakeLTRB(
+ std::min(a.left(), b.left()),
+ std::min(a.top(), b.top()),
+ std::max(a.right(), b.right()),
+ std::max(a.bottom(), b.bottom()));
+}
+
+// Inverts the position of |rect| from bottom-up coordinates to top-down,
+// relative to |bounds|.
+void InvertRectYOrigin(const DesktopRect& bounds,
+ DesktopRect* rect) {
+ assert(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;
+ }
+
+ return display_config;
+}
+
+} // namespace
+
+MacDisplayConfiguration::MacDisplayConfiguration()
+ : id(0),
+ dip_to_pixel_scale(1.0f) {
+}
+
+MacDesktopConfiguration::MacDesktopConfiguration()
+ : dip_to_pixel_scale(1.0f) {
+}
+
+MacDesktopConfiguration::~MacDesktopConfiguration() {
+}
+
+// static
+MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) {
+ MacDesktopConfiguration desktop_config;
+
+ NSArray* screens = [NSScreen screens];
+ assert(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 =
+ JoinRects(desktop_config.bounds, display_config.bounds);
+ desktop_config.pixel_bounds =
+ JoinRects(desktop_config.pixel_bounds, 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;
+}
+
+// Finds the display configuration with the specified id.
+const MacDisplayConfiguration*
+MacDesktopConfiguration::FindDisplayConfigurationById(
+ CGDirectDisplayID id) {
+ for (MacDisplayConfigurations::const_iterator it = displays.begin();
+ it != displays.end(); ++it) {
+ if (it->id == id)
+ return &(*it);
+ }
+ return NULL;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc
new file mode 100644
index 0000000000..eeccecb6cc
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc
@@ -0,0 +1,91 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h"
+
+#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
+#include "webrtc/system_wrappers/include/event_wrapper.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+// The amount of time allowed for displays to reconfigure.
+static const int64_t kDisplayConfigurationEventTimeoutMs = 10 * 1000;
+
+DesktopConfigurationMonitor::DesktopConfigurationMonitor()
+ : ref_count_(0),
+ display_configuration_capture_event_(EventWrapper::Create()) {
+ CGError err = CGDisplayRegisterReconfigurationCallback(
+ DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err;
+ abort();
+ }
+ display_configuration_capture_event_->Set();
+
+ desktop_configuration_ = MacDesktopConfiguration::GetCurrent(
+ MacDesktopConfiguration::TopLeftOrigin);
+}
+
+DesktopConfigurationMonitor::~DesktopConfigurationMonitor() {
+ CGError err = CGDisplayRemoveReconfigurationCallback(
+ DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this);
+ if (err != kCGErrorSuccess)
+ LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err;
+}
+
+void DesktopConfigurationMonitor::Lock() {
+ if (!display_configuration_capture_event_->Wait(
+ kDisplayConfigurationEventTimeoutMs)) {
+ LOG_F(LS_ERROR) << "Event wait timed out.";
+ abort();
+ }
+}
+
+void DesktopConfigurationMonitor::Unlock() {
+ display_configuration_capture_event_->Set();
+}
+
+// static
+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) {
+ 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()) {
+ desktop_configuration_ = MacDesktopConfiguration::GetCurrent(
+ MacDesktopConfiguration::TopLeftOrigin);
+ display_configuration_capture_event_->Set();
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h
new file mode 100644
index 0000000000..b2fa81a416
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h
@@ -0,0 +1,66 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include <set>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
+#include "webrtc/system_wrappers/include/atomic32.h"
+
+namespace webrtc {
+
+class EventWrapper;
+
+// The class provides functions to synchronize capturing and display
+// reconfiguring across threads, and the up-to-date MacDesktopConfiguration.
+class DesktopConfigurationMonitor {
+ public:
+ DesktopConfigurationMonitor();
+ // Acquires a lock on the current configuration.
+ void Lock();
+ // Releases the lock previously acquired.
+ void Unlock();
+ // Returns the current desktop configuration. Should only be called when the
+ // lock has been acquired.
+ const MacDesktopConfiguration& desktop_configuration() {
+ return desktop_configuration_;
+ }
+
+ void AddRef() { ++ref_count_; }
+ void Release() {
+ if (--ref_count_ == 0)
+ delete this;
+ }
+
+ private:
+ static void DisplaysReconfiguredCallback(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags,
+ void *user_parameter);
+ ~DesktopConfigurationMonitor();
+
+ void DisplaysReconfigured(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags);
+
+ Atomic32 ref_count_;
+ std::set<CGDirectDisplayID> reconfiguring_displays_;
+ MacDesktopConfiguration desktop_configuration_;
+ rtc::scoped_ptr<EventWrapper> display_configuration_capture_event_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(DesktopConfigurationMonitor);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_
diff --git a/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc
new file mode 100644
index 0000000000..84579c4149
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc
@@ -0,0 +1,244 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h"
+
+#include <assert.h>
+#include <libproc.h>
+#include <string>
+
+#include "webrtc/base/macutils.h"
+#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
+#include "webrtc/modules/desktop_capture/mac/window_list_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+
+namespace webrtc {
+
+namespace {
+
+const int64_t kUpdateIntervalMs = 500;
+
+// Returns true if the window is minimized.
+bool IsWindowMinimized(CGWindowID id) {
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL);
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+ bool minimized = false;
+
+ if (window_array && CFArrayGetCount(window_array)) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFBooleanRef on_screen = reinterpret_cast<CFBooleanRef>(
+ CFDictionaryGetValue(window, kCGWindowIsOnscreen));
+
+ minimized = !on_screen;
+ }
+
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+
+ return minimized;
+}
+
+// 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;
+}
+
+std::string GetWindowTitle(CGWindowID id) {
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL);
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+ std::string title;
+
+ if (window_array && CFArrayGetCount(window_array)) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFStringRef title_ref = reinterpret_cast<CFStringRef>(
+ CFDictionaryGetValue(window, kCGWindowName));
+
+ if (title_ref)
+ rtc::ToUtf8(title_ref, &title);
+ }
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+
+ return title;
+}
+
+int GetWindowOwnerPid(CGWindowID id) {
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL);
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+ int pid = 0;
+
+ if (window_array && CFArrayGetCount(window_array)) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowOwnerPID));
+
+ if (pid_ref)
+ CFNumberGetValue(pid_ref, kCFNumberIntType, &pid);
+ }
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+
+ return pid;
+}
+
+// Returns the window that is full-screen and has the same title and owner pid
+// as the input window.
+CGWindowID FindFullScreenWindowWithSamePidAndTitle(CGWindowID id) {
+ int pid = GetWindowOwnerPid(id);
+ std::string title = GetWindowTitle(id);
+
+ // Only get on screen, non-desktop windows.
+ CFArrayRef window_array = CGWindowListCopyWindowInfo(
+ kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
+ kCGNullWindowID);
+ if (!window_array)
+ return kCGNullWindowID;
+
+ CGWindowID full_screen_window = kCGNullWindowID;
+
+ 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));
+ CFStringRef window_title_ref = reinterpret_cast<CFStringRef>(
+ CFDictionaryGetValue(window, kCGWindowName));
+ CFNumberRef window_id_ref = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowNumber));
+ CFNumberRef window_pid_ref = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowOwnerPID));
+
+ if (!window_title_ref || !window_id_ref || !window_pid_ref)
+ continue;
+
+ int window_pid = 0;
+ CFNumberGetValue(window_pid_ref, kCFNumberIntType, &window_pid);
+ if (window_pid != pid)
+ continue;
+
+ std::string window_title;
+ if (!rtc::ToUtf8(window_title_ref, &window_title) ||
+ window_title != title) {
+ continue;
+ }
+
+ CGWindowID window_id;
+ CFNumberGetValue(window_id_ref, kCFNumberIntType, &window_id);
+ if (IsWindowFullScreen(desktop_config, window)) {
+ full_screen_window = window_id;
+ break;
+ }
+ }
+
+ CFRelease(window_array);
+ return full_screen_window;
+}
+
+bool IsChromeWindow(CGWindowID id) {
+ int pid = GetWindowOwnerPid(id);
+ char buffer[PROC_PIDPATHINFO_MAXSIZE];
+ int path_length = proc_pidpath(pid, buffer, sizeof(buffer));
+ if (path_length <= 0)
+ return false;
+
+ const char* last_slash = strrchr(buffer, '/');
+ std::string name(last_slash ? last_slash + 1 : buffer);
+ return name.find("Google Chrome") == 0 || name == "Chromium";
+}
+
+} // namespace
+
+FullScreenChromeWindowDetector::FullScreenChromeWindowDetector()
+ : ref_count_(0) {}
+
+FullScreenChromeWindowDetector::~FullScreenChromeWindowDetector() {}
+
+CGWindowID FullScreenChromeWindowDetector::FindFullScreenWindow(
+ CGWindowID original_window) {
+ if (!IsChromeWindow(original_window) || !IsWindowMinimized(original_window))
+ return kCGNullWindowID;
+
+ CGWindowID full_screen_window_id =
+ FindFullScreenWindowWithSamePidAndTitle(original_window);
+
+ if (full_screen_window_id == kCGNullWindowID)
+ return kCGNullWindowID;
+
+ for (WindowCapturer::WindowList::iterator it = previous_window_list_.begin();
+ it != previous_window_list_.end(); ++it) {
+ if (static_cast<CGWindowID>(it->id) != full_screen_window_id)
+ continue;
+
+ int64_t time_interval =
+ (TickTime::Now() - last_udpate_time_).Milliseconds();
+ LOG(LS_WARNING) << "The full-screen window exists in the list, "
+ << "which was updated " << time_interval << "ms ago.";
+ return kCGNullWindowID;
+ }
+
+ return full_screen_window_id;
+}
+
+void FullScreenChromeWindowDetector::UpdateWindowListIfNeeded(
+ CGWindowID original_window) {
+ if (IsChromeWindow(original_window) &&
+ (TickTime::Now() - last_udpate_time_).Milliseconds()
+ > kUpdateIntervalMs) {
+ previous_window_list_.clear();
+ previous_window_list_.swap(current_window_list_);
+
+ // No need to update the window list when the window is minimized.
+ if (IsWindowMinimized(original_window)) {
+ previous_window_list_.clear();
+ return;
+ }
+
+ GetWindowList(&current_window_list_);
+ last_udpate_time_ = TickTime::Now();
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h
new file mode 100644
index 0000000000..4e6008966e
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h
@@ -0,0 +1,69 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_CHROME_WINDOW_DETECTOR_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_CHROME_WINDOW_DETECTOR_H_
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+#include "webrtc/system_wrappers/include/atomic32.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+
+// This is a work around for the Chrome tab full-screen behavior: Chrome
+// creates a new window in full-screen mode to show a tab full-screen and
+// minimizes the old window. To continue capturing in this case, we try to
+// find the new full-screen window using these criteria:
+// 0. The original shared window is minimized.
+// 1. The original shared window's owner application name is "Google Chrome".
+// 2. The original window and the new window have the same title and owner
+// pid.
+// 3. The new window is full-screen.
+// 4. The new window didn't exist at least 500 millisecond ago.
+
+class FullScreenChromeWindowDetector {
+ public:
+ FullScreenChromeWindowDetector();
+
+ void AddRef() { ++ref_count_; }
+ void Release() {
+ if (--ref_count_ == 0)
+ delete this;
+ }
+
+ // Returns the full-screen window in place of the original window if all the
+ // criteria are met, or kCGNullWindowID if no such window found.
+ CGWindowID FindFullScreenWindow(CGWindowID original_window);
+
+ // The caller should call this function periodically, no less than twice per
+ // second.
+ void UpdateWindowListIfNeeded(CGWindowID original_window);
+
+ private:
+ ~FullScreenChromeWindowDetector();
+
+ Atomic32 ref_count_;
+
+ // We cache the last two results of the window list, so
+ // |previous_window_list_| is taken at least 500ms before the next Capture()
+ // call. If we only save the last result, we may get false positive (i.e.
+ // full-screen window exists in the list) if Capture() is called too soon.
+ WindowCapturer::WindowList current_window_list_;
+ WindowCapturer::WindowList previous_window_list_;
+ TickTime last_udpate_time_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(FullScreenChromeWindowDetector);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_CHROME_WINDOW_DETECTOR_H_
diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc
new file mode 100644
index 0000000000..5dcbc52a9e
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc
@@ -0,0 +1,55 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h"
+
+#include <stddef.h>
+
+namespace webrtc {
+
+ScopedPixelBufferObject::ScopedPixelBufferObject()
+ : cgl_context_(NULL),
+ pixel_buffer_object_(0) {
+}
+
+ScopedPixelBufferObject::~ScopedPixelBufferObject() {
+ Release();
+}
+
+bool ScopedPixelBufferObject::Init(CGLContextObj cgl_context,
+ int size_in_bytes) {
+ cgl_context_ = cgl_context;
+ CGLContextObj CGL_MACRO_CONTEXT = cgl_context_;
+ glGenBuffersARB(1, &pixel_buffer_object_);
+ if (glGetError() == GL_NO_ERROR) {
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_);
+ glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size_in_bytes, NULL,
+ GL_STREAM_READ_ARB);
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
+ if (glGetError() != GL_NO_ERROR) {
+ Release();
+ }
+ } else {
+ cgl_context_ = NULL;
+ pixel_buffer_object_ = 0;
+ }
+ return pixel_buffer_object_ != 0;
+}
+
+void ScopedPixelBufferObject::Release() {
+ if (pixel_buffer_object_) {
+ CGLContextObj CGL_MACRO_CONTEXT = cgl_context_;
+ glDeleteBuffersARB(1, &pixel_buffer_object_);
+ cgl_context_ = NULL;
+ pixel_buffer_object_ = 0;
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h
new file mode 100644
index 0000000000..a32d470954
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h
@@ -0,0 +1,41 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_
+
+#include <OpenGL/CGLMacro.h>
+#include <OpenGL/OpenGL.h>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class ScopedPixelBufferObject {
+ public:
+ ScopedPixelBufferObject();
+ ~ScopedPixelBufferObject();
+
+ bool Init(CGLContextObj cgl_context, int size_in_bytes);
+ void Release();
+
+ GLuint get() const { return pixel_buffer_object_; }
+
+ private:
+ CGLContextObj cgl_context_;
+ GLuint pixel_buffer_object_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScopedPixelBufferObject);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_
diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.cc b/webrtc/modules/desktop_capture/mac/window_list_utils.cc
new file mode 100644
index 0000000000..0c3eaa3abd
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/window_list_utils.cc
@@ -0,0 +1,62 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mac/window_list_utils.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "webrtc/base/macutils.h"
+
+namespace webrtc {
+
+bool GetWindowList(WindowCapturer::WindowList* windows) {
+ // 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 (!rtc::ToUtf8(window_title, &(window.title)) ||
+ window.title.empty()) {
+ continue;
+ }
+ windows->push_back(window);
+ }
+ }
+
+ CFRelease(window_array);
+ return true;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.h b/webrtc/modules/desktop_capture/mac/window_list_utils.h
new file mode 100644
index 0000000000..7be38506bb
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mac/window_list_utils.h
@@ -0,0 +1,24 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_
+
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+
+namespace webrtc {
+
+// A helper function to get the on-screen windows.
+bool GetWindowList(WindowCapturer::WindowList* windows);
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_
+
diff --git a/webrtc/modules/desktop_capture/mouse_cursor.cc b/webrtc/modules/desktop_capture/mouse_cursor.cc
new file mode 100644
index 0000000000..22a9c0ee8c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor.cc
@@ -0,0 +1,38 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mouse_cursor.h"
+
+#include <assert.h>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace webrtc {
+
+MouseCursor::MouseCursor() {}
+
+MouseCursor::MouseCursor(DesktopFrame* image, const DesktopVector& hotspot)
+ : image_(image),
+ hotspot_(hotspot) {
+ assert(0 <= hotspot_.x() && hotspot_.x() <= image_->size().width());
+ assert(0 <= hotspot_.y() && hotspot_.y() <= image_->size().height());
+}
+
+MouseCursor::~MouseCursor() {}
+
+// static
+MouseCursor* MouseCursor::CopyOf(const MouseCursor& cursor) {
+ return cursor.image()
+ ? new MouseCursor(BasicDesktopFrame::CopyOf(*cursor.image()),
+ cursor.hotspot())
+ : new MouseCursor();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mouse_cursor.h b/webrtc/modules/desktop_capture/mouse_cursor.h
new file mode 100644
index 0000000000..dd5dc0eb44
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor.h
@@ -0,0 +1,48 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+
+namespace webrtc {
+
+class DesktopFrame;
+
+class MouseCursor {
+ public:
+ MouseCursor();
+
+ // Takes ownership of |image|. |hotspot| must be within |image| boundaries.
+ MouseCursor(DesktopFrame* image, const DesktopVector& hotspot);
+
+ ~MouseCursor();
+
+ static MouseCursor* CopyOf(const MouseCursor& cursor);
+
+ void set_image(DesktopFrame* image) { image_.reset(image); }
+ const DesktopFrame* image() const { return image_.get(); }
+
+ void set_hotspot(const DesktopVector& hotspot ) { hotspot_ = hotspot; }
+ const DesktopVector& hotspot() const { return hotspot_; }
+
+ private:
+ rtc::scoped_ptr<DesktopFrame> image_;
+ DesktopVector hotspot_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(MouseCursor);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor.h b/webrtc/modules/desktop_capture/mouse_cursor_monitor.h
new file mode 100644
index 0000000000..24dfe72dfa
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor.h
@@ -0,0 +1,90 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_
+
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class DesktopCaptureOptions;
+class DesktopFrame;
+class MouseCursor;
+
+// Captures mouse shape and position.
+class MouseCursorMonitor {
+ public:
+ enum CursorState {
+ // Cursor on top of the window including window decorations.
+ INSIDE,
+
+ // Cursor is outside of the window.
+ OUTSIDE,
+ };
+
+ enum Mode {
+ // Capture only shape of the mouse cursor, but not position.
+ SHAPE_ONLY,
+
+ // Capture both, mouse cursor shape and position.
+ SHAPE_AND_POSITION,
+ };
+
+ // Callback interface used to pass current mouse cursor position and shape.
+ class Callback {
+ public:
+ // Called in response to Capture() when the cursor shape has changed. Must
+ // take ownership of |cursor|.
+ virtual void OnMouseCursor(MouseCursor* cursor) = 0;
+
+ // Called in response to Capture(). |position| indicates cursor position
+ // relative to the |window| specified in the constructor.
+ virtual void OnMouseCursorPosition(CursorState state,
+ const DesktopVector& position) = 0;
+
+ protected:
+ virtual ~Callback() {}
+ };
+
+ virtual ~MouseCursorMonitor() {}
+
+ // Creates a capturer that notifies of mouse cursor events while the cursor is
+ // over the specified window.
+ static MouseCursorMonitor* CreateForWindow(
+ const DesktopCaptureOptions& options,
+ WindowId window);
+
+ // Creates a capturer that monitors the mouse cursor shape and position across
+ // the entire desktop.
+ //
+ // TODO(sergeyu): Provide a way to select a specific screen.
+ static MouseCursorMonitor* CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen);
+
+ // Initializes the monitor with the |callback|, which must remain valid until
+ // capturer is destroyed.
+ virtual void Init(Callback* callback, Mode mode) = 0;
+
+ // Captures current cursor shape and position (depending on the |mode| passed
+ // to Init()). Calls Callback::OnMouseCursor() if cursor shape has
+ // changed since the last call (or when Capture() is called for the first
+ // time) and then Callback::OnMouseCursorPosition() if mode is set to
+ // SHAPE_AND_POSITION.
+ virtual void Capture() = 0;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_
+
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm b/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm
new file mode 100644
index 0000000000..6033127ae1
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm
@@ -0,0 +1,296 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+
+#include <assert.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <Cocoa/Cocoa.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "webrtc/base/macutils.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.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/full_screen_chrome_window_detector.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+class MouseCursorMonitorMac : public MouseCursorMonitor {
+ public:
+ MouseCursorMonitorMac(const DesktopCaptureOptions& options,
+ CGWindowID window_id,
+ ScreenId screen_id);
+ virtual ~MouseCursorMonitorMac();
+
+ void Init(Callback* callback, Mode mode) override;
+ void Capture() override;
+
+ private:
+ static void DisplaysReconfiguredCallback(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags,
+ void *user_parameter);
+ void DisplaysReconfigured(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags);
+
+ void CaptureImage();
+
+ rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
+ CGWindowID window_id_;
+ ScreenId screen_id_;
+ Callback* callback_;
+ Mode mode_;
+ rtc::scoped_ptr<MouseCursor> last_cursor_;
+ rtc::scoped_refptr<FullScreenChromeWindowDetector>
+ full_screen_chrome_window_detector_;
+};
+
+MouseCursorMonitorMac::MouseCursorMonitorMac(
+ const DesktopCaptureOptions& options,
+ CGWindowID window_id,
+ ScreenId screen_id)
+ : configuration_monitor_(options.configuration_monitor()),
+ window_id_(window_id),
+ screen_id_(screen_id),
+ callback_(NULL),
+ mode_(SHAPE_AND_POSITION),
+ full_screen_chrome_window_detector_(
+ options.full_screen_chrome_window_detector()) {
+ assert(window_id == kCGNullWindowID || screen_id == kInvalidScreenId);
+ if (screen_id != kInvalidScreenId &&
+ rtc::GetOSVersionName() < rtc::kMacOSLion) {
+ // Single screen capture is not supported on pre OS X 10.7.
+ screen_id_ = kFullDesktopScreenId;
+ }
+}
+
+MouseCursorMonitorMac::~MouseCursorMonitorMac() {}
+
+void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+ mode_ = mode;
+}
+
+void MouseCursorMonitorMac::Capture() {
+ assert(callback_);
+
+ CaptureImage();
+
+ if (mode_ != SHAPE_AND_POSITION)
+ return;
+
+ CursorState state = INSIDE;
+
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint gc_position = CGEventGetLocation(event);
+ CFRelease(event);
+
+ DesktopVector position(gc_position.x, gc_position.y);
+
+ configuration_monitor_->Lock();
+ MacDesktopConfiguration configuration =
+ configuration_monitor_->desktop_configuration();
+ configuration_monitor_->Unlock();
+ float scale = 1.0f;
+
+ // Find the dpi to physical pixel scale for the screen where the mouse cursor
+ // is.
+ for (MacDisplayConfigurations::iterator it = configuration.displays.begin();
+ it != configuration.displays.end(); ++it) {
+ if (it->bounds.Contains(position)) {
+ scale = it->dip_to_pixel_scale;
+ break;
+ }
+ }
+ // If we are capturing cursor for a specific window then we need to figure out
+ // if the current mouse position is covered by another window and also adjust
+ // |position| to make it relative to the window origin.
+ if (window_id_ != kCGNullWindowID) {
+ CGWindowID on_screen_window = window_id_;
+ if (full_screen_chrome_window_detector_) {
+ CGWindowID full_screen_window =
+ full_screen_chrome_window_detector_->FindFullScreenWindow(window_id_);
+
+ if (full_screen_window != kCGNullWindowID)
+ on_screen_window = full_screen_window;
+ }
+
+ // Get list of windows that may be covering parts of |on_screen_window|.
+ // CGWindowListCopyWindowInfo() returns windows in order from front to back,
+ // so |on_screen_window| is expected to be the last in the list.
+ CFArrayRef window_array =
+ CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly |
+ kCGWindowListOptionOnScreenAboveWindow |
+ kCGWindowListOptionIncludingWindow,
+ on_screen_window);
+ bool found_window = false;
+ if (window_array) {
+ CFIndex count = CFArrayGetCount(window_array);
+ for (CFIndex i = 0; i < count; ++i) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, i));
+
+ // Skip the Dock window. Dock window covers the whole screen, but it is
+ // transparent.
+ CFStringRef window_name = reinterpret_cast<CFStringRef>(
+ CFDictionaryGetValue(window, kCGWindowName));
+ if (window_name && CFStringCompare(window_name, CFSTR("Dock"), 0) == 0)
+ continue;
+
+ CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>(
+ CFDictionaryGetValue(window, kCGWindowBounds));
+ CFNumberRef window_number = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowNumber));
+
+ if (window_bounds && window_number) {
+ CGRect gc_window_rect;
+ if (!CGRectMakeWithDictionaryRepresentation(window_bounds,
+ &gc_window_rect)) {
+ continue;
+ }
+ DesktopRect window_rect =
+ DesktopRect::MakeXYWH(gc_window_rect.origin.x,
+ gc_window_rect.origin.y,
+ gc_window_rect.size.width,
+ gc_window_rect.size.height);
+
+ CGWindowID window_id;
+ if (!CFNumberGetValue(window_number, kCFNumberIntType, &window_id))
+ continue;
+
+ if (window_id == on_screen_window) {
+ found_window = true;
+ if (!window_rect.Contains(position))
+ state = OUTSIDE;
+ position = position.subtract(window_rect.top_left());
+
+ assert(i == count - 1);
+ break;
+ } else if (window_rect.Contains(position)) {
+ state = OUTSIDE;
+ position.set(-1, -1);
+ break;
+ }
+ }
+ }
+ CFRelease(window_array);
+ }
+ if (!found_window) {
+ // If we failed to get list of windows or the window wasn't in the list
+ // pretend that the cursor is outside the window. This can happen, e.g. if
+ // the window was closed.
+ state = OUTSIDE;
+ position.set(-1, -1);
+ }
+ } else {
+ assert(screen_id_ >= kFullDesktopScreenId);
+ if (screen_id_ != kFullDesktopScreenId) {
+ // For single screen capturing, convert the position to relative to the
+ // target screen.
+ const MacDisplayConfiguration* config =
+ configuration.FindDisplayConfigurationById(
+ static_cast<CGDirectDisplayID>(screen_id_));
+ if (config) {
+ if (!config->pixel_bounds.Contains(position))
+ state = OUTSIDE;
+ position = position.subtract(config->bounds.top_left());
+ } else {
+ // The target screen is no longer valid.
+ state = OUTSIDE;
+ position.set(-1, -1);
+ }
+ } else {
+ position.subtract(configuration.bounds.top_left());
+ }
+ }
+ if (state == INSIDE) {
+ // Convert Density Independent Pixel to physical pixel.
+ position = DesktopVector(round(position.x() * scale),
+ round(position.y() * scale));
+ }
+ callback_->OnMouseCursorPosition(state, position);
+}
+
+void MouseCursorMonitorMac::CaptureImage() {
+ NSCursor* nscursor = [NSCursor currentSystemCursor];
+
+ NSImage* nsimage = [nscursor image];
+ NSSize nssize = [nsimage size];
+ DesktopSize size(nssize.width, nssize.height);
+ NSPoint nshotspot = [nscursor hotSpot];
+ DesktopVector hotspot(
+ std::max(0, std::min(size.width(), static_cast<int>(nshotspot.x))),
+ std::max(0, std::min(size.height(), static_cast<int>(nshotspot.y))));
+ CGImageRef cg_image =
+ [nsimage CGImageForProposedRect:NULL context:nil hints:nil];
+ if (!cg_image)
+ return;
+
+ if (CGImageGetBitsPerPixel(cg_image) != DesktopFrame::kBytesPerPixel * 8 ||
+ CGImageGetBytesPerRow(cg_image) !=
+ static_cast<size_t>(DesktopFrame::kBytesPerPixel * size.width()) ||
+ CGImageGetBitsPerComponent(cg_image) != 8) {
+ return;
+ }
+
+ CGDataProviderRef provider = CGImageGetDataProvider(cg_image);
+ CFDataRef image_data_ref = CGDataProviderCopyData(provider);
+ if (image_data_ref == NULL)
+ return;
+
+ const uint8_t* src_data =
+ reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref));
+
+ // Compare the cursor with the previous one.
+ if (last_cursor_.get() &&
+ last_cursor_->image()->size().equals(size) &&
+ last_cursor_->hotspot().equals(hotspot) &&
+ memcmp(last_cursor_->image()->data(), src_data,
+ last_cursor_->image()->stride() * size.height()) == 0) {
+ CFRelease(image_data_ref);
+ return;
+ }
+
+ // Create a MouseCursor that describes the cursor and pass it to
+ // the client.
+ rtc::scoped_ptr<DesktopFrame> image(
+ new BasicDesktopFrame(DesktopSize(size.width(), size.height())));
+ memcpy(image->data(), src_data,
+ size.width() * size.height() * DesktopFrame::kBytesPerPixel);
+
+ CFRelease(image_data_ref);
+
+ rtc::scoped_ptr<MouseCursor> cursor(
+ new MouseCursor(image.release(), hotspot));
+ last_cursor_.reset(MouseCursor::CopyOf(*cursor));
+
+ callback_->OnMouseCursor(cursor.release());
+}
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
+ const DesktopCaptureOptions& options, WindowId window) {
+ return new MouseCursorMonitorMac(options, window, kInvalidScreenId);
+}
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen) {
+ return new MouseCursorMonitorMac(options, kCGNullWindowID, screen);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc
new file mode 100644
index 0000000000..3a632cc0d9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc
@@ -0,0 +1,29 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+
+#include <stddef.h>
+
+namespace webrtc {
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
+ const DesktopCaptureOptions& options,
+ WindowId window) {
+ return NULL;
+}
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen) {
+ return NULL;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc
new file mode 100644
index 0000000000..5d5a81caba
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+class MouseCursorMonitorTest : public testing::Test,
+ public MouseCursorMonitor::Callback {
+ public:
+ MouseCursorMonitorTest()
+ : position_received_(false) {
+ }
+
+ // MouseCursorMonitor::Callback interface
+ void OnMouseCursor(MouseCursor* cursor_image) override {
+ cursor_image_.reset(cursor_image);
+ }
+
+ void OnMouseCursorPosition(MouseCursorMonitor::CursorState state,
+ const DesktopVector& position) override {
+ state_ = state;
+ position_ = position;
+ position_received_ = true;
+ }
+
+ protected:
+ rtc::scoped_ptr<MouseCursor> cursor_image_;
+ MouseCursorMonitor::CursorState state_;
+ DesktopVector position_;
+ bool position_received_;
+};
+
+// TODO(sergeyu): On Mac we need to initialize NSApplication before running the
+// tests. Figure out how to do that without breaking other tests in
+// modules_unittests and enable these tests on Mac.
+// https://code.google.com/p/webrtc/issues/detail?id=2532
+//
+// Disabled on Windows due to flake, see:
+// https://code.google.com/p/webrtc/issues/detail?id=3408
+// Disabled on Linux due to flake, see:
+// https://code.google.com/p/webrtc/issues/detail?id=3245
+#if !defined(WEBRTC_MAC) && !defined(WEBRTC_WIN) && !defined(WEBRTC_LINUX)
+#define MAYBE(x) x
+#else
+#define MAYBE(x) DISABLED_##x
+#endif
+
+TEST_F(MouseCursorMonitorTest, MAYBE(FromScreen)) {
+ rtc::scoped_ptr<MouseCursorMonitor> capturer(
+ MouseCursorMonitor::CreateForScreen(
+ DesktopCaptureOptions::CreateDefault(),
+ webrtc::kFullDesktopScreenId));
+ assert(capturer.get());
+ capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
+ capturer->Capture();
+
+ EXPECT_TRUE(cursor_image_.get());
+ EXPECT_GE(cursor_image_->hotspot().x(), 0);
+ EXPECT_LE(cursor_image_->hotspot().x(),
+ cursor_image_->image()->size().width());
+ EXPECT_GE(cursor_image_->hotspot().y(), 0);
+ EXPECT_LE(cursor_image_->hotspot().y(),
+ cursor_image_->image()->size().height());
+
+ EXPECT_TRUE(position_received_);
+ EXPECT_EQ(MouseCursorMonitor::INSIDE, state_);
+}
+
+TEST_F(MouseCursorMonitorTest, MAYBE(FromWindow)) {
+ DesktopCaptureOptions options = DesktopCaptureOptions::CreateDefault();
+
+ // First get list of windows.
+ rtc::scoped_ptr<WindowCapturer> window_capturer(
+ WindowCapturer::Create(options));
+
+ // If window capturing is not supported then skip this test.
+ if (!window_capturer.get())
+ return;
+
+ WindowCapturer::WindowList windows;
+ EXPECT_TRUE(window_capturer->GetWindowList(&windows));
+
+ // Iterate over all windows and try capturing mouse cursor for each of them.
+ for (size_t i = 0; i < windows.size(); ++i) {
+ cursor_image_.reset();
+ position_received_ = false;
+
+ rtc::scoped_ptr<MouseCursorMonitor> capturer(
+ MouseCursorMonitor::CreateForWindow(
+ DesktopCaptureOptions::CreateDefault(), windows[i].id));
+ assert(capturer.get());
+
+ capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
+ capturer->Capture();
+
+ EXPECT_TRUE(cursor_image_.get());
+ EXPECT_TRUE(position_received_);
+ }
+}
+
+// Make sure that OnMouseCursorPosition() is not called in the SHAPE_ONLY mode.
+TEST_F(MouseCursorMonitorTest, MAYBE(ShapeOnly)) {
+ rtc::scoped_ptr<MouseCursorMonitor> capturer(
+ MouseCursorMonitor::CreateForScreen(
+ DesktopCaptureOptions::CreateDefault(),
+ webrtc::kFullDesktopScreenId));
+ assert(capturer.get());
+ capturer->Init(this, MouseCursorMonitor::SHAPE_ONLY);
+ capturer->Capture();
+
+ EXPECT_TRUE(cursor_image_.get());
+ EXPECT_FALSE(position_received_);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc
new file mode 100644
index 0000000000..54dfba2032
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc
@@ -0,0 +1,173 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+
+#include <assert.h>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/win/cursor.h"
+#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+class MouseCursorMonitorWin : public MouseCursorMonitor {
+ public:
+ explicit MouseCursorMonitorWin(HWND window);
+ explicit MouseCursorMonitorWin(ScreenId screen);
+ virtual ~MouseCursorMonitorWin();
+
+ void Init(Callback* callback, Mode mode) override;
+ void Capture() override;
+
+ private:
+ // Get the rect of the currently selected screen, relative to the primary
+ // display's top-left. If the screen is disabled or disconnected, or any error
+ // happens, an empty rect is returned.
+ DesktopRect GetScreenRect();
+
+ HWND window_;
+ ScreenId screen_;
+
+ Callback* callback_;
+ Mode mode_;
+
+ HDC desktop_dc_;
+
+ HCURSOR last_cursor_;
+};
+
+MouseCursorMonitorWin::MouseCursorMonitorWin(HWND window)
+ : window_(window),
+ screen_(kInvalidScreenId),
+ callback_(NULL),
+ mode_(SHAPE_AND_POSITION),
+ desktop_dc_(NULL),
+ last_cursor_(NULL) {
+}
+
+MouseCursorMonitorWin::MouseCursorMonitorWin(ScreenId screen)
+ : window_(NULL),
+ screen_(screen),
+ callback_(NULL),
+ mode_(SHAPE_AND_POSITION),
+ desktop_dc_(NULL),
+ last_cursor_(NULL) {
+ assert(screen >= kFullDesktopScreenId);
+}
+
+MouseCursorMonitorWin::~MouseCursorMonitorWin() {
+ if (desktop_dc_)
+ ReleaseDC(NULL, desktop_dc_);
+}
+
+void MouseCursorMonitorWin::Init(Callback* callback, Mode mode) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+ mode_ = mode;
+
+ desktop_dc_ = GetDC(NULL);
+}
+
+void MouseCursorMonitorWin::Capture() {
+ assert(callback_);
+
+ CURSORINFO cursor_info;
+ cursor_info.cbSize = sizeof(CURSORINFO);
+ if (!GetCursorInfo(&cursor_info)) {
+ LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError();
+ return;
+ }
+
+ if (last_cursor_ != cursor_info.hCursor) {
+ last_cursor_ = cursor_info.hCursor;
+ // Note that |cursor_info.hCursor| does not need to be freed.
+ rtc::scoped_ptr<MouseCursor> cursor(
+ CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor));
+ if (cursor.get())
+ callback_->OnMouseCursor(cursor.release());
+ }
+
+ if (mode_ != SHAPE_AND_POSITION)
+ return;
+
+ DesktopVector position(cursor_info.ptScreenPos.x, cursor_info.ptScreenPos.y);
+ bool inside = cursor_info.flags == CURSOR_SHOWING;
+
+ if (window_) {
+ DesktopRect original_rect;
+ DesktopRect cropped_rect;
+ if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) {
+ position.set(0, 0);
+ inside = false;
+ } else {
+ if (inside) {
+ HWND windowUnderCursor = WindowFromPoint(cursor_info.ptScreenPos);
+ inside = windowUnderCursor ?
+ (window_ == GetAncestor(windowUnderCursor, GA_ROOT)) : false;
+ }
+ position = position.subtract(cropped_rect.top_left());
+ }
+ } else {
+ assert(screen_ != kInvalidScreenId);
+ DesktopRect rect = GetScreenRect();
+ if (inside)
+ inside = rect.Contains(position);
+ position = position.subtract(rect.top_left());
+ }
+
+ callback_->OnMouseCursorPosition(inside ? INSIDE : OUTSIDE, position);
+}
+
+DesktopRect MouseCursorMonitorWin::GetScreenRect() {
+ assert(screen_ != kInvalidScreenId);
+ if (screen_ == kFullDesktopScreenId) {
+ return DesktopRect::MakeXYWH(
+ GetSystemMetrics(SM_XVIRTUALSCREEN),
+ GetSystemMetrics(SM_YVIRTUALSCREEN),
+ GetSystemMetrics(SM_CXVIRTUALSCREEN),
+ GetSystemMetrics(SM_CYVIRTUALSCREEN));
+ }
+ DISPLAY_DEVICE device;
+ device.cb = sizeof(device);
+ BOOL result = EnumDisplayDevices(NULL, screen_, &device, 0);
+ if (!result)
+ return DesktopRect();
+
+ DEVMODE device_mode;
+ device_mode.dmSize = sizeof(device_mode);
+ device_mode.dmDriverExtra = 0;
+ result = EnumDisplaySettingsEx(
+ device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0);
+ if (!result)
+ return DesktopRect();
+
+ return DesktopRect::MakeXYWH(device_mode.dmPosition.x,
+ device_mode.dmPosition.y,
+ device_mode.dmPelsWidth,
+ device_mode.dmPelsHeight);
+}
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
+ const DesktopCaptureOptions& options, WindowId window) {
+ return new MouseCursorMonitorWin(reinterpret_cast<HWND>(window));
+}
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen) {
+ return new MouseCursorMonitorWin(screen);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc
new file mode 100644
index 0000000000..fc6d76eb7a
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc
@@ -0,0 +1,232 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+
+#include <X11/extensions/Xfixes.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace {
+
+// WindowCapturer returns window IDs of X11 windows with WM_STATE attribute.
+// These windows may not be immediate children of the root window, because
+// window managers may re-parent them to add decorations. However,
+// XQueryPointer() expects to be passed children of the root. This function
+// searches up the list of the windows to find the root child that corresponds
+// to |window|.
+Window GetTopLevelWindow(Display* display, Window window) {
+ while (true) {
+ // If the window is in WithdrawnState then look at all of its children.
+ ::Window root, parent;
+ ::Window *children;
+ unsigned int num_children;
+ if (!XQueryTree(display, window, &root, &parent, &children,
+ &num_children)) {
+ LOG(LS_ERROR) << "Failed to query for child windows although window"
+ << "does not have a valid WM_STATE.";
+ return None;
+ }
+ if (children)
+ XFree(children);
+
+ if (parent == root)
+ break;
+
+ window = parent;
+ }
+
+ return window;
+}
+
+} // namespace
+
+namespace webrtc {
+
+class MouseCursorMonitorX11 : public MouseCursorMonitor,
+ public SharedXDisplay::XEventHandler {
+ public:
+ MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window);
+ virtual ~MouseCursorMonitorX11();
+
+ void Init(Callback* callback, Mode mode) override;
+ void Capture() override;
+
+ private:
+ // SharedXDisplay::XEventHandler interface.
+ bool HandleXEvent(const XEvent& event) override;
+
+ Display* display() { return x_display_->display(); }
+
+ // Captures current cursor shape and stores it in |cursor_shape_|.
+ void CaptureCursor();
+
+ rtc::scoped_refptr<SharedXDisplay> x_display_;
+ Callback* callback_;
+ Mode mode_;
+ Window window_;
+
+ bool have_xfixes_;
+ int xfixes_event_base_;
+ int xfixes_error_base_;
+
+ rtc::scoped_ptr<MouseCursor> cursor_shape_;
+};
+
+MouseCursorMonitorX11::MouseCursorMonitorX11(
+ const DesktopCaptureOptions& options,
+ Window window)
+ : x_display_(options.x_display()),
+ callback_(NULL),
+ mode_(SHAPE_AND_POSITION),
+ window_(window),
+ have_xfixes_(false),
+ xfixes_event_base_(-1),
+ xfixes_error_base_(-1) {}
+
+MouseCursorMonitorX11::~MouseCursorMonitorX11() {
+ if (have_xfixes_) {
+ x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
+ this);
+ }
+}
+
+void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) {
+ // Init can be called only once per instance of MouseCursorMonitor.
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+ mode_ = mode;
+
+ have_xfixes_ =
+ XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_);
+
+ if (have_xfixes_) {
+ // Register for changes to the cursor shape.
+ XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask);
+ x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this);
+
+ CaptureCursor();
+ } else {
+ LOG(LS_INFO) << "X server does not support XFixes.";
+ }
+}
+
+void MouseCursorMonitorX11::Capture() {
+ assert(callback_);
+
+ // Process X11 events in case XFixes has sent cursor notification.
+ x_display_->ProcessPendingXEvents();
+
+ // cursor_shape_| is set only if we were notified of a cursor shape change.
+ if (cursor_shape_.get())
+ callback_->OnMouseCursor(cursor_shape_.release());
+
+ // Get cursor position if necessary.
+ if (mode_ == SHAPE_AND_POSITION) {
+ int root_x;
+ int root_y;
+ int win_x;
+ int win_y;
+ Window root_window;
+ Window child_window;
+ unsigned int mask;
+
+ XErrorTrap error_trap(display());
+ Bool result = XQueryPointer(display(), window_, &root_window, &child_window,
+ &root_x, &root_y, &win_x, &win_y, &mask);
+ CursorState state;
+ if (!result || error_trap.GetLastErrorAndDisable() != 0) {
+ state = OUTSIDE;
+ } else {
+ // In screen mode (window_ == root_window) the mouse is always inside.
+ // XQueryPointer() sets |child_window| to None if the cursor is outside
+ // |window_|.
+ state =
+ (window_ == root_window || child_window != None) ? INSIDE : OUTSIDE;
+ }
+
+ callback_->OnMouseCursorPosition(state,
+ webrtc::DesktopVector(win_x, win_y));
+ }
+}
+
+bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) {
+ if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) {
+ const XFixesCursorNotifyEvent* cursor_event =
+ reinterpret_cast<const XFixesCursorNotifyEvent*>(&event);
+ if (cursor_event->subtype == XFixesDisplayCursorNotify) {
+ CaptureCursor();
+ }
+ // Return false, even if the event has been handled, because there might be
+ // other listeners for cursor notifications.
+ }
+ return false;
+}
+
+void MouseCursorMonitorX11::CaptureCursor() {
+ assert(have_xfixes_);
+
+ XFixesCursorImage* img;
+ {
+ XErrorTrap error_trap(display());
+ img = XFixesGetCursorImage(display());
+ if (!img || error_trap.GetLastErrorAndDisable() != 0)
+ return;
+ }
+
+ rtc::scoped_ptr<DesktopFrame> image(
+ new BasicDesktopFrame(DesktopSize(img->width, img->height)));
+
+ // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
+ unsigned long* src = img->pixels;
+ uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
+ uint32_t* dst_end = dst + (img->width * img->height);
+ while (dst < dst_end) {
+ *dst++ = static_cast<uint32_t>(*src++);
+ }
+
+ DesktopVector hotspot(std::min(img->width, img->xhot),
+ std::min(img->height, img->yhot));
+
+ XFree(img);
+
+ cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
+}
+
+// static
+MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
+ const DesktopCaptureOptions& options, WindowId window) {
+ if (!options.x_display())
+ return NULL;
+ window = GetTopLevelWindow(options.x_display()->display(), window);
+ if (window == None)
+ return NULL;
+ return new MouseCursorMonitorX11(options, window);
+}
+
+MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
+ const DesktopCaptureOptions& options,
+ ScreenId screen) {
+ if (!options.x_display())
+ return NULL;
+ return new MouseCursorMonitorX11(
+ options, DefaultRootWindow(options.x_display()->display()));
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/mouse_cursor_shape.h b/webrtc/modules/desktop_capture/mouse_cursor_shape.h
new file mode 100644
index 0000000000..57120a0b3f
--- /dev/null
+++ b/webrtc/modules/desktop_capture/mouse_cursor_shape.h
@@ -0,0 +1,17 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_
+
+// This file is no longer needed, but some code in chromium still includes it.
+// TODO(sergeyu): Cleanup dependencies in chromium and remove this file.
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_
diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc
new file mode 100644
index 0000000000..94d8a27b13
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc
@@ -0,0 +1,44 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
+
+#include <assert.h>
+#include <algorithm>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+ScreenCaptureFrameQueue::ScreenCaptureFrameQueue() : current_(0) {}
+
+ScreenCaptureFrameQueue::~ScreenCaptureFrameQueue() {}
+
+void ScreenCaptureFrameQueue::MoveToNextFrame() {
+ current_ = (current_ + 1) % kQueueLength;
+
+ // Verify that the frame is not shared, i.e. that consumer has released it
+ // before attempting to capture again.
+ assert(!frames_[current_].get() || !frames_[current_]->IsShared());
+}
+
+void ScreenCaptureFrameQueue::ReplaceCurrentFrame(DesktopFrame* frame) {
+ frames_[current_].reset(SharedDesktopFrame::Wrap(frame));
+}
+
+void ScreenCaptureFrameQueue::Reset() {
+ for (int i = 0; i < kQueueLength; ++i)
+ frames_[i].reset();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.h b/webrtc/modules/desktop_capture/screen_capture_frame_queue.h
new file mode 100644
index 0000000000..6cd9e3bfc8
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.h
@@ -0,0 +1,74 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+class DesktopFrame;
+} // namespace webrtc
+
+namespace webrtc {
+
+// Represents a queue of reusable video frames. Provides access to the 'current'
+// frame - the frame that the caller is working with at the moment, and to the
+// 'previous' frame - the predecessor of the current frame swapped by
+// MoveToNextFrame() call, if any.
+//
+// The caller is expected to (re)allocate frames if current_frame() returns
+// NULL. The caller can mark all frames in the queue for reallocation (when,
+// say, frame dimensions change). The queue records which frames need updating
+// which the caller can query.
+//
+// Frame consumer is expected to never hold more than kQueueLength frames
+// created by this function and it should release the earliest one before trying
+// to capture a new frame (i.e. before MoveToNextFrame() is called).
+class ScreenCaptureFrameQueue {
+ public:
+ ScreenCaptureFrameQueue();
+ ~ScreenCaptureFrameQueue();
+
+ // Moves to the next frame in the queue, moving the 'current' frame to become
+ // the 'previous' one.
+ void MoveToNextFrame();
+
+ // Replaces the current frame with a new one allocated by the caller. The
+ // existing frame (if any) is destroyed. Takes ownership of |frame|.
+ void ReplaceCurrentFrame(DesktopFrame* frame);
+
+ // Marks all frames obsolete and resets the previous frame pointer. No
+ // frames are freed though as the caller can still access them.
+ void Reset();
+
+ SharedDesktopFrame* current_frame() const {
+ return frames_[current_].get();
+ }
+
+ SharedDesktopFrame* previous_frame() const {
+ return frames_[(current_ + kQueueLength - 1) % kQueueLength].get();
+ }
+
+ private:
+ // Index of the current frame.
+ int current_;
+
+ static const int kQueueLength = 2;
+ rtc::scoped_ptr<SharedDesktopFrame> frames_[kQueueLength];
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameQueue);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_
diff --git a/webrtc/modules/desktop_capture/screen_capturer.cc b/webrtc/modules/desktop_capture/screen_capturer.cc
new file mode 100644
index 0000000000..97f69d3baf
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer.cc
@@ -0,0 +1,36 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+
+namespace webrtc {
+
+ScreenCapturer* ScreenCapturer::Create() {
+ return Create(DesktopCaptureOptions::CreateDefault());
+}
+
+#if defined(WEBRTC_LINUX)
+ScreenCapturer* ScreenCapturer::CreateWithXDamage(
+ bool use_update_notifications) {
+ DesktopCaptureOptions options;
+ options.set_use_update_notifications(use_update_notifications);
+ return Create(options);
+}
+#elif defined(WEBRTC_WIN)
+ScreenCapturer* ScreenCapturer::CreateWithDisableAero(bool disable_effects) {
+ DesktopCaptureOptions options;
+ options.set_disable_effects(disable_effects);
+ return Create(options);
+}
+#endif
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer.h b/webrtc/modules/desktop_capture/screen_capturer.h
new file mode 100644
index 0000000000..fbfb0e5898
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer.h
@@ -0,0 +1,93 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_
+
+#include <vector>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class DesktopCaptureOptions;
+
+// Class used to capture video frames asynchronously.
+//
+// The full capture sequence is as follows:
+//
+// (1) Start
+// This is when pre-capture steps are executed, such as flagging the
+// display to prevent it from sleeping during a session.
+//
+// (2) CaptureFrame
+// This is where the bits for the invalid rects are packaged up and sent
+// to the encoder.
+// A screen capture is performed if needed. For example, Windows requires
+// a capture to calculate the diff from the previous screen, whereas the
+// Mac version does not.
+//
+// Implementation has to ensure the following guarantees:
+// 1. Double buffering
+// Since data can be read while another capture action is happening.
+class ScreenCapturer : public DesktopCapturer {
+ public:
+ // Use a struct to represent a screen although it has only an id for now,
+ // because we may want to add more fields (e.g. description) in the future.
+ struct Screen {
+ ScreenId id;
+ };
+ typedef std::vector<Screen> ScreenList;
+
+ // TODO(sergeyu): Remove this class once all dependencies are removed from
+ // chromium.
+ class MouseShapeObserver {
+ };
+
+ virtual ~ScreenCapturer() {}
+
+ // Creates platform-specific capturer.
+ //
+ // TODO(sergeyu): Remove all Create() methods except the first one.
+ // crbug.com/172183
+ static ScreenCapturer* Create(const DesktopCaptureOptions& options);
+ static ScreenCapturer* Create();
+
+#if defined(WEBRTC_LINUX)
+ // Creates platform-specific capturer and instructs it whether it should use
+ // X DAMAGE support.
+ static ScreenCapturer* CreateWithXDamage(bool use_x_damage);
+#elif defined(WEBRTC_WIN)
+ // Creates Windows-specific capturer and instructs it whether or not to
+ // disable desktop compositing.
+ static ScreenCapturer* CreateWithDisableAero(bool disable_aero);
+#endif // defined(WEBRTC_WIN)
+
+ // TODO(sergeyu): Remove this method once all dependencies are removed from
+ // chromium.
+ virtual void SetMouseShapeObserver(
+ MouseShapeObserver* mouse_shape_observer) {};
+
+ // Get the list of screens (not containing kFullDesktopScreenId). Returns
+ // false in case of a failure.
+ virtual bool GetScreenList(ScreenList* screens) = 0;
+
+ // Select the screen to be captured. Returns false in case of a failure (e.g.
+ // if there is no screen with the specified id). If this is never called, the
+ // full desktop is captured.
+ virtual bool SelectScreen(ScreenId id) = 0;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_
diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper.cc b/webrtc/modules/desktop_capture/screen_capturer_helper.cc
new file mode 100644
index 0000000000..fa7096d24d
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_helper.cc
@@ -0,0 +1,104 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer_helper.h"
+
+#include <assert.h>
+#include <algorithm>
+
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+ScreenCapturerHelper::ScreenCapturerHelper()
+ : invalid_region_lock_(RWLockWrapper::CreateRWLock()),
+ log_grid_size_(0) {
+}
+
+ScreenCapturerHelper::~ScreenCapturerHelper() {
+}
+
+void ScreenCapturerHelper::ClearInvalidRegion() {
+ WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_);
+ invalid_region_.Clear();
+}
+
+void ScreenCapturerHelper::InvalidateRegion(
+ const DesktopRegion& invalid_region) {
+ WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_);
+ invalid_region_.AddRegion(invalid_region);
+}
+
+void ScreenCapturerHelper::InvalidateScreen(const DesktopSize& size) {
+ WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_);
+ invalid_region_.AddRect(DesktopRect::MakeSize(size));
+}
+
+void ScreenCapturerHelper::TakeInvalidRegion(
+ DesktopRegion* invalid_region) {
+ invalid_region->Clear();
+
+ {
+ WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_);
+ invalid_region->Swap(&invalid_region_);
+ }
+
+ if (log_grid_size_ > 0) {
+ DesktopRegion expanded_region;
+ ExpandToGrid(*invalid_region, log_grid_size_, &expanded_region);
+ expanded_region.Swap(invalid_region);
+
+ invalid_region->IntersectWith(DesktopRect::MakeSize(size_most_recent_));
+ }
+}
+
+void ScreenCapturerHelper::SetLogGridSize(int log_grid_size) {
+ log_grid_size_ = log_grid_size;
+}
+
+const DesktopSize& ScreenCapturerHelper::size_most_recent() const {
+ return size_most_recent_;
+}
+
+void ScreenCapturerHelper::set_size_most_recent(
+ const DesktopSize& size) {
+ size_most_recent_ = size;
+}
+
+// Returns the largest multiple of |n| that is <= |x|.
+// |n| must be a power of 2. |nMask| is ~(|n| - 1).
+static int DownToMultiple(int x, int nMask) {
+ return (x & nMask);
+}
+
+// Returns the smallest multiple of |n| that is >= |x|.
+// |n| must be a power of 2. |nMask| is ~(|n| - 1).
+static int UpToMultiple(int x, int n, int nMask) {
+ return ((x + n - 1) & nMask);
+}
+
+void ScreenCapturerHelper::ExpandToGrid(const DesktopRegion& region,
+ int log_grid_size,
+ DesktopRegion* result) {
+ assert(log_grid_size >= 1);
+ int grid_size = 1 << log_grid_size;
+ int grid_size_mask = ~(grid_size - 1);
+
+ result->Clear();
+ for (DesktopRegion::Iterator it(region); !it.IsAtEnd(); it.Advance()) {
+ int left = DownToMultiple(it.rect().left(), grid_size_mask);
+ int right = UpToMultiple(it.rect().right(), grid_size, grid_size_mask);
+ int top = DownToMultiple(it.rect().top(), grid_size_mask);
+ int bottom = UpToMultiple(it.rect().bottom(), grid_size, grid_size_mask);
+ result->AddRect(DesktopRect::MakeLTRB(left, top, right, bottom));
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper.h b/webrtc/modules/desktop_capture/screen_capturer_helper.h
new file mode 100644
index 0000000000..da1f2bfeeb
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_helper.h
@@ -0,0 +1,88 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/system_wrappers/include/rw_lock_wrapper.h"
+
+namespace webrtc {
+
+// ScreenCapturerHelper is intended to be used by an implementation of the
+// ScreenCapturer interface. It maintains a thread-safe invalid region, and
+// the size of the most recently captured screen, on behalf of the
+// ScreenCapturer that owns it.
+class ScreenCapturerHelper {
+ public:
+ ScreenCapturerHelper();
+ ~ScreenCapturerHelper();
+
+ // Clear out the invalid region.
+ void ClearInvalidRegion();
+
+ // Invalidate the specified region.
+ void InvalidateRegion(const DesktopRegion& invalid_region);
+
+ // Invalidate the entire screen, of a given size.
+ void InvalidateScreen(const DesktopSize& size);
+
+ // Copies current invalid region to |invalid_region| clears invalid region
+ // storage for the next frame.
+ void TakeInvalidRegion(DesktopRegion* invalid_region);
+
+ // Access the size of the most recently captured screen.
+ const DesktopSize& size_most_recent() const;
+ void set_size_most_recent(const DesktopSize& size);
+
+ // Lossy compression can result in color values leaking between pixels in one
+ // block. If part of a block changes, then unchanged parts of that block can
+ // be changed in the compressed output. So we need to re-render an entire
+ // block whenever part of the block changes.
+ //
+ // If |log_grid_size| is >= 1, then this function makes TakeInvalidRegion()
+ // produce an invalid region expanded so that its vertices lie on a grid of
+ // size 2 ^ |log_grid_size|. The expanded region is then clipped to the size
+ // of the most recently captured screen, as previously set by
+ // set_size_most_recent().
+ // If |log_grid_size| is <= 0, then the invalid region is not expanded.
+ void SetLogGridSize(int log_grid_size);
+
+ // Expands a region so that its vertices all lie on a grid.
+ // The grid size must be >= 2, so |log_grid_size| must be >= 1.
+ static void ExpandToGrid(const DesktopRegion& region,
+ int log_grid_size,
+ DesktopRegion* result);
+
+ private:
+ // A region that has been manually invalidated (through InvalidateRegion).
+ // These will be returned as dirty_region in the capture data during the next
+ // capture.
+ DesktopRegion invalid_region_;
+
+ // A lock protecting |invalid_region_| across threads.
+ rtc::scoped_ptr<RWLockWrapper> invalid_region_lock_;
+
+ // The size of the most recently captured screen.
+ DesktopSize size_most_recent_;
+
+ // The log (base 2) of the size of the grid to which the invalid region is
+ // expanded.
+ // If the value is <= 0, then the invalid region is not expanded to a grid.
+ int log_grid_size_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerHelper);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_
diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc
new file mode 100644
index 0000000000..1ebc0afd18
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc
@@ -0,0 +1,188 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer_helper.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace webrtc {
+
+class ScreenCapturerHelperTest : public testing::Test {
+ protected:
+ ScreenCapturerHelper capturer_helper_;
+};
+
+TEST_F(ScreenCapturerHelperTest, ClearInvalidRegion) {
+ DesktopRegion region(DesktopRect::MakeXYWH(1, 2, 3, 4));
+ capturer_helper_.InvalidateRegion(region);
+ capturer_helper_.ClearInvalidRegion();
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(region.is_empty());
+}
+
+TEST_F(ScreenCapturerHelperTest, InvalidateRegion) {
+ DesktopRegion region;
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(region.is_empty());
+
+ region.SetRect(DesktopRect::MakeXYWH(1, 2, 3, 4));
+ capturer_helper_.InvalidateRegion(region);
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(1, 2, 3, 4)).Equals(region));
+
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(1, 2, 3, 4)));
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(4, 2, 3, 4)));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(1, 2, 6, 4)).Equals(region));
+}
+
+TEST_F(ScreenCapturerHelperTest, InvalidateScreen) {
+ DesktopRegion region;
+ capturer_helper_.InvalidateScreen(DesktopSize(12, 34));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeWH(12, 34)).Equals(region));
+}
+
+TEST_F(ScreenCapturerHelperTest, SizeMostRecent) {
+ EXPECT_TRUE(capturer_helper_.size_most_recent().is_empty());
+ capturer_helper_.set_size_most_recent(DesktopSize(12, 34));
+ EXPECT_TRUE(
+ DesktopSize(12, 34).equals(capturer_helper_.size_most_recent()));
+}
+
+TEST_F(ScreenCapturerHelperTest, SetLogGridSize) {
+ capturer_helper_.set_size_most_recent(DesktopSize(10, 10));
+
+ DesktopRegion region;
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion().Equals(region));
+
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region));
+
+ capturer_helper_.SetLogGridSize(-1);
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region));
+
+ capturer_helper_.SetLogGridSize(0);
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region));
+
+ capturer_helper_.SetLogGridSize(1);
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)));
+ capturer_helper_.TakeInvalidRegion(&region);
+
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(6, 6, 2, 2)).Equals(region));
+
+ capturer_helper_.SetLogGridSize(2);
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(4, 4, 4, 4)).Equals(region));
+
+ capturer_helper_.SetLogGridSize(0);
+ capturer_helper_.InvalidateRegion(
+ DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)));
+ capturer_helper_.TakeInvalidRegion(&region);
+ EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region));
+}
+
+void TestExpandRegionToGrid(const DesktopRegion& region, int log_grid_size,
+ const DesktopRegion& expanded_region_expected) {
+ DesktopRegion expanded_region1;
+ ScreenCapturerHelper::ExpandToGrid(region, log_grid_size, &expanded_region1);
+ EXPECT_TRUE(expanded_region_expected.Equals(expanded_region1));
+
+ DesktopRegion expanded_region2;
+ ScreenCapturerHelper::ExpandToGrid(expanded_region1, log_grid_size,
+ &expanded_region2);
+ EXPECT_TRUE(expanded_region1.Equals(expanded_region2));
+}
+
+void TestExpandRectToGrid(int l, int t, int r, int b, int log_grid_size,
+ int lExpanded, int tExpanded,
+ int rExpanded, int bExpanded) {
+ TestExpandRegionToGrid(DesktopRegion(DesktopRect::MakeLTRB(l, t, r, b)),
+ log_grid_size,
+ DesktopRegion(DesktopRect::MakeLTRB(
+ lExpanded, tExpanded, rExpanded, bExpanded)));
+}
+
+TEST_F(ScreenCapturerHelperTest, ExpandToGrid) {
+ const int kLogGridSize = 4;
+ const int kGridSize = 1 << kLogGridSize;
+ for (int i = -2; i <= 2; i++) {
+ int x = i * kGridSize;
+ for (int j = -2; j <= 2; j++) {
+ int y = j * kGridSize;
+ TestExpandRectToGrid(x + 0, y + 0, x + 1, y + 1, kLogGridSize,
+ x + 0, y + 0, x + kGridSize, y + kGridSize);
+ TestExpandRectToGrid(x + 0, y + kGridSize - 1, x + 1, y + kGridSize,
+ kLogGridSize,
+ x + 0, y + 0, x + kGridSize, y + kGridSize);
+ TestExpandRectToGrid(x + kGridSize - 1, y + kGridSize - 1,
+ x + kGridSize, y + kGridSize, kLogGridSize,
+ x + 0, y + 0, x + kGridSize, y + kGridSize);
+ TestExpandRectToGrid(x + kGridSize - 1, y + 0,
+ x + kGridSize, y + 1, kLogGridSize,
+ x + 0, y + 0, x + kGridSize, y + kGridSize);
+ TestExpandRectToGrid(x - 1, y + 0, x + 1, y + 1, kLogGridSize,
+ x - kGridSize, y + 0, x + kGridSize, y + kGridSize);
+ TestExpandRectToGrid(x - 1, y - 1, x + 1, y + 0, kLogGridSize,
+ x - kGridSize, y - kGridSize, x + kGridSize, y);
+ TestExpandRectToGrid(x + 0, y - 1, x + 1, y + 1, kLogGridSize,
+ x, y - kGridSize, x + kGridSize, y + kGridSize);
+ TestExpandRectToGrid(x - 1, y - 1, x + 0, y + 1, kLogGridSize,
+ x - kGridSize, y - kGridSize, x, y + kGridSize);
+
+ // Construct a region consisting of 3 pixels and verify that it's expanded
+ // properly to 3 squares that are kGridSize by kGridSize.
+ for (int q = 0; q < 4; ++q) {
+ DesktopRegion region;
+ DesktopRegion expanded_region_expected;
+
+ if (q != 0) {
+ region.AddRect(DesktopRect::MakeXYWH(x - 1, y - 1, 1, 1));
+ expanded_region_expected.AddRect(DesktopRect::MakeXYWH(
+ x - kGridSize, y - kGridSize, kGridSize, kGridSize));
+ }
+ if (q != 1) {
+ region.AddRect(DesktopRect::MakeXYWH(x, y - 1, 1, 1));
+ expanded_region_expected.AddRect(DesktopRect::MakeXYWH(
+ x, y - kGridSize, kGridSize, kGridSize));
+ }
+ if (q != 2) {
+ region.AddRect(DesktopRect::MakeXYWH(x - 1, y, 1, 1));
+ expanded_region_expected.AddRect(DesktopRect::MakeXYWH(
+ x - kGridSize, y, kGridSize, kGridSize));
+ }
+ if (q != 3) {
+ region.AddRect(DesktopRect::MakeXYWH(x, y, 1, 1));
+ expanded_region_expected.AddRect(DesktopRect::MakeXYWH(
+ x, y, kGridSize, kGridSize));
+ }
+
+ TestExpandRegionToGrid(region, kLogGridSize, expanded_region_expected);
+ }
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac.mm b/webrtc/modules/desktop_capture/screen_capturer_mac.mm
new file mode 100644
index 0000000000..a0f0e5464e
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_mac.mm
@@ -0,0 +1,982 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include <stddef.h>
+#include <set>
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Cocoa/Cocoa.h>
+#include <dlfcn.h>
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <OpenGL/CGLMacro.h>
+#include <OpenGL/OpenGL.h>
+
+#include "webrtc/base/macutils.h"
+#include "webrtc/base/scoped_ptr.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/scoped_pixel_buffer_object.h"
+#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
+#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+
+namespace {
+
+// Definitions used to dynamic-link to deprecated OS 10.6 functions.
+const char* kApplicationServicesLibraryName =
+ "/System/Library/Frameworks/ApplicationServices.framework/"
+ "ApplicationServices";
+typedef void* (*CGDisplayBaseAddressFunc)(CGDirectDisplayID);
+typedef size_t (*CGDisplayBytesPerRowFunc)(CGDirectDisplayID);
+typedef size_t (*CGDisplayBitsPerPixelFunc)(CGDirectDisplayID);
+const char* kOpenGlLibraryName =
+ "/System/Library/Frameworks/OpenGL.framework/OpenGL";
+typedef CGLError (*CGLSetFullScreenFunc)(CGLContextObj);
+
+// Standard Mac displays have 72dpi, but we report 96dpi for
+// consistency with Windows and Linux.
+const int kStandardDPI = 96;
+
+// 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 NULL;
+
+ CFArrayRef all_windows = CGWindowListCopyWindowInfo(
+ kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
+ if (!all_windows)
+ return NULL;
+
+ CFMutableArrayRef returned_array = CFArrayCreateMutable(
+ NULL, CFArrayGetCount(all_windows), NULL);
+
+ bool found = false;
+ for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(all_windows, i));
+
+ CFNumberRef id_ref = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowNumber));
+
+ CGWindowID id;
+ CFNumberGetValue(id_ref, kCFNumberIntType, &id);
+ 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 = NULL;
+ }
+ 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(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+
+ if (CFArrayGetCount(window_array) > 0) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>(
+ CFDictionaryGetValue(window, 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. The caller should release the returned CGImageRef and CFDataRef.
+CGImageRef CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds,
+ float dip_to_pixel_scale,
+ CFArrayRef window_list,
+ CFDataRef* data_ref) {
+ 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();
+
+ CGImageRef excluded_image = CGWindowListCreateImageFromArray(
+ window_bounds, window_list, kCGWindowImageDefault);
+
+ CGDataProviderRef provider = CGImageGetDataProvider(excluded_image);
+ *data_ref = CGDataProviderCopyData(provider);
+ assert(*data_ref);
+ return excluded_image;
+}
+
+// A class to perform video frame capturing for mac.
+class ScreenCapturerMac : public ScreenCapturer {
+ public:
+ explicit ScreenCapturerMac(
+ rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor);
+ virtual ~ScreenCapturerMac();
+
+ bool Init();
+
+ // Overridden from ScreenCapturer:
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+ void SetExcludedWindow(WindowId window) override;
+ bool GetScreenList(ScreenList* screens) override;
+ bool SelectScreen(ScreenId id) override;
+
+ private:
+ void GlBlitFast(const DesktopFrame& frame,
+ const DesktopRegion& region);
+ void GlBlitSlow(const DesktopFrame& frame);
+ void CgBlitPreLion(const DesktopFrame& frame,
+ const DesktopRegion& region);
+ // 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 ScreenConfigurationChanged();
+
+ bool RegisterRefreshAndMoveHandlers();
+ void UnregisterRefreshAndMoveHandlers();
+
+ void ScreenRefresh(CGRectCount count, const CGRect *rect_array);
+ void ScreenUpdateMove(CGScreenUpdateMoveDelta delta,
+ size_t count,
+ const CGRect *rect_array);
+ static void ScreenRefreshCallback(CGRectCount count,
+ const CGRect *rect_array,
+ void *user_parameter);
+ static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta,
+ size_t count,
+ const CGRect *rect_array,
+ void *user_parameter);
+ void ReleaseBuffers();
+
+ DesktopFrame* CreateFrame();
+
+ Callback* callback_;
+
+ CGLContextObj cgl_context_;
+ ScopedPixelBufferObject pixel_buffer_object_;
+
+ // Queue of the frames buffers.
+ ScreenCaptureFrameQueue 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_;
+
+ // 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_;
+
+ // Monitoring display reconfiguration.
+ rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor_;
+
+ // Power management assertion to prevent the screen from sleeping.
+ IOPMAssertionID power_assertion_id_display_;
+
+ // Power management assertion to indicate that the user is active.
+ IOPMAssertionID power_assertion_id_user_;
+
+ // Dynamically link to deprecated APIs for Mac OS X 10.6 support.
+ void* app_services_library_;
+ CGDisplayBaseAddressFunc cg_display_base_address_;
+ CGDisplayBytesPerRowFunc cg_display_bytes_per_row_;
+ CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_;
+ void* opengl_library_;
+ CGLSetFullScreenFunc cgl_set_full_screen_;
+
+ CGWindowID excluded_window_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac);
+};
+
+// DesktopFrame wrapper that flips wrapped frame upside down by inverting
+// stride.
+class InvertedDesktopFrame : public DesktopFrame {
+ public:
+ // Takes ownership of |frame|.
+ InvertedDesktopFrame(DesktopFrame* frame)
+ : DesktopFrame(
+ frame->size(), -frame->stride(),
+ frame->data() + (frame->size().height() - 1) * frame->stride(),
+ frame->shared_memory()),
+ original_frame_(frame) {
+ set_dpi(frame->dpi());
+ set_capture_time_ms(frame->capture_time_ms());
+ mutable_updated_region()->Swap(frame->mutable_updated_region());
+ }
+ virtual ~InvertedDesktopFrame() {}
+
+ private:
+ rtc::scoped_ptr<DesktopFrame> original_frame_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(InvertedDesktopFrame);
+};
+
+ScreenCapturerMac::ScreenCapturerMac(
+ rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor)
+ : callback_(NULL),
+ cgl_context_(NULL),
+ 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_base_address_(NULL),
+ cg_display_bytes_per_row_(NULL),
+ cg_display_bits_per_pixel_(NULL),
+ opengl_library_(NULL),
+ cgl_set_full_screen_(NULL),
+ excluded_window_(0) {
+}
+
+ScreenCapturerMac::~ScreenCapturerMac() {
+ if (power_assertion_id_display_ != kIOPMNullAssertionID) {
+ IOPMAssertionRelease(power_assertion_id_display_);
+ power_assertion_id_display_ = kIOPMNullAssertionID;
+ }
+ if (power_assertion_id_user_ != kIOPMNullAssertionID) {
+ IOPMAssertionRelease(power_assertion_id_user_);
+ power_assertion_id_user_ = kIOPMNullAssertionID;
+ }
+
+ ReleaseBuffers();
+ UnregisterRefreshAndMoveHandlers();
+ dlclose(app_services_library_);
+ dlclose(opengl_library_);
+}
+
+bool ScreenCapturerMac::Init() {
+ if (!RegisterRefreshAndMoveHandlers()) {
+ return false;
+ }
+ desktop_config_monitor_->Lock();
+ desktop_config_ = desktop_config_monitor_->desktop_configuration();
+ desktop_config_monitor_->Unlock();
+ ScreenConfigurationChanged();
+ return true;
+}
+
+void ScreenCapturerMac::ReleaseBuffers() {
+ if (cgl_context_) {
+ pixel_buffer_object_.Release();
+ CGLDestroyContext(cgl_context_);
+ cgl_context_ = NULL;
+ }
+ // 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) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+
+ // Create power management assertions to wake the display and prevent it from
+ // going to sleep on user idle.
+ // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above
+ // instead of the following two assertions.
+ IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ CFSTR("Chrome Remote Desktop connection active"),
+ &power_assertion_id_display_);
+ // This assertion ensures that the display is woken up if it already asleep
+ // (as used by Apple Remote Desktop).
+ IOPMAssertionCreateWithName(CFSTR("UserIsActive"),
+ kIOPMAssertionLevelOn,
+ CFSTR("Chrome Remote Desktop connection active"),
+ &power_assertion_id_user_);
+}
+
+void ScreenCapturerMac::Capture(const DesktopRegion& region_to_capture) {
+ TickTime capture_start_time = TickTime::Now();
+
+ queue_.MoveToNextFrame();
+
+ 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;
+ helper_.TakeInvalidRegion(&region);
+
+ // If the current buffer is from an older generation then allocate a new one.
+ // Note that we can't reallocate other buffers at this point, since the caller
+ // may still be reading from them.
+ if (!queue_.current_frame())
+ queue_.ReplaceCurrentFrame(CreateFrame());
+
+ DesktopFrame* current_frame = queue_.current_frame();
+
+ bool flip = false; // GL capturers need flipping.
+ if (rtc::GetOSVersionName() >= rtc::kMacOSLion) {
+ // 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.
+ if (!CgBlitPostLion(*current_frame, region)) {
+ desktop_config_monitor_->Unlock();
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+ } else if (cgl_context_) {
+ flip = true;
+ if (pixel_buffer_object_.get() != 0) {
+ GlBlitFast(*current_frame, region);
+ } else {
+ // See comment in ScopedPixelBufferObject::Init about why the slow
+ // path is always used on 10.5.
+ GlBlitSlow(*current_frame);
+ }
+ } else {
+ CgBlitPreLion(*current_frame, region);
+ }
+
+ DesktopFrame* new_frame = queue_.current_frame()->Share();
+ *new_frame->mutable_updated_region() = region;
+
+ if (flip)
+ new_frame = new InvertedDesktopFrame(new_frame);
+
+ helper_.set_size_most_recent(new_frame->size());
+
+ // Signal that we are done capturing data from the display framebuffer,
+ // and accessing display structures.
+ desktop_config_monitor_->Unlock();
+
+ new_frame->set_capture_time_ms(
+ (TickTime::Now() - capture_start_time).Milliseconds());
+ callback_->OnCaptureCompleted(new_frame);
+}
+
+void ScreenCapturerMac::SetExcludedWindow(WindowId window) {
+ excluded_window_ = window;
+}
+
+bool ScreenCapturerMac::GetScreenList(ScreenList* screens) {
+ assert(screens->size() == 0);
+ if (rtc::GetOSVersionName() < rtc::kMacOSLion) {
+ // 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) {
+ if (rtc::GetOSVersionName() < rtc::kMacOSLion) {
+ // 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::GlBlitFast(const DesktopFrame& frame,
+ const DesktopRegion& region) {
+ // Clip to the size of our current screen.
+ DesktopRect clip_rect = DesktopRect::MakeSize(frame.size());
+ if (queue_.previous_frame()) {
+ // We are doing double buffer for the capture data so we just need to copy
+ // the invalid region from the previous capture in the current buffer.
+ // TODO(hclam): We can reduce the amount of copying here by subtracting
+ // |capturer_helper_|s region from |last_invalid_region_|.
+ // http://crbug.com/92354
+
+ // Since the image obtained from OpenGL is upside-down, need to do some
+ // magic here to copy the correct rectangle.
+ const int y_offset = (frame.size().height() - 1) * frame.stride();
+ for (DesktopRegion::Iterator i(last_invalid_region_);
+ !i.IsAtEnd(); i.Advance()) {
+ DesktopRect copy_rect = i.rect();
+ copy_rect.IntersectWith(clip_rect);
+ if (!copy_rect.is_empty()) {
+ CopyRect(queue_.previous_frame()->data() + y_offset,
+ -frame.stride(),
+ frame.data() + y_offset,
+ -frame.stride(),
+ DesktopFrame::kBytesPerPixel,
+ copy_rect);
+ }
+ }
+ }
+ last_invalid_region_ = region;
+
+ CGLContextObj CGL_MACRO_CONTEXT = cgl_context_;
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_.get());
+ glReadPixels(0, 0, frame.size().width(), frame.size().height(), GL_BGRA,
+ GL_UNSIGNED_BYTE, 0);
+ GLubyte* ptr = static_cast<GLubyte*>(
+ glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB));
+ if (ptr == NULL) {
+ // If the buffer can't be mapped, assume that it's no longer valid and
+ // release it.
+ pixel_buffer_object_.Release();
+ } else {
+ // Copy only from the dirty rects. Since the image obtained from OpenGL is
+ // upside-down we need to do some magic here to copy the correct rectangle.
+ const int y_offset = (frame.size().height() - 1) * frame.stride();
+ for (DesktopRegion::Iterator i(region);
+ !i.IsAtEnd(); i.Advance()) {
+ DesktopRect copy_rect = i.rect();
+ copy_rect.IntersectWith(clip_rect);
+ if (!copy_rect.is_empty()) {
+ CopyRect(ptr + y_offset,
+ -frame.stride(),
+ frame.data() + y_offset,
+ -frame.stride(),
+ DesktopFrame::kBytesPerPixel,
+ copy_rect);
+ }
+ }
+ }
+ if (!glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB)) {
+ // If glUnmapBuffer returns false, then the contents of the data store are
+ // undefined. This might be because the screen mode has changed, in which
+ // case it will be recreated in ScreenConfigurationChanged, but releasing
+ // the object here is the best option. Capturing will fall back on
+ // GlBlitSlow until such time as the pixel buffer object is recreated.
+ pixel_buffer_object_.Release();
+ }
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
+}
+
+void ScreenCapturerMac::GlBlitSlow(const DesktopFrame& frame) {
+ CGLContextObj CGL_MACRO_CONTEXT = cgl_context_;
+ glReadBuffer(GL_FRONT);
+ glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
+ glPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment.
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_PACK_SKIP_ROWS, 0);
+ glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
+ // Read a block of pixels from the frame buffer.
+ glReadPixels(0, 0, frame.size().width(), frame.size().height(),
+ GL_BGRA, GL_UNSIGNED_BYTE, frame.data());
+ glPopClientAttrib();
+}
+
+void ScreenCapturerMac::CgBlitPreLion(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
+ // crbug.com/92354.
+ if (queue_.previous_frame()) {
+ memcpy(frame.data(),
+ queue_.previous_frame()->data(),
+ frame.stride() * frame.size().height());
+ }
+
+ for (size_t i = 0; i < desktop_config_.displays.size(); ++i) {
+ const MacDisplayConfiguration& display_config = desktop_config_.displays[i];
+
+ // Use deprecated APIs to determine the display buffer layout.
+ assert(cg_display_base_address_ && cg_display_bytes_per_row_ &&
+ cg_display_bits_per_pixel_);
+ uint8_t* display_base_address = reinterpret_cast<uint8_t*>(
+ (*cg_display_base_address_)(display_config.id));
+ assert(display_base_address);
+ int src_bytes_per_row = (*cg_display_bytes_per_row_)(display_config.id);
+ int src_bytes_per_pixel =
+ (*cg_display_bits_per_pixel_)(display_config.id) / 8;
+
+ // 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());
+
+ // 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());
+
+ // Calculate where in the output buffer the display's origin is.
+ uint8_t* out_ptr = frame.data() +
+ (display_bounds.left() * src_bytes_per_pixel) +
+ (display_bounds.top() * frame.stride());
+
+ // Copy the dirty region from the display buffer into our desktop buffer.
+ for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) {
+ CopyRect(display_base_address,
+ src_bytes_per_row,
+ out_ptr,
+ frame.stride(),
+ src_bytes_per_pixel,
+ i.rect());
+ }
+ }
+}
+
+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
+ // crbug.com/92354.
+ if (queue_.previous_frame()) {
+ 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 {
+ 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;
+ CGImageRef excluded_image = NULL;
+ CFDataRef excluded_window_region_data = NULL;
+ 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,
+ &excluded_window_region_data);
+ }
+ }
+
+ // Create an image containing a snapshot of the display.
+ CGImageRef image = CGDisplayCreateImage(display_config.id);
+ if (image == NULL)
+ continue;
+
+ // Request access to the raw pixel data via the image's DataProvider.
+ CGDataProviderRef provider = CGImageGetDataProvider(image);
+ CFDataRef data = CGDataProviderCopyData(provider);
+ assert(data);
+
+ const uint8_t* display_base_address = CFDataGetBytePtr(data);
+ int src_bytes_per_row = CGImageGetBytesPerRow(image);
+ int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8;
+
+ // Calculate where in the output buffer the display's origin is.
+ uint8_t* out_ptr = frame.data() +
+ (display_bounds.left() * src_bytes_per_pixel) +
+ (display_bounds.top() * frame.stride());
+
+ // Copy the dirty region from the display buffer into our desktop buffer.
+ for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) {
+ CopyRect(display_base_address,
+ src_bytes_per_row,
+ out_ptr,
+ frame.stride(),
+ src_bytes_per_pixel,
+ i.rect());
+ }
+
+ // Copy the region of the excluded window to the frame.
+ if (excluded_image) {
+ assert(excluded_window_region_data);
+ display_base_address = CFDataGetBytePtr(excluded_window_region_data);
+ src_bytes_per_row = CGImageGetBytesPerRow(excluded_image);
+
+ // 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());
+ out_ptr = frame.data() +
+ (window_bounds_relative_to_desktop.left() * src_bytes_per_pixel) +
+ (window_bounds_relative_to_desktop.top() * frame.stride());
+
+ CopyRect(display_base_address,
+ src_bytes_per_row,
+ out_ptr,
+ frame.stride(),
+ src_bytes_per_pixel,
+ DesktopRect::MakeSize(excluded_window_bounds.size()));
+ CFRelease(excluded_window_region_data);
+ CFRelease(excluded_image);
+ }
+
+ CFRelease(data);
+ CFRelease(image);
+ }
+ 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();
+
+ // CgBlitPostLion uses CGDisplayCreateImage() to snapshot each display's
+ // contents. Although the API exists in OS 10.6, it crashes the caller if
+ // the machine has no monitor connected, so we fall back to depcreated APIs
+ // when running on 10.6.
+ if (rtc::GetOSVersionName() >= rtc::kMacOSLion) {
+ LOG(LS_INFO) << "Using CgBlitPostLion.";
+ // No need for any OpenGL support on Lion
+ return;
+ }
+
+ // Dynamically link to the deprecated pre-Lion capture APIs.
+ app_services_library_ = dlopen(kApplicationServicesLibraryName,
+ RTLD_LAZY);
+ if (!app_services_library_) {
+ LOG_F(LS_ERROR) << "Failed to open " << kApplicationServicesLibraryName;
+ abort();
+ }
+
+ opengl_library_ = dlopen(kOpenGlLibraryName, RTLD_LAZY);
+ if (!opengl_library_) {
+ LOG_F(LS_ERROR) << "Failed to open " << kOpenGlLibraryName;
+ abort();
+ }
+
+ cg_display_base_address_ = reinterpret_cast<CGDisplayBaseAddressFunc>(
+ dlsym(app_services_library_, "CGDisplayBaseAddress"));
+ cg_display_bytes_per_row_ = reinterpret_cast<CGDisplayBytesPerRowFunc>(
+ dlsym(app_services_library_, "CGDisplayBytesPerRow"));
+ cg_display_bits_per_pixel_ = reinterpret_cast<CGDisplayBitsPerPixelFunc>(
+ dlsym(app_services_library_, "CGDisplayBitsPerPixel"));
+ cgl_set_full_screen_ = reinterpret_cast<CGLSetFullScreenFunc>(
+ dlsym(opengl_library_, "CGLSetFullScreen"));
+ if (!(cg_display_base_address_ && cg_display_bytes_per_row_ &&
+ cg_display_bits_per_pixel_ && cgl_set_full_screen_)) {
+ LOG_F(LS_ERROR);
+ abort();
+ }
+
+ if (desktop_config_.displays.size() > 1) {
+ LOG(LS_INFO) << "Using CgBlitPreLion (Multi-monitor).";
+ return;
+ }
+
+ CGDirectDisplayID mainDevice = CGMainDisplayID();
+ if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) {
+ LOG(LS_INFO) << "Using CgBlitPreLion (OpenGL unavailable).";
+ return;
+ }
+
+ LOG(LS_INFO) << "Using GlBlit";
+
+ CGLPixelFormatAttribute attributes[] = {
+ // This function does an early return if GetOSVersionName() >= kMacOSLion,
+ // this code only runs on 10.6 and can be deleted once 10.6 support is
+ // dropped. So just keep using kCGLPFAFullScreen even though it was
+ // deprecated in 10.6 -- it's still functional there, and it's not used on
+ // newer OS X versions.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ kCGLPFAFullScreen,
+#pragma clang diagnostic pop
+ kCGLPFADisplayMask,
+ (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice),
+ (CGLPixelFormatAttribute)0
+ };
+ CGLPixelFormatObj pixel_format = NULL;
+ GLint matching_pixel_format_count = 0;
+ CGLError err = CGLChoosePixelFormat(attributes,
+ &pixel_format,
+ &matching_pixel_format_count);
+ assert(err == kCGLNoError);
+ err = CGLCreateContext(pixel_format, NULL, &cgl_context_);
+ assert(err == kCGLNoError);
+ CGLDestroyPixelFormat(pixel_format);
+ (*cgl_set_full_screen_)(cgl_context_);
+ CGLSetCurrentContext(cgl_context_);
+
+ size_t buffer_size = screen_pixel_bounds_.width() *
+ screen_pixel_bounds_.height() *
+ sizeof(uint32_t);
+ pixel_buffer_object_.Init(cgl_context_, buffer_size);
+}
+
+bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() {
+ CGError err = CGRegisterScreenRefreshCallback(
+ ScreenCapturerMac::ScreenRefreshCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(LS_ERROR) << "CGRegisterScreenRefreshCallback " << err;
+ return false;
+ }
+
+ err = CGScreenRegisterMoveCallback(
+ ScreenCapturerMac::ScreenUpdateMoveCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(LS_ERROR) << "CGScreenRegisterMoveCallback " << err;
+ return false;
+ }
+
+ return true;
+}
+
+void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() {
+ CGUnregisterScreenRefreshCallback(
+ ScreenCapturerMac::ScreenRefreshCallback, this);
+ CGScreenUnregisterMoveCallback(
+ ScreenCapturerMac::ScreenUpdateMoveCallback, this);
+}
+
+void ScreenCapturerMac::ScreenRefresh(CGRectCount count,
+ const CGRect* rect_array) {
+ 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], dip_to_pixel_scale_);
+ // Translate from local desktop to capturer framebuffer coordinates.
+ rect.Translate(translate_vector);
+ region.AddRect(rect);
+ }
+
+ helper_.InvalidateRegion(region);
+}
+
+void ScreenCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta,
+ size_t count,
+ const CGRect* rect_array) {
+ // Translate |rect_array| to identify the move's destination.
+ CGRect refresh_rects[count];
+ for (CGRectCount i = 0; i < count; ++i) {
+ refresh_rects[i] = CGRectOffset(rect_array[i], delta.dX, delta.dY);
+ }
+
+ // Currently we just treat move events the same as refreshes.
+ ScreenRefresh(count, refresh_rects);
+}
+
+void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count,
+ const CGRect* rect_array,
+ void* user_parameter) {
+ ScreenCapturerMac* capturer =
+ reinterpret_cast<ScreenCapturerMac*>(user_parameter);
+ if (capturer->screen_pixel_bounds_.is_empty())
+ capturer->ScreenConfigurationChanged();
+ capturer->ScreenRefresh(count, rect_array);
+}
+
+void ScreenCapturerMac::ScreenUpdateMoveCallback(
+ CGScreenUpdateMoveDelta delta,
+ size_t count,
+ const CGRect* rect_array,
+ void* user_parameter) {
+ ScreenCapturerMac* capturer =
+ reinterpret_cast<ScreenCapturerMac*>(user_parameter);
+ capturer->ScreenUpdateMove(delta, count, rect_array);
+}
+
+DesktopFrame* ScreenCapturerMac::CreateFrame() {
+ rtc::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) {
+ if (!options.configuration_monitor())
+ return NULL;
+
+ rtc::scoped_ptr<ScreenCapturerMac> capturer(
+ new ScreenCapturerMac(options.configuration_monitor()));
+ if (!capturer->Init())
+ capturer.reset();
+ return capturer.release();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc
new file mode 100644
index 0000000000..e3d037b1e9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc
@@ -0,0 +1,97 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include <ostream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/scoped_ptr.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/screen_capturer_mock_objects.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Return;
+
+namespace webrtc {
+
+class ScreenCapturerMacTest : public testing::Test {
+ public:
+ // Verifies that the whole screen is initially dirty.
+ void CaptureDoneCallback1(DesktopFrame* frame);
+
+ // Verifies that a rectangle explicitly marked as dirty is propagated
+ // correctly.
+ void CaptureDoneCallback2(DesktopFrame* frame);
+
+ protected:
+ void SetUp() override { capturer_.reset(ScreenCapturer::Create()); }
+
+ rtc::scoped_ptr<ScreenCapturer> capturer_;
+ MockScreenCapturerCallback callback_;
+};
+
+void ScreenCapturerMacTest::CaptureDoneCallback1(
+ DesktopFrame* frame) {
+ rtc::scoped_ptr<DesktopFrame> owned_frame(frame);
+
+ MacDesktopConfiguration config = MacDesktopConfiguration::GetCurrent(
+ MacDesktopConfiguration::BottomLeftOrigin);
+
+ // Verify that the region contains full frame.
+ DesktopRegion::Iterator it(frame->updated_region());
+ EXPECT_TRUE(!it.IsAtEnd() && it.rect().equals(config.pixel_bounds));
+}
+
+void ScreenCapturerMacTest::CaptureDoneCallback2(
+ DesktopFrame* frame) {
+ rtc::scoped_ptr<DesktopFrame> owned_frame(frame);
+
+ MacDesktopConfiguration config = MacDesktopConfiguration::GetCurrent(
+ MacDesktopConfiguration::BottomLeftOrigin);
+ int width = config.pixel_bounds.width();
+ int height = config.pixel_bounds.height();
+
+ EXPECT_EQ(width, frame->size().width());
+ EXPECT_EQ(height, frame->size().height());
+ EXPECT_TRUE(frame->data() != NULL);
+ // Depending on the capture method, the screen may be flipped or not, so
+ // the stride may be positive or negative.
+ EXPECT_EQ(static_cast<int>(sizeof(uint32_t) * width),
+ abs(frame->stride()));
+}
+
+TEST_F(ScreenCapturerMacTest, Capture) {
+ EXPECT_CALL(callback_, OnCaptureCompleted(_))
+ .Times(2)
+ .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback1))
+ .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback2));
+
+ EXPECT_CALL(callback_, CreateSharedMemory(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(static_cast<SharedMemory*>(NULL)));
+
+ SCOPED_TRACE("");
+ capturer_->Start(&callback_);
+
+ // Check that we get an initial full-screen updated.
+ capturer_->Capture(DesktopRegion());
+
+ // Check that subsequent dirty rects are propagated correctly.
+ capturer_->Capture(DesktopRegion());
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h b/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h
new file mode 100644
index 0000000000..8b83f41252
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h
@@ -0,0 +1,47 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/modules/desktop_capture/screen_capturer.h"
+
+namespace webrtc {
+
+class MockScreenCapturer : public ScreenCapturer {
+ public:
+ MockScreenCapturer() {}
+ virtual ~MockScreenCapturer() {}
+
+ MOCK_METHOD1(Start, void(Callback* callback));
+ MOCK_METHOD1(Capture, void(const DesktopRegion& region));
+ MOCK_METHOD1(GetScreenList, bool(ScreenList* screens));
+ MOCK_METHOD1(SelectScreen, bool(ScreenId id));
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(MockScreenCapturer);
+};
+
+class MockScreenCapturerCallback : public ScreenCapturer::Callback {
+ public:
+ MockScreenCapturerCallback() {}
+ virtual ~MockScreenCapturerCallback() {}
+
+ MOCK_METHOD1(CreateSharedMemory, SharedMemory*(size_t));
+ MOCK_METHOD1(OnCaptureCompleted, void(DesktopFrame*));
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(MockScreenCapturerCallback);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_
diff --git a/webrtc/modules/desktop_capture/screen_capturer_null.cc b/webrtc/modules/desktop_capture/screen_capturer_null.cc
new file mode 100644
index 0000000000..a0bc7f13ea
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_null.cc
@@ -0,0 +1,20 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+namespace webrtc {
+
+// static
+ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
+ return NULL;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc
new file mode 100644
index 0000000000..a3cf6d93cc
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc
@@ -0,0 +1,142 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/modules/desktop_capture/screen_capturer_mock_objects.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+const int kTestSharedMemoryId = 123;
+
+namespace webrtc {
+
+class ScreenCapturerTest : public testing::Test {
+ public:
+ SharedMemory* CreateSharedMemory(size_t size);
+
+ void SetUp() override {
+ capturer_.reset(
+ ScreenCapturer::Create(DesktopCaptureOptions::CreateDefault()));
+ }
+
+ protected:
+ rtc::scoped_ptr<ScreenCapturer> capturer_;
+ MockScreenCapturerCallback callback_;
+};
+
+class FakeSharedMemory : public SharedMemory {
+ public:
+ FakeSharedMemory(char* buffer, size_t size)
+ : SharedMemory(buffer, size, 0, kTestSharedMemoryId),
+ buffer_(buffer) {
+ }
+ virtual ~FakeSharedMemory() {
+ delete[] buffer_;
+ }
+ private:
+ char* buffer_;
+ RTC_DISALLOW_COPY_AND_ASSIGN(FakeSharedMemory);
+};
+
+SharedMemory* ScreenCapturerTest::CreateSharedMemory(size_t size) {
+ return new FakeSharedMemory(new char[size], size);
+}
+
+TEST_F(ScreenCapturerTest, GetScreenListAndSelectScreen) {
+ webrtc::ScreenCapturer::ScreenList screens;
+ EXPECT_TRUE(capturer_->GetScreenList(&screens));
+ for(webrtc::ScreenCapturer::ScreenList::iterator it = screens.begin();
+ it != screens.end(); ++it) {
+ EXPECT_TRUE(capturer_->SelectScreen(it->id));
+ }
+}
+
+TEST_F(ScreenCapturerTest, StartCapturer) {
+ capturer_->Start(&callback_);
+}
+
+TEST_F(ScreenCapturerTest, Capture) {
+ // Assume that Start() treats the screen as invalid initially.
+ DesktopFrame* frame = NULL;
+ EXPECT_CALL(callback_, OnCaptureCompleted(_))
+ .WillOnce(SaveArg<0>(&frame));
+
+ EXPECT_CALL(callback_, CreateSharedMemory(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(static_cast<SharedMemory*>(NULL)));
+
+ capturer_->Start(&callback_);
+ capturer_->Capture(DesktopRegion());
+
+ ASSERT_TRUE(frame);
+ EXPECT_GT(frame->size().width(), 0);
+ EXPECT_GT(frame->size().height(), 0);
+ EXPECT_GE(frame->stride(),
+ frame->size().width() * DesktopFrame::kBytesPerPixel);
+ EXPECT_TRUE(frame->shared_memory() == NULL);
+
+ // Verify that the region contains whole screen.
+ EXPECT_FALSE(frame->updated_region().is_empty());
+ DesktopRegion::Iterator it(frame->updated_region());
+ ASSERT_TRUE(!it.IsAtEnd());
+ EXPECT_TRUE(it.rect().equals(DesktopRect::MakeSize(frame->size())));
+ it.Advance();
+ EXPECT_TRUE(it.IsAtEnd());
+
+ delete frame;
+}
+
+#if defined(WEBRTC_WIN)
+
+TEST_F(ScreenCapturerTest, UseSharedBuffers) {
+ DesktopFrame* frame = NULL;
+ EXPECT_CALL(callback_, OnCaptureCompleted(_))
+ .WillOnce(SaveArg<0>(&frame));
+
+ EXPECT_CALL(callback_, CreateSharedMemory(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke(this, &ScreenCapturerTest::CreateSharedMemory));
+
+ capturer_->Start(&callback_);
+ capturer_->Capture(DesktopRegion());
+
+ ASSERT_TRUE(frame);
+ ASSERT_TRUE(frame->shared_memory());
+ EXPECT_EQ(frame->shared_memory()->id(), kTestSharedMemoryId);
+
+ delete frame;
+}
+
+TEST_F(ScreenCapturerTest, UseMagnifier) {
+ DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault());
+ options.set_allow_use_magnification_api(true);
+ capturer_.reset(ScreenCapturer::Create(options));
+
+ DesktopFrame* frame = NULL;
+ EXPECT_CALL(callback_, OnCaptureCompleted(_)).WillOnce(SaveArg<0>(&frame));
+
+ capturer_->Start(&callback_);
+ capturer_->Capture(DesktopRegion());
+ ASSERT_TRUE(frame);
+ delete frame;
+}
+
+#endif // defined(WEBRTC_WIN)
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_win.cc b/webrtc/modules/desktop_capture/screen_capturer_win.cc
new file mode 100644
index 0000000000..1f33155656
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_win.cc
@@ -0,0 +1,30 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h"
+#include "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h"
+
+namespace webrtc {
+
+// static
+ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
+ rtc::scoped_ptr<ScreenCapturer> gdi_capturer(
+ new ScreenCapturerWinGdi(options));
+
+ if (options.allow_use_magnification_api())
+ return new ScreenCapturerWinMagnifier(gdi_capturer.Pass());
+
+ return gdi_capturer.release();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_x11.cc b/webrtc/modules/desktop_capture/screen_capturer_x11.cc
new file mode 100644
index 0000000000..3a3c418654
--- /dev/null
+++ b/webrtc/modules/desktop_capture/screen_capturer_x11.cc
@@ -0,0 +1,444 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include <string.h>
+#include <set>
+
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/differ.h"
+#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
+#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
+#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+namespace {
+
+// A class to perform video frame capturing for Linux.
+class ScreenCapturerLinux : public ScreenCapturer,
+ public SharedXDisplay::XEventHandler {
+ public:
+ ScreenCapturerLinux();
+ virtual ~ScreenCapturerLinux();
+
+ // TODO(ajwong): Do we really want this to be synchronous?
+ bool Init(const DesktopCaptureOptions& options);
+
+ // DesktopCapturer interface.
+ void Start(Callback* delegate) override;
+ void Capture(const DesktopRegion& region) override;
+
+ // ScreenCapturer interface.
+ bool GetScreenList(ScreenList* screens) override;
+ bool SelectScreen(ScreenId id) override;
+
+ private:
+ Display* display() { return options_.x_display()->display(); }
+
+ // SharedXDisplay::XEventHandler interface.
+ bool HandleXEvent(const XEvent& event) override;
+
+ void InitXDamage();
+
+ // Capture screen pixels to the current buffer in the queue. In the DAMAGE
+ // case, the ScreenCapturerHelper already holds the list of invalid rectangles
+ // from HandleXEvent(). In the non-DAMAGE case, this captures the
+ // whole screen, then calculates some invalid rectangles that include any
+ // differences between this and the previous capture.
+ DesktopFrame* CaptureScreen();
+
+ // Called when the screen configuration is changed.
+ void ScreenConfigurationChanged();
+
+ // Synchronize the current buffer with |last_buffer_|, by copying pixels from
+ // the area of |last_invalid_rects|.
+ // Note this only works on the assumption that kNumBuffers == 2, as
+ // |last_invalid_rects| holds the differences from the previous buffer and
+ // the one prior to that (which will then be the current buffer).
+ void SynchronizeFrame();
+
+ void DeinitXlib();
+
+ DesktopCaptureOptions options_;
+
+ Callback* callback_;
+
+ // X11 graphics context.
+ GC gc_;
+ Window root_window_;
+
+ // XFixes.
+ bool has_xfixes_;
+ int xfixes_event_base_;
+ int xfixes_error_base_;
+
+ // XDamage information.
+ bool use_damage_;
+ Damage damage_handle_;
+ int damage_event_base_;
+ int damage_error_base_;
+ XserverRegion damage_region_;
+
+ // Access to the X Server's pixel buffer.
+ XServerPixelBuffer x_server_pixel_buffer_;
+
+ // A thread-safe list of invalid rectangles, and the size of the most
+ // recently captured screen.
+ ScreenCapturerHelper helper_;
+
+ // Queue of the frames buffers.
+ ScreenCaptureFrameQueue queue_;
+
+ // Invalid region from the previous capture. This is used to synchronize the
+ // current with the last buffer used.
+ DesktopRegion last_invalid_region_;
+
+ // |Differ| for use when polling for changes.
+ rtc::scoped_ptr<Differ> differ_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux);
+};
+
+ScreenCapturerLinux::ScreenCapturerLinux()
+ : callback_(NULL),
+ gc_(NULL),
+ root_window_(BadValue),
+ has_xfixes_(false),
+ xfixes_event_base_(-1),
+ xfixes_error_base_(-1),
+ use_damage_(false),
+ damage_handle_(0),
+ damage_event_base_(-1),
+ damage_error_base_(-1),
+ damage_region_(0) {
+ helper_.SetLogGridSize(4);
+}
+
+ScreenCapturerLinux::~ScreenCapturerLinux() {
+ options_.x_display()->RemoveEventHandler(ConfigureNotify, this);
+ if (use_damage_) {
+ options_.x_display()->RemoveEventHandler(
+ damage_event_base_ + XDamageNotify, this);
+ }
+ DeinitXlib();
+}
+
+bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) {
+ options_ = options;
+
+ root_window_ = RootWindow(display(), DefaultScreen(display()));
+ if (root_window_ == BadValue) {
+ LOG(LS_ERROR) << "Unable to get the root window";
+ DeinitXlib();
+ return false;
+ }
+
+ gc_ = XCreateGC(display(), root_window_, 0, NULL);
+ if (gc_ == NULL) {
+ LOG(LS_ERROR) << "Unable to get graphics context";
+ DeinitXlib();
+ return false;
+ }
+
+ options_.x_display()->AddEventHandler(ConfigureNotify, this);
+
+ // Check for XFixes extension. This is required for cursor shape
+ // notifications, and for our use of XDamage.
+ if (XFixesQueryExtension(display(), &xfixes_event_base_,
+ &xfixes_error_base_)) {
+ has_xfixes_ = true;
+ } else {
+ LOG(LS_INFO) << "X server does not support XFixes.";
+ }
+
+ // Register for changes to the dimensions of the root window.
+ XSelectInput(display(), root_window_, StructureNotifyMask);
+
+ if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) {
+ LOG(LS_ERROR) << "Failed to initialize pixel buffer.";
+ return false;
+ }
+
+ if (options_.use_update_notifications()) {
+ InitXDamage();
+ }
+
+ return true;
+}
+
+void ScreenCapturerLinux::InitXDamage() {
+ // Our use of XDamage requires XFixes.
+ if (!has_xfixes_) {
+ return;
+ }
+
+ // Check for XDamage extension.
+ if (!XDamageQueryExtension(display(), &damage_event_base_,
+ &damage_error_base_)) {
+ LOG(LS_INFO) << "X server does not support XDamage.";
+ return;
+ }
+
+ // TODO(lambroslambrou): Disable DAMAGE in situations where it is known
+ // to fail, such as when Desktop Effects are enabled, with graphics
+ // drivers (nVidia, ATI) that fail to report DAMAGE notifications
+ // properly.
+
+ // Request notifications every time the screen becomes damaged.
+ damage_handle_ = XDamageCreate(display(), root_window_,
+ XDamageReportNonEmpty);
+ if (!damage_handle_) {
+ LOG(LS_ERROR) << "Unable to initialize XDamage.";
+ return;
+ }
+
+ // Create an XFixes server-side region to collate damage into.
+ damage_region_ = XFixesCreateRegion(display(), 0, 0);
+ if (!damage_region_) {
+ XDamageDestroy(display(), damage_handle_);
+ LOG(LS_ERROR) << "Unable to create XFixes region.";
+ return;
+ }
+
+ options_.x_display()->AddEventHandler(
+ damage_event_base_ + XDamageNotify, this);
+
+ use_damage_ = true;
+ LOG(LS_INFO) << "Using XDamage extension.";
+}
+
+void ScreenCapturerLinux::Start(Callback* callback) {
+ RTC_DCHECK(!callback_);
+ RTC_DCHECK(callback);
+
+ callback_ = callback;
+}
+
+void ScreenCapturerLinux::Capture(const DesktopRegion& region) {
+ TickTime capture_start_time = TickTime::Now();
+
+ queue_.MoveToNextFrame();
+
+ // Process XEvents for XDamage and cursor shape tracking.
+ options_.x_display()->ProcessPendingXEvents();
+
+ // ProcessPendingXEvents() may call ScreenConfigurationChanged() which
+ // reinitializes |x_server_pixel_buffer_|. Check if the pixel buffer is still
+ // in a good shape.
+ if (!x_server_pixel_buffer_.is_initialized()) {
+ // We failed to initialize pixel buffer.
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ // If the current frame 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()) {
+ rtc::scoped_ptr<DesktopFrame> frame(
+ new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
+ queue_.ReplaceCurrentFrame(frame.release());
+ }
+
+ // Refresh the Differ helper used by CaptureFrame(), if needed.
+ DesktopFrame* frame = queue_.current_frame();
+ if (!use_damage_ && (
+ !differ_.get() ||
+ (differ_->width() != frame->size().width()) ||
+ (differ_->height() != frame->size().height()) ||
+ (differ_->bytes_per_row() != frame->stride()))) {
+ differ_.reset(new Differ(frame->size().width(), frame->size().height(),
+ DesktopFrame::kBytesPerPixel,
+ frame->stride()));
+ }
+
+ DesktopFrame* result = CaptureScreen();
+ last_invalid_region_ = result->updated_region();
+ result->set_capture_time_ms(
+ (TickTime::Now() - capture_start_time).Milliseconds());
+ callback_->OnCaptureCompleted(result);
+}
+
+bool ScreenCapturerLinux::GetScreenList(ScreenList* screens) {
+ RTC_DCHECK(screens->size() == 0);
+ // TODO(jiayl): implement screen enumeration.
+ Screen default_screen;
+ default_screen.id = 0;
+ screens->push_back(default_screen);
+ return true;
+}
+
+bool ScreenCapturerLinux::SelectScreen(ScreenId id) {
+ // TODO(jiayl): implement screen selection.
+ return true;
+}
+
+bool ScreenCapturerLinux::HandleXEvent(const XEvent& event) {
+ if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) {
+ const XDamageNotifyEvent* damage_event =
+ reinterpret_cast<const XDamageNotifyEvent*>(&event);
+ if (damage_event->damage != damage_handle_)
+ return false;
+ RTC_DCHECK(damage_event->level == XDamageReportNonEmpty);
+ return true;
+ } else if (event.type == ConfigureNotify) {
+ ScreenConfigurationChanged();
+ return true;
+ }
+ return false;
+}
+
+DesktopFrame* ScreenCapturerLinux::CaptureScreen() {
+ DesktopFrame* frame = queue_.current_frame()->Share();
+ assert(x_server_pixel_buffer_.window_size().equals(frame->size()));
+
+ // Pass the screen size to the helper, so it can clip the invalid region if it
+ // expands that region to a grid.
+ helper_.set_size_most_recent(frame->size());
+
+ // In the DAMAGE case, ensure the frame is up-to-date with the previous frame
+ // if any. If there isn't a previous frame, that means a screen-resolution
+ // change occurred, and |invalid_rects| will be updated to include the whole
+ // screen.
+ if (use_damage_ && queue_.previous_frame())
+ SynchronizeFrame();
+
+ DesktopRegion* updated_region = frame->mutable_updated_region();
+
+ x_server_pixel_buffer_.Synchronize();
+ if (use_damage_ && queue_.previous_frame()) {
+ // Atomically fetch and clear the damage region.
+ XDamageSubtract(display(), damage_handle_, None, damage_region_);
+ int rects_num = 0;
+ XRectangle bounds;
+ XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_,
+ &rects_num, &bounds);
+ for (int i = 0; i < rects_num; ++i) {
+ updated_region->AddRect(DesktopRect::MakeXYWH(
+ rects[i].x, rects[i].y, rects[i].width, rects[i].height));
+ }
+ XFree(rects);
+ helper_.InvalidateRegion(*updated_region);
+
+ // Capture the damaged portions of the desktop.
+ helper_.TakeInvalidRegion(updated_region);
+
+ // Clip the damaged portions to the current screen size, just in case some
+ // spurious XDamage notifications were received for a previous (larger)
+ // screen size.
+ updated_region->IntersectWith(
+ DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()));
+
+ for (DesktopRegion::Iterator it(*updated_region);
+ !it.IsAtEnd(); it.Advance()) {
+ x_server_pixel_buffer_.CaptureRect(it.rect(), frame);
+ }
+ } else {
+ // Doing full-screen polling, or this is the first capture after a
+ // screen-resolution change. In either case, need a full-screen capture.
+ DesktopRect screen_rect = DesktopRect::MakeSize(frame->size());
+ x_server_pixel_buffer_.CaptureRect(screen_rect, frame);
+
+ if (queue_.previous_frame()) {
+ // Full-screen polling, so calculate the invalid rects here, based on the
+ // changed pixels between current and previous buffers.
+ RTC_DCHECK(differ_.get() != NULL);
+ RTC_DCHECK(queue_.previous_frame()->data());
+ differ_->CalcDirtyRegion(queue_.previous_frame()->data(),
+ frame->data(), updated_region);
+ } else {
+ // No previous buffer, so always invalidate the whole screen, whether
+ // or not DAMAGE is being used. DAMAGE doesn't necessarily send a
+ // full-screen notification after a screen-resolution change, so
+ // this is done here.
+ updated_region->SetRect(screen_rect);
+ }
+ }
+
+ return frame;
+}
+
+void ScreenCapturerLinux::ScreenConfigurationChanged() {
+ // Make sure the frame buffers will be reallocated.
+ queue_.Reset();
+
+ helper_.ClearInvalidRegion();
+ if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) {
+ LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen "
+ "configuration change.";
+ }
+}
+
+void ScreenCapturerLinux::SynchronizeFrame() {
+ // Synchronize the current buffer with the previous one since we do not
+ // capture the entire desktop. Note that encoder may be reading from the
+ // previous buffer at this time so thread access complaints are false
+ // positives.
+
+ // TODO(hclam): We can reduce the amount of copying here by subtracting
+ // |capturer_helper_|s region from |last_invalid_region_|.
+ // http://crbug.com/92354
+ RTC_DCHECK(queue_.previous_frame());
+
+ DesktopFrame* current = queue_.current_frame();
+ DesktopFrame* last = queue_.previous_frame();
+ RTC_DCHECK(current != last);
+ for (DesktopRegion::Iterator it(last_invalid_region_);
+ !it.IsAtEnd(); it.Advance()) {
+ current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect());
+ }
+}
+
+void ScreenCapturerLinux::DeinitXlib() {
+ if (gc_) {
+ XFreeGC(display(), gc_);
+ gc_ = NULL;
+ }
+
+ x_server_pixel_buffer_.Release();
+
+ if (display()) {
+ if (damage_handle_) {
+ XDamageDestroy(display(), damage_handle_);
+ damage_handle_ = 0;
+ }
+
+ if (damage_region_) {
+ XFixesDestroyRegion(display(), damage_region_);
+ damage_region_ = 0;
+ }
+ }
+}
+
+} // namespace
+
+// static
+ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
+ if (!options.x_display())
+ return NULL;
+
+ rtc::scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux());
+ if (!capturer->Init(options))
+ capturer.reset();
+ return capturer.release();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/shared_desktop_frame.cc b/webrtc/modules/desktop_capture/shared_desktop_frame.cc
new file mode 100644
index 0000000000..1f1aefa13b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/shared_desktop_frame.cc
@@ -0,0 +1,80 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/shared_desktop_frame.h"
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/system_wrappers/include/atomic32.h"
+
+namespace webrtc {
+
+class SharedDesktopFrame::Core {
+ public:
+ Core(DesktopFrame* frame) : frame_(frame) {}
+
+ DesktopFrame* frame() { return frame_.get(); }
+
+ bool HasOneRef() { return ref_count_.Value() == 1; }
+
+ virtual int32_t AddRef() {
+ return ++ref_count_;
+ }
+
+ virtual int32_t Release() {
+ int32_t ref_count;
+ ref_count = --ref_count_;
+ if (ref_count == 0)
+ delete this;
+ return ref_count;
+ }
+
+ private:
+ virtual ~Core() {}
+
+ Atomic32 ref_count_;
+ rtc::scoped_ptr<DesktopFrame> frame_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+SharedDesktopFrame::~SharedDesktopFrame() {}
+
+// static
+SharedDesktopFrame* SharedDesktopFrame::Wrap(
+ DesktopFrame* desktop_frame) {
+ rtc::scoped_refptr<Core> core(new Core(desktop_frame));
+ return new SharedDesktopFrame(core);
+}
+
+DesktopFrame* SharedDesktopFrame::GetUnderlyingFrame() {
+ return core_->frame();
+}
+
+SharedDesktopFrame* SharedDesktopFrame::Share() {
+ SharedDesktopFrame* result = new SharedDesktopFrame(core_);
+ result->set_dpi(dpi());
+ result->set_capture_time_ms(capture_time_ms());
+ *result->mutable_updated_region() = updated_region();
+ return result;
+}
+
+bool SharedDesktopFrame::IsShared() {
+ return !core_->HasOneRef();
+}
+
+SharedDesktopFrame::SharedDesktopFrame(rtc::scoped_refptr<Core> core)
+ : DesktopFrame(core->frame()->size(),
+ core->frame()->stride(),
+ core->frame()->data(),
+ core->frame()->shared_memory()),
+ core_(core) {
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/shared_desktop_frame.h b/webrtc/modules/desktop_capture/shared_desktop_frame.h
new file mode 100644
index 0000000000..7d18db153c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/shared_desktop_frame.h
@@ -0,0 +1,49 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_
+
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace webrtc {
+
+// SharedDesktopFrame is a DesktopFrame that may have multiple instances all
+// sharing the same buffer.
+class SharedDesktopFrame : public DesktopFrame {
+ public:
+ virtual ~SharedDesktopFrame();
+
+ static SharedDesktopFrame* Wrap(DesktopFrame* desktop_frame);
+
+ // Returns the underlying instance of DesktopFrame.
+ DesktopFrame* GetUnderlyingFrame();
+
+ // Creates a clone of this object.
+ SharedDesktopFrame* Share();
+
+ // Checks if the frame is currently shared. If it returns false it's
+ // guaranteed that there are no clones of the object.
+ bool IsShared();
+
+ private:
+ class Core;
+
+ SharedDesktopFrame(rtc::scoped_refptr<Core> core);
+
+ rtc::scoped_refptr<Core> core_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(SharedDesktopFrame);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_
diff --git a/webrtc/modules/desktop_capture/shared_memory.cc b/webrtc/modules/desktop_capture/shared_memory.cc
new file mode 100644
index 0000000000..872116eec9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/shared_memory.cc
@@ -0,0 +1,28 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/shared_memory.h"
+
+namespace webrtc {
+
+#if defined(WEBRTC_WIN)
+const SharedMemory::Handle SharedMemory::kInvalidHandle = NULL;
+#else
+const SharedMemory::Handle SharedMemory::kInvalidHandle = -1;
+#endif
+
+SharedMemory::SharedMemory(void* data, size_t size, Handle handle, int id)
+ : data_(data),
+ size_(size),
+ handle_(handle),
+ id_(id) {
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/shared_memory.h b/webrtc/modules/desktop_capture/shared_memory.h
new file mode 100644
index 0000000000..631f119b5f
--- /dev/null
+++ b/webrtc/modules/desktop_capture/shared_memory.h
@@ -0,0 +1,68 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_
+
+#include <stddef.h>
+
+#if defined(WEBRTC_WIN)
+#include <windows.h>
+#endif
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// SharedMemory is a base class for shared memory. It stores all required
+// parameters of the buffer, but doesn't have any logic to allocate or destroy
+// the actual buffer. DesktopCapturer consumers that need to use shared memory
+// for video frames must extend this class with creation and destruction logic
+// specific for the target platform and then implement
+// DesktopCapturer::Delegate::CreateSharedMemory() as appropriate.
+class SharedMemory {
+ public:
+#if defined(WEBRTC_WIN)
+ typedef HANDLE Handle;
+ static const Handle kInvalidHandle;
+#else
+ typedef int Handle;
+ static const Handle kInvalidHandle;
+#endif
+
+ void* data() const { return data_; }
+ size_t size() const { return size_; }
+
+ // Platform-specific handle of the buffer.
+ Handle handle() const { return handle_; }
+
+ // Integer identifier that can be used used by consumers of DesktopCapturer
+ // interface to identify shared memory buffers it created.
+ int id() const { return id_; }
+
+ virtual ~SharedMemory() {}
+
+ protected:
+ SharedMemory(void* data, size_t size, Handle handle, int id);
+
+ void* const data_;
+ const size_t size_;
+ const Handle handle_;
+ const int id_;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(SharedMemory);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_
+
diff --git a/webrtc/modules/desktop_capture/win/cursor.cc b/webrtc/modules/desktop_capture/win/cursor.cc
new file mode 100644
index 0000000000..a3acaf822b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor.cc
@@ -0,0 +1,248 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/cursor.h"
+
+#include <algorithm>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+namespace {
+
+#if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
+
+#define RGBA(r, g, b, a) \
+ ((((a) << 24) & 0xff000000) | \
+ (((b) << 16) & 0xff0000) | \
+ (((g) << 8) & 0xff00) | \
+ ((r) & 0xff))
+
+#else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
+
+#define RGBA(r, g, b, a) \
+ ((((r) << 24) & 0xff000000) | \
+ (((g) << 16) & 0xff0000) | \
+ (((b) << 8) & 0xff00) | \
+ ((a) & 0xff))
+
+#endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
+
+const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
+
+// Pixel colors used when generating cursor outlines.
+const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
+const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
+const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
+
+const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
+
+// Expands the cursor shape to add a white outline for visibility against
+// dark backgrounds.
+void AddCursorOutline(int width, int height, uint32_t* data) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
+ // neighbor pixels to see if this should be changed to an outline pixel.
+ if (*data == kPixelRgbaTransparent) {
+ // Change to white pixel if any neighbors (top, bottom, left, right)
+ // are black.
+ if ((y > 0 && data[-width] == kPixelRgbaBlack) ||
+ (y < height - 1 && data[width] == kPixelRgbaBlack) ||
+ (x > 0 && data[-1] == kPixelRgbaBlack) ||
+ (x < width - 1 && data[1] == kPixelRgbaBlack)) {
+ *data = kPixelRgbaWhite;
+ }
+ }
+ data++;
+ }
+ }
+}
+
+// Premultiplies RGB components of the pixel data in the given image by
+// the corresponding alpha components.
+void AlphaMul(uint32_t* data, int width, int height) {
+ static_assert(sizeof(uint32_t) == kBytesPerPixel,
+ "size of uint32 should be the number of bytes per pixel");
+
+ for (uint32_t* data_end = data + width * height; data != data_end; ++data) {
+ RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data);
+ RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data);
+ to->rgbBlue =
+ (static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff;
+ to->rgbGreen =
+ (static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff;
+ to->rgbRed =
+ (static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff;
+ }
+}
+
+// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
+// Returns true if non-zero alpha is found. |stride| is expressed in pixels.
+bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) {
+ const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ if (plane->rgbReserved != 0)
+ return true;
+ plane += 1;
+ }
+ plane += stride - width;
+ }
+
+ return false;
+}
+
+} // namespace
+
+MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) {
+ ICONINFO iinfo;
+ if (!GetIconInfo(cursor, &iinfo)) {
+ LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = "
+ << GetLastError();
+ return NULL;
+ }
+
+ int hotspot_x = iinfo.xHotspot;
+ int hotspot_y = iinfo.yHotspot;
+
+ // Make sure the bitmaps will be freed.
+ win::ScopedBitmap scoped_mask(iinfo.hbmMask);
+ win::ScopedBitmap scoped_color(iinfo.hbmColor);
+ bool is_color = iinfo.hbmColor != NULL;
+
+ // Get |scoped_mask| dimensions.
+ BITMAP bitmap_info;
+ if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) {
+ LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = "
+ << GetLastError();
+ return NULL;
+ }
+
+ int width = bitmap_info.bmWidth;
+ int height = bitmap_info.bmHeight;
+ rtc::scoped_ptr<uint32_t[]> mask_data(new uint32_t[width * height]);
+
+ // Get pixel data from |scoped_mask| converting it to 32bpp along the way.
+ // GetDIBits() sets the alpha component of every pixel to 0.
+ BITMAPV5HEADER bmi = {0};
+ bmi.bV5Size = sizeof(bmi);
+ bmi.bV5Width = width;
+ bmi.bV5Height = -height; // request a top-down bitmap.
+ bmi.bV5Planes = 1;
+ bmi.bV5BitCount = kBytesPerPixel * 8;
+ bmi.bV5Compression = BI_RGB;
+ bmi.bV5AlphaMask = 0xff000000;
+ bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
+ bmi.bV5Intent = LCS_GM_BUSINESS;
+ if (!GetDIBits(dc,
+ scoped_mask,
+ 0,
+ height,
+ mask_data.get(),
+ reinterpret_cast<BITMAPINFO*>(&bmi),
+ DIB_RGB_COLORS)) {
+ LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
+ << GetLastError();
+ return NULL;
+ }
+
+ uint32_t* mask_plane = mask_data.get();
+ rtc::scoped_ptr<DesktopFrame> image(
+ new BasicDesktopFrame(DesktopSize(width, height)));
+ bool has_alpha = false;
+
+ if (is_color) {
+ image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
+ // Get the pixels from the color bitmap.
+ if (!GetDIBits(dc,
+ scoped_color,
+ 0,
+ height,
+ image->data(),
+ reinterpret_cast<BITMAPINFO*>(&bmi),
+ DIB_RGB_COLORS)) {
+ LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
+ << GetLastError();
+ return NULL;
+ }
+
+ // GetDIBits() does not provide any indication whether the bitmap has alpha
+ // channel, so we use HasAlphaChannel() below to find it out.
+ has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()),
+ width, width, height);
+ } else {
+ // For non-color cursors, the mask contains both an AND and an XOR mask and
+ // the height includes both. Thus, the width is correct, but we need to
+ // divide by 2 to get the correct mask height.
+ height /= 2;
+
+ image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
+
+ // The XOR mask becomes the color bitmap.
+ memcpy(
+ image->data(), mask_plane + (width * height), image->stride() * height);
+ }
+
+ // Reconstruct transparency from the mask if the color image does not has
+ // alpha channel.
+ if (!has_alpha) {
+ bool add_outline = false;
+ uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
+ uint32_t* mask = mask_plane;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // The two bitmaps combine as follows:
+ // mask color Windows Result Our result RGB Alpha
+ // 0 00 Black Black 00 ff
+ // 0 ff White White ff ff
+ // 1 00 Screen Transparent 00 00
+ // 1 ff Reverse-screen Black 00 ff
+ //
+ // Since we don't support XOR cursors, we replace the "Reverse Screen"
+ // with black. In this case, we also add an outline around the cursor
+ // so that it is visible against a dark background.
+ if (*mask == kPixelRgbWhite) {
+ if (*dst != 0) {
+ add_outline = true;
+ *dst = kPixelRgbaBlack;
+ } else {
+ *dst = kPixelRgbaTransparent;
+ }
+ } else {
+ *dst = kPixelRgbaBlack ^ *dst;
+ }
+
+ ++dst;
+ ++mask;
+ }
+ }
+ if (add_outline) {
+ AddCursorOutline(
+ width, height, reinterpret_cast<uint32_t*>(image->data()));
+ }
+ }
+
+ // Pre-multiply the resulting pixels since MouseCursor uses premultiplied
+ // images.
+ AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height);
+
+ return new MouseCursor(
+ image.release(), DesktopVector(hotspot_x, hotspot_y));
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/cursor.h b/webrtc/modules/desktop_capture/win/cursor.h
new file mode 100644
index 0000000000..d521cc0819
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor.h
@@ -0,0 +1,25 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
+
+#include <windows.h>
+
+namespace webrtc {
+
+class MouseCursor;
+
+// Converts an HCURSOR into a |MouseCursor| instance.
+MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor);
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur
new file mode 100644
index 0000000000..27702b825c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur
new file mode 100644
index 0000000000..7e0d8596da
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur
new file mode 100644
index 0000000000..fefb09e1a1
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur
new file mode 100644
index 0000000000..4f8a094f31
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur
new file mode 100644
index 0000000000..ac9cdbfbb3
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur
new file mode 100644
index 0000000000..efdbee5415
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur
new file mode 100644
index 0000000000..9678d55446
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur
Binary files differ
diff --git a/webrtc/modules/desktop_capture/win/cursor_unittest.cc b/webrtc/modules/desktop_capture/win/cursor_unittest.cc
new file mode 100644
index 0000000000..32bab13e00
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_unittest.cc
@@ -0,0 +1,89 @@
+/*
+ * 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 "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/win/cursor.h"
+#include "webrtc/modules/desktop_capture/win/cursor_unittest_resources.h"
+#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
+
+namespace webrtc {
+
+namespace {
+
+// Loads |left| from resources, converts it to a |MouseCursor| instance and
+// compares pixels with |right|. Returns true of MouseCursor bits match |right|.
+// |right| must be a 32bpp cursor with alpha channel.
+bool ConvertToMouseShapeAndCompare(unsigned left, unsigned right) {
+ HMODULE instance = GetModuleHandle(NULL);
+
+ // Load |left| from the EXE module's resources.
+ win::ScopedCursor cursor(reinterpret_cast<HCURSOR>(
+ LoadImage(instance, MAKEINTRESOURCE(left), IMAGE_CURSOR, 0, 0, 0)));
+ EXPECT_TRUE(cursor != NULL);
+
+ // Convert |cursor| to |mouse_shape|.
+ HDC dc = GetDC(NULL);
+ rtc::scoped_ptr<MouseCursor> mouse_shape(
+ CreateMouseCursorFromHCursor(dc, cursor));
+ ReleaseDC(NULL, dc);
+
+ EXPECT_TRUE(mouse_shape.get());
+
+ // Load |right|.
+ cursor.Set(reinterpret_cast<HCURSOR>(
+ LoadImage(instance, MAKEINTRESOURCE(right), IMAGE_CURSOR, 0, 0, 0)));
+
+ ICONINFO iinfo;
+ EXPECT_TRUE(GetIconInfo(cursor, &iinfo));
+ EXPECT_TRUE(iinfo.hbmColor);
+
+ // Make sure the bitmaps will be freed.
+ win::ScopedBitmap scoped_mask(iinfo.hbmMask);
+ win::ScopedBitmap scoped_color(iinfo.hbmColor);
+
+ // Get |scoped_color| dimensions.
+ BITMAP bitmap_info;
+ EXPECT_TRUE(GetObject(scoped_color, sizeof(bitmap_info), &bitmap_info));
+
+ int width = bitmap_info.bmWidth;
+ int height = bitmap_info.bmHeight;
+ EXPECT_TRUE(DesktopSize(width, height).equals(mouse_shape->image()->size()));
+
+ // Get the pixels from |scoped_color|.
+ int size = width * height;
+ rtc::scoped_ptr<uint32_t[]> data(new uint32_t[size]);
+ EXPECT_TRUE(GetBitmapBits(scoped_color, size * sizeof(uint32_t), data.get()));
+
+ // Compare the 32bpp image in |mouse_shape| with the one loaded from |right|.
+ return memcmp(data.get(), mouse_shape->image()->data(),
+ size * sizeof(uint32_t)) == 0;
+}
+
+} // namespace
+
+TEST(MouseCursorTest, MatchCursors) {
+ EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR1_24BPP,
+ IDD_CURSOR1_32BPP));
+
+ EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR1_8BPP,
+ IDD_CURSOR1_32BPP));
+
+ EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR2_1BPP,
+ IDD_CURSOR2_32BPP));
+
+ EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR3_4BPP,
+ IDD_CURSOR3_32BPP));
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/cursor_unittest_resources.h b/webrtc/modules/desktop_capture/win/cursor_unittest_resources.h
new file mode 100644
index 0000000000..89545c165f
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_unittest_resources.h
@@ -0,0 +1,24 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
+
+#define IDD_CURSOR1_24BPP 101
+#define IDD_CURSOR1_32BPP 102
+#define IDD_CURSOR1_8BPP 103
+
+#define IDD_CURSOR2_1BPP 104
+#define IDD_CURSOR2_32BPP 105
+
+#define IDD_CURSOR3_4BPP 106
+#define IDD_CURSOR3_32BPP 107
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
diff --git a/webrtc/modules/desktop_capture/win/cursor_unittest_resources.rc b/webrtc/modules/desktop_capture/win/cursor_unittest_resources.rc
new file mode 100644
index 0000000000..4f41489ecd
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/cursor_unittest_resources.rc
@@ -0,0 +1,28 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/cursor_unittest_resources.h"
+
+// These cursors are matched with their less than 32bpp counterparts below.
+IDD_CURSOR1_32BPP CURSOR "cursor_test_data/1_32bpp.cur"
+IDD_CURSOR2_32BPP CURSOR "cursor_test_data/2_32bpp.cur"
+IDD_CURSOR3_32BPP CURSOR "cursor_test_data/3_32bpp.cur"
+
+// Matches IDD_CURSOR1_32BPP.
+IDD_CURSOR1_24BPP CURSOR "cursor_test_data/1_24bpp.cur"
+
+// Matches IDD_CURSOR1_32BPP.
+IDD_CURSOR1_8BPP CURSOR "cursor_test_data/1_8bpp.cur"
+
+// Matches IDD_CURSOR2_32BPP.
+IDD_CURSOR2_1BPP CURSOR "cursor_test_data/2_1bpp.cur"
+
+// Matches IDD_CURSOR3_32BPP.
+IDD_CURSOR3_4BPP CURSOR "cursor_test_data/3_4bpp.cur"
diff --git a/webrtc/modules/desktop_capture/win/desktop.cc b/webrtc/modules/desktop_capture/win/desktop.cc
new file mode 100644
index 0000000000..97bbfb717b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/desktop.cc
@@ -0,0 +1,110 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/desktop.h"
+
+#include <vector>
+
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+Desktop::Desktop(HDESK desktop, bool own) : desktop_(desktop), own_(own) {
+}
+
+Desktop::~Desktop() {
+ if (own_ && desktop_ != NULL) {
+ if (!::CloseDesktop(desktop_)) {
+ LOG(LS_ERROR) << "Failed to close the owned desktop handle: "
+ << GetLastError();
+ }
+ }
+}
+
+bool Desktop::GetName(std::wstring* desktop_name_out) const {
+ if (desktop_ == NULL)
+ return false;
+
+ DWORD length = 0;
+ int rv = GetUserObjectInformationW(desktop_, UOI_NAME, NULL, 0, &length);
+ if (rv || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ abort();
+
+ length /= sizeof(WCHAR);
+ std::vector<WCHAR> buffer(length);
+ if (!GetUserObjectInformationW(desktop_, UOI_NAME, &buffer[0],
+ length * sizeof(WCHAR), &length)) {
+ LOG(LS_ERROR) << "Failed to query the desktop name: " << GetLastError();
+ return false;
+ }
+
+ desktop_name_out->assign(&buffer[0], length / sizeof(WCHAR));
+ return true;
+}
+
+bool Desktop::IsSame(const Desktop& other) const {
+ std::wstring name;
+ if (!GetName(&name))
+ return false;
+
+ std::wstring other_name;
+ if (!other.GetName(&other_name))
+ return false;
+
+ return name == other_name;
+}
+
+bool Desktop::SetThreadDesktop() const {
+ if (!::SetThreadDesktop(desktop_)) {
+ LOG(LS_ERROR) << "Failed to assign the desktop to the current thread: "
+ << GetLastError();
+ return false;
+ }
+
+ return true;
+}
+
+Desktop* Desktop::GetDesktop(const WCHAR* desktop_name) {
+ ACCESS_MASK desired_access =
+ DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE |
+ DESKTOP_HOOKCONTROL | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
+ DESKTOP_SWITCHDESKTOP | GENERIC_WRITE;
+ HDESK desktop = OpenDesktop(desktop_name, 0, FALSE, desired_access);
+ if (desktop == NULL) {
+ LOG(LS_ERROR) << "Failed to open the desktop '" << desktop_name << "': "
+ << GetLastError();
+ return NULL;
+ }
+
+ return new Desktop(desktop, true);
+}
+
+Desktop* Desktop::GetInputDesktop() {
+ HDESK desktop = OpenInputDesktop(
+ 0, FALSE, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE);
+ if (desktop == NULL)
+ return NULL;
+
+ return new Desktop(desktop, true);
+}
+
+Desktop* Desktop::GetThreadDesktop() {
+ HDESK desktop = ::GetThreadDesktop(GetCurrentThreadId());
+ if (desktop == NULL) {
+ LOG(LS_ERROR) << "Failed to retrieve the handle of the desktop assigned to "
+ "the current thread: "
+ << GetLastError();
+ return NULL;
+ }
+
+ return new Desktop(desktop, false);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/desktop.h b/webrtc/modules/desktop_capture/win/desktop.h
new file mode 100644
index 0000000000..dc3b8c61b9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/desktop.h
@@ -0,0 +1,64 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_
+
+#include <windows.h>
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace webrtc {
+
+class Desktop {
+ public:
+ ~Desktop();
+
+ // Returns the name of the desktop represented by the object. Return false if
+ // quering the name failed for any reason.
+ bool GetName(std::wstring* desktop_name_out) const;
+
+ // Returns true if |other| has the same name as this desktop. Returns false
+ // in any other case including failing Win32 APIs and uninitialized desktop
+ // handles.
+ bool IsSame(const Desktop& other) const;
+
+ // Assigns the desktop to the current thread. Returns false is the operation
+ // failed for any reason.
+ bool SetThreadDesktop() const;
+
+ // Returns the desktop by its name or NULL if an error occurs.
+ static Desktop* GetDesktop(const wchar_t* desktop_name);
+
+ // Returns the desktop currently receiving user input or NULL if an error
+ // occurs.
+ static Desktop* GetInputDesktop();
+
+ // Returns the desktop currently assigned to the calling thread or NULL if
+ // an error occurs.
+ static Desktop* GetThreadDesktop();
+
+ private:
+ Desktop(HDESK desktop, bool own);
+
+ // The desktop handle.
+ HDESK desktop_;
+
+ // True if |desktop_| must be closed on teardown.
+ bool own_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(Desktop);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_
diff --git a/webrtc/modules/desktop_capture/win/scoped_gdi_object.h b/webrtc/modules/desktop_capture/win/scoped_gdi_object.h
new file mode 100644
index 0000000000..1cac63e43d
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/scoped_gdi_object.h
@@ -0,0 +1,95 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
+
+#include <windows.h>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+namespace win {
+
+// Scoper for GDI objects.
+template<class T, class Traits>
+class ScopedGDIObject {
+ public:
+ ScopedGDIObject() : handle_(NULL) {}
+ explicit ScopedGDIObject(T object) : handle_(object) {}
+
+ ~ScopedGDIObject() {
+ Traits::Close(handle_);
+ }
+
+ T Get() {
+ return handle_;
+ }
+
+ void Set(T object) {
+ if (handle_ && object != handle_)
+ Traits::Close(handle_);
+ handle_ = object;
+ }
+
+ ScopedGDIObject& operator=(T object) {
+ Set(object);
+ return *this;
+ }
+
+ T release() {
+ T object = handle_;
+ handle_ = NULL;
+ return object;
+ }
+
+ operator T() { return handle_; }
+
+ private:
+ T handle_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScopedGDIObject);
+};
+
+// The traits class that uses DeleteObject() to close a handle.
+template <typename T>
+class DeleteObjectTraits {
+ public:
+ // Closes the handle.
+ static void Close(T handle) {
+ if (handle)
+ DeleteObject(handle);
+ }
+
+ private:
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DeleteObjectTraits);
+};
+
+// The traits class that uses DestroyCursor() to close a handle.
+class DestroyCursorTraits {
+ public:
+ // Closes the handle.
+ static void Close(HCURSOR handle) {
+ if (handle)
+ DestroyCursor(handle);
+ }
+
+ private:
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DestroyCursorTraits);
+};
+
+typedef ScopedGDIObject<HBITMAP, DeleteObjectTraits<HBITMAP> > ScopedBitmap;
+typedef ScopedGDIObject<HCURSOR, DestroyCursorTraits> ScopedCursor;
+
+} // namespace win
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
diff --git a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc
new file mode 100644
index 0000000000..12f9e89e96
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc
@@ -0,0 +1,57 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
+
+#include "webrtc/system_wrappers/include/logging.h"
+
+#include "webrtc/modules/desktop_capture/win/desktop.h"
+
+namespace webrtc {
+
+ScopedThreadDesktop::ScopedThreadDesktop()
+ : initial_(Desktop::GetThreadDesktop()) {
+}
+
+ScopedThreadDesktop::~ScopedThreadDesktop() {
+ Revert();
+}
+
+bool ScopedThreadDesktop::IsSame(const Desktop& desktop) {
+ if (assigned_.get() != NULL) {
+ return assigned_->IsSame(desktop);
+ } else {
+ return initial_->IsSame(desktop);
+ }
+}
+
+void ScopedThreadDesktop::Revert() {
+ if (assigned_.get() != NULL) {
+ initial_->SetThreadDesktop();
+ assigned_.reset();
+ }
+}
+
+bool ScopedThreadDesktop::SetThreadDesktop(Desktop* desktop) {
+ Revert();
+
+ rtc::scoped_ptr<Desktop> scoped_desktop(desktop);
+
+ if (initial_->IsSame(*desktop))
+ return true;
+
+ if (!desktop->SetThreadDesktop())
+ return false;
+
+ assigned_.reset(scoped_desktop.release());
+ return true;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h
new file mode 100644
index 0000000000..df8652ac9d
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h
@@ -0,0 +1,53 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_
+
+#include <windows.h>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace webrtc {
+
+class Desktop;
+
+class ScopedThreadDesktop {
+ public:
+ ScopedThreadDesktop();
+ ~ScopedThreadDesktop();
+
+ // Returns true if |desktop| has the same desktop name as the currently
+ // assigned desktop (if assigned) or as the initial desktop (if not assigned).
+ // Returns false in any other case including failing Win32 APIs and
+ // uninitialized desktop handles.
+ bool IsSame(const Desktop& desktop);
+
+ // Reverts the calling thread to use the initial desktop.
+ void Revert();
+
+ // Assigns |desktop| to be the calling thread. Returns true if the thread has
+ // been switched to |desktop| successfully. Takes ownership of |desktop|.
+ bool SetThreadDesktop(Desktop* desktop);
+
+ private:
+ // The desktop handle assigned to the calling thread by Set
+ rtc::scoped_ptr<Desktop> assigned_;
+
+ // The desktop handle assigned to the calling thread at creation.
+ rtc::scoped_ptr<Desktop> initial_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScopedThreadDesktop);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_
diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.cc b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc
new file mode 100644
index 0000000000..1b33545277
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc
@@ -0,0 +1,92 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
+
+#include <assert.h>
+#include <windows.h>
+
+namespace webrtc {
+
+bool GetScreenList(ScreenCapturer::ScreenList* screens) {
+ assert(screens->size() == 0);
+
+ BOOL enum_result = TRUE;
+ for (int device_index = 0;; ++device_index) {
+ DISPLAY_DEVICE device;
+ device.cb = sizeof(device);
+ enum_result = EnumDisplayDevices(NULL, device_index, &device, 0);
+
+ // |enum_result| is 0 if we have enumerated all devices.
+ if (!enum_result)
+ break;
+
+ // We only care about active displays.
+ if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE))
+ continue;
+
+ ScreenCapturer::Screen screen;
+ screen.id = device_index;
+ screens->push_back(screen);
+ }
+ return true;
+}
+
+bool IsScreenValid(ScreenId screen, std::wstring* device_key) {
+ if (screen == kFullDesktopScreenId) {
+ *device_key = L"";
+ return true;
+ }
+
+ DISPLAY_DEVICE device;
+ device.cb = sizeof(device);
+ BOOL enum_result = EnumDisplayDevices(NULL, screen, &device, 0);
+ if (enum_result)
+ *device_key = device.DeviceKey;
+
+ return !!enum_result;
+}
+
+DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key) {
+ if (screen == kFullDesktopScreenId) {
+ return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN),
+ GetSystemMetrics(SM_YVIRTUALSCREEN),
+ GetSystemMetrics(SM_CXVIRTUALSCREEN),
+ GetSystemMetrics(SM_CYVIRTUALSCREEN));
+ }
+
+ DISPLAY_DEVICE device;
+ device.cb = sizeof(device);
+ BOOL result = EnumDisplayDevices(NULL, screen, &device, 0);
+ if (!result)
+ return DesktopRect();
+
+ // Verifies the device index still maps to the same display device, to make
+ // sure we are capturing the same device when devices are added or removed.
+ // DeviceKey is documented as reserved, but it actually contains the registry
+ // key for the device and is unique for each monitor, while DeviceID is not.
+ if (device_key != device.DeviceKey)
+ return DesktopRect();
+
+ DEVMODE device_mode;
+ device_mode.dmSize = sizeof(device_mode);
+ device_mode.dmDriverExtra = 0;
+ result = EnumDisplaySettingsEx(
+ device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0);
+ if (!result)
+ return DesktopRect();
+
+ return DesktopRect::MakeXYWH(device_mode.dmPosition.x,
+ device_mode.dmPosition.y,
+ device_mode.dmPelsWidth,
+ device_mode.dmPelsHeight);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.h b/webrtc/modules/desktop_capture/win/screen_capture_utils.h
new file mode 100644
index 0000000000..42473e047b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.h
@@ -0,0 +1,35 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
+
+#include "webrtc/modules/desktop_capture/screen_capturer.h"
+
+namespace webrtc {
+
+// Output the list of active screens into |screens|. Returns true if succeeded,
+// or false if it fails to enumerate the display devices.
+bool GetScreenList(ScreenCapturer::ScreenList* screens);
+
+// Returns true if |screen| is a valid screen. The screen device key is
+// returned through |device_key| if the screen is valid. The device key can be
+// used in GetScreenRect to verify the screen matches the previously obtained
+// id.
+bool IsScreenValid(ScreenId screen, std::wstring* device_key);
+
+// Get the rect of the screen identified by |screen|, relative to the primary
+// display's top-left. If the screen device key does not match |device_key|, or
+// the screen does not exist, or any error happens, an empty rect is returned.
+DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key);
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_
diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc
new file mode 100644
index 0000000000..3cf64879f9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc
@@ -0,0 +1,270 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h"
+
+#include <assert.h>
+
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/modules/desktop_capture/differ.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/win/cursor.h"
+#include "webrtc/modules/desktop_capture/win/desktop.h"
+#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+
+namespace {
+
+// Constants from dwmapi.h.
+const UINT DWM_EC_DISABLECOMPOSITION = 0;
+const UINT DWM_EC_ENABLECOMPOSITION = 1;
+
+const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
+
+} // namespace
+
+ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options)
+ : callback_(NULL),
+ current_screen_id_(kFullDesktopScreenId),
+ desktop_dc_(NULL),
+ memory_dc_(NULL),
+ dwmapi_library_(NULL),
+ composition_func_(NULL),
+ set_thread_execution_state_failed_(false) {
+ if (options.disable_effects()) {
+ // Load dwmapi.dll dynamically since it is not available on XP.
+ if (!dwmapi_library_)
+ dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
+
+ if (dwmapi_library_) {
+ composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
+ GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
+ }
+ }
+}
+
+ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
+ if (desktop_dc_)
+ ReleaseDC(NULL, desktop_dc_);
+ if (memory_dc_)
+ DeleteDC(memory_dc_);
+
+ // Restore Aero.
+ if (composition_func_)
+ (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
+
+ if (dwmapi_library_)
+ FreeLibrary(dwmapi_library_);
+}
+
+void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) {
+ TickTime capture_start_time = TickTime::Now();
+
+ queue_.MoveToNextFrame();
+
+ // Request that the system not power-down the system, or the display hardware.
+ if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
+ if (!set_thread_execution_state_failed_) {
+ set_thread_execution_state_failed_ = true;
+ LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
+ << GetLastError();
+ }
+ }
+
+ // Make sure the GDI capture resources are up-to-date.
+ PrepareCaptureResources();
+
+ if (!CaptureImage()) {
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ const DesktopFrame* current_frame = queue_.current_frame();
+ const DesktopFrame* last_frame = queue_.previous_frame();
+ if (last_frame && last_frame->size().equals(current_frame->size())) {
+ // Make sure the differencer is set up correctly for these previous and
+ // current screens.
+ if (!differ_.get() ||
+ (differ_->width() != current_frame->size().width()) ||
+ (differ_->height() != current_frame->size().height()) ||
+ (differ_->bytes_per_row() != current_frame->stride())) {
+ differ_.reset(new Differ(current_frame->size().width(),
+ current_frame->size().height(),
+ DesktopFrame::kBytesPerPixel,
+ current_frame->stride()));
+ }
+
+ // Calculate difference between the two last captured frames.
+ DesktopRegion region;
+ differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
+ &region);
+ helper_.InvalidateRegion(region);
+ } else {
+ // No previous frame is available, or the screen is resized. Invalidate the
+ // whole screen.
+ helper_.InvalidateScreen(current_frame->size());
+ }
+
+ helper_.set_size_most_recent(current_frame->size());
+
+ // Emit the current frame.
+ DesktopFrame* frame = queue_.current_frame()->Share();
+ frame->set_dpi(DesktopVector(
+ GetDeviceCaps(desktop_dc_, LOGPIXELSX),
+ GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
+ frame->mutable_updated_region()->Clear();
+ helper_.TakeInvalidRegion(frame->mutable_updated_region());
+ frame->set_capture_time_ms(
+ (TickTime::Now() - capture_start_time).Milliseconds());
+ callback_->OnCaptureCompleted(frame);
+}
+
+bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) {
+ return webrtc::GetScreenList(screens);
+}
+
+bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) {
+ bool valid = IsScreenValid(id, &current_device_key_);
+ if (valid)
+ current_screen_id_ = id;
+ return valid;
+}
+
+void ScreenCapturerWinGdi::Start(Callback* callback) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+
+ // Vote to disable Aero composited desktop effects while capturing. Windows
+ // will restore Aero automatically if the process exits. This has no effect
+ // under Windows 8 or higher. See crbug.com/124018.
+ if (composition_func_)
+ (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
+}
+
+void ScreenCapturerWinGdi::PrepareCaptureResources() {
+ // Switch to the desktop receiving user input if different from the current
+ // one.
+ rtc::scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
+ if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
+ // Release GDI resources otherwise SetThreadDesktop will fail.
+ if (desktop_dc_) {
+ ReleaseDC(NULL, desktop_dc_);
+ desktop_dc_ = NULL;
+ }
+
+ if (memory_dc_) {
+ DeleteDC(memory_dc_);
+ memory_dc_ = NULL;
+ }
+
+ // If SetThreadDesktop() fails, the thread is still assigned a desktop.
+ // So we can continue capture screen bits, just from the wrong desktop.
+ desktop_.SetThreadDesktop(input_desktop.release());
+
+ // Re-assert our vote to disable Aero.
+ // See crbug.com/124018 and crbug.com/129906.
+ if (composition_func_ != NULL) {
+ (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
+ }
+ }
+
+ // If the display bounds have changed then recreate GDI resources.
+ // TODO(wez): Also check for pixel format changes.
+ DesktopRect screen_rect(DesktopRect::MakeXYWH(
+ GetSystemMetrics(SM_XVIRTUALSCREEN),
+ GetSystemMetrics(SM_YVIRTUALSCREEN),
+ GetSystemMetrics(SM_CXVIRTUALSCREEN),
+ GetSystemMetrics(SM_CYVIRTUALSCREEN)));
+ if (!screen_rect.equals(desktop_dc_rect_)) {
+ if (desktop_dc_) {
+ ReleaseDC(NULL, desktop_dc_);
+ desktop_dc_ = NULL;
+ }
+ if (memory_dc_) {
+ DeleteDC(memory_dc_);
+ memory_dc_ = NULL;
+ }
+ desktop_dc_rect_ = DesktopRect();
+ }
+
+ if (desktop_dc_ == NULL) {
+ assert(memory_dc_ == NULL);
+
+ // Create GDI device contexts to capture from the desktop into memory.
+ desktop_dc_ = GetDC(NULL);
+ if (!desktop_dc_)
+ abort();
+ memory_dc_ = CreateCompatibleDC(desktop_dc_);
+ if (!memory_dc_)
+ abort();
+
+ desktop_dc_rect_ = screen_rect;
+
+ // Make sure the frame buffers will be reallocated.
+ queue_.Reset();
+
+ helper_.ClearInvalidRegion();
+ }
+}
+
+bool ScreenCapturerWinGdi::CaptureImage() {
+ DesktopRect screen_rect =
+ GetScreenRect(current_screen_id_, current_device_key_);
+ if (screen_rect.is_empty())
+ return false;
+
+ DesktopSize size = screen_rect.size();
+ // 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_.current_frame()->size().equals(screen_rect.size())) {
+ assert(desktop_dc_ != NULL);
+ assert(memory_dc_ != NULL);
+
+ size_t buffer_size = size.width() * size.height() *
+ DesktopFrame::kBytesPerPixel;
+ SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size);
+
+ rtc::scoped_ptr<DesktopFrame> buffer(
+ DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
+ if (!buffer.get())
+ return false;
+ queue_.ReplaceCurrentFrame(buffer.release());
+ }
+
+ // Select the target bitmap into the memory dc and copy the rect from desktop
+ // to memory.
+ DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
+ queue_.current_frame()->GetUnderlyingFrame());
+ HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
+ if (previous_object != NULL) {
+ BitBlt(memory_dc_,
+ 0, 0, screen_rect.width(), screen_rect.height(),
+ desktop_dc_,
+ screen_rect.left(), screen_rect.top(),
+ SRCCOPY | CAPTUREBLT);
+
+ // Select back the previously selected object to that the device contect
+ // could be destroyed independently of the bitmap if needed.
+ SelectObject(memory_dc_, previous_object);
+ }
+ return true;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h
new file mode 100644
index 0000000000..202b9aaa87
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h
@@ -0,0 +1,89 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_
+
+#include "webrtc/modules/desktop_capture/screen_capturer.h"
+
+#include <windows.h>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
+#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
+#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
+
+namespace webrtc {
+
+class Differ;
+
+// ScreenCapturerWinGdi captures 32bit RGB using GDI.
+//
+// ScreenCapturerWinGdi is double-buffered as required by ScreenCapturer.
+class ScreenCapturerWinGdi : public ScreenCapturer {
+ public:
+ explicit ScreenCapturerWinGdi(const DesktopCaptureOptions& options);
+ virtual ~ScreenCapturerWinGdi();
+
+ // Overridden from ScreenCapturer:
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+ bool GetScreenList(ScreenList* screens) override;
+ bool SelectScreen(ScreenId id) override;
+
+ private:
+ typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
+
+ // Make sure that the device contexts match the screen configuration.
+ void PrepareCaptureResources();
+
+ // Captures the current screen contents into the current buffer. Returns true
+ // if succeeded.
+ bool CaptureImage();
+
+ // Capture the current cursor shape.
+ void CaptureCursor();
+
+ Callback* callback_;
+ ScreenId current_screen_id_;
+ std::wstring current_device_key_;
+
+ // A thread-safe list of invalid rectangles, and the size of the most
+ // recently captured screen.
+ ScreenCapturerHelper helper_;
+
+ ScopedThreadDesktop desktop_;
+
+ // GDI resources used for screen capture.
+ HDC desktop_dc_;
+ HDC memory_dc_;
+
+ // Queue of the frames buffers.
+ ScreenCaptureFrameQueue queue_;
+
+ // Rectangle describing the bounds of the desktop device context, relative to
+ // the primary display's top-left.
+ DesktopRect desktop_dc_rect_;
+
+ // Class to calculate the difference between two screen bitmaps.
+ rtc::scoped_ptr<Differ> differ_;
+
+ HMODULE dwmapi_library_;
+ DwmEnableCompositionFunc composition_func_;
+
+ // Used to suppress duplicate logging of SetThreadExecutionState errors.
+ bool set_thread_execution_state_failed_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinGdi);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_
diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
new file mode 100644
index 0000000000..db40478023
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
@@ -0,0 +1,449 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h"
+
+#include <assert.h>
+
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/modules/desktop_capture/differ.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/win/cursor.h"
+#include "webrtc/modules/desktop_capture/win/desktop.h"
+#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+
+// kMagnifierWindowClass has to be "Magnifier" according to the Magnification
+// API. The other strings can be anything.
+static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost";
+static LPCTSTR kHostWindowName = L"MagnifierHost";
+static LPCTSTR kMagnifierWindowClass = L"Magnifier";
+static LPCTSTR kMagnifierWindowName = L"MagnifierWindow";
+
+Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES);
+
+ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier(
+ rtc::scoped_ptr<ScreenCapturer> fallback_capturer)
+ : fallback_capturer_(fallback_capturer.Pass()),
+ fallback_capturer_started_(false),
+ callback_(NULL),
+ current_screen_id_(kFullDesktopScreenId),
+ excluded_window_(NULL),
+ set_thread_execution_state_failed_(false),
+ desktop_dc_(NULL),
+ mag_lib_handle_(NULL),
+ mag_initialize_func_(NULL),
+ mag_uninitialize_func_(NULL),
+ set_window_source_func_(NULL),
+ set_window_filter_list_func_(NULL),
+ set_image_scaling_callback_func_(NULL),
+ host_window_(NULL),
+ magnifier_window_(NULL),
+ magnifier_initialized_(false),
+ magnifier_capture_succeeded_(true) {
+}
+
+ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() {
+ // DestroyWindow must be called before MagUninitialize. magnifier_window_ is
+ // destroyed automatically when host_window_ is destroyed.
+ if (host_window_)
+ DestroyWindow(host_window_);
+
+ if (magnifier_initialized_)
+ mag_uninitialize_func_();
+
+ if (mag_lib_handle_)
+ FreeLibrary(mag_lib_handle_);
+
+ if (desktop_dc_)
+ ReleaseDC(NULL, desktop_dc_);
+}
+
+void ScreenCapturerWinMagnifier::Start(Callback* callback) {
+ assert(!callback_);
+ assert(callback);
+ callback_ = callback;
+
+ InitializeMagnifier();
+}
+
+void ScreenCapturerWinMagnifier::Capture(const DesktopRegion& region) {
+ TickTime capture_start_time = TickTime::Now();
+
+ queue_.MoveToNextFrame();
+
+ // Request that the system not power-down the system, or the display hardware.
+ if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
+ if (!set_thread_execution_state_failed_) {
+ set_thread_execution_state_failed_ = true;
+ LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
+ << GetLastError();
+ }
+ }
+ // Switch to the desktop receiving user input if different from the current
+ // one.
+ rtc::scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
+ if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
+ // Release GDI resources otherwise SetThreadDesktop will fail.
+ if (desktop_dc_) {
+ ReleaseDC(NULL, desktop_dc_);
+ desktop_dc_ = NULL;
+ }
+ // If SetThreadDesktop() fails, the thread is still assigned a desktop.
+ // So we can continue capture screen bits, just from the wrong desktop.
+ desktop_.SetThreadDesktop(input_desktop.release());
+ }
+
+ bool succeeded = false;
+
+ // Do not try to use the magnifier if it failed before and in multi-screen
+ // setup (where the API crashes sometimes).
+ if (magnifier_initialized_ && (GetSystemMetrics(SM_CMONITORS) == 1) &&
+ magnifier_capture_succeeded_) {
+ DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
+ CreateCurrentFrameIfNecessary(rect.size());
+
+ // CaptureImage may fail in some situations, e.g. windows8 metro mode.
+ succeeded = CaptureImage(rect);
+ }
+
+ // Defer to the fallback capturer if magnifier capturer did not work.
+ if (!succeeded) {
+ LOG_F(LS_WARNING) << "Switching to the fallback screen capturer.";
+ StartFallbackCapturer();
+ fallback_capturer_->Capture(region);
+ return;
+ }
+
+ const DesktopFrame* current_frame = queue_.current_frame();
+ const DesktopFrame* last_frame = queue_.previous_frame();
+ if (last_frame && last_frame->size().equals(current_frame->size())) {
+ // Make sure the differencer is set up correctly for these previous and
+ // current screens.
+ if (!differ_.get() || (differ_->width() != current_frame->size().width()) ||
+ (differ_->height() != current_frame->size().height()) ||
+ (differ_->bytes_per_row() != current_frame->stride())) {
+ differ_.reset(new Differ(current_frame->size().width(),
+ current_frame->size().height(),
+ DesktopFrame::kBytesPerPixel,
+ current_frame->stride()));
+ }
+
+ // Calculate difference between the two last captured frames.
+ DesktopRegion region;
+ differ_->CalcDirtyRegion(
+ last_frame->data(), current_frame->data(), &region);
+ helper_.InvalidateRegion(region);
+ } else {
+ // No previous frame is available, or the screen is resized. Invalidate the
+ // whole screen.
+ helper_.InvalidateScreen(current_frame->size());
+ }
+
+ helper_.set_size_most_recent(current_frame->size());
+
+ // Emit the current frame.
+ DesktopFrame* frame = queue_.current_frame()->Share();
+ frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
+ GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
+ frame->mutable_updated_region()->Clear();
+ helper_.TakeInvalidRegion(frame->mutable_updated_region());
+ frame->set_capture_time_ms(
+ (TickTime::Now() - capture_start_time).Milliseconds());
+ callback_->OnCaptureCompleted(frame);
+}
+
+bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) {
+ return webrtc::GetScreenList(screens);
+}
+
+bool ScreenCapturerWinMagnifier::SelectScreen(ScreenId id) {
+ bool valid = IsScreenValid(id, &current_device_key_);
+
+ // Set current_screen_id_ even if the fallback capturer is being used, so we
+ // can switch back to the magnifier when possible.
+ if (valid)
+ current_screen_id_ = id;
+
+ if (fallback_capturer_started_)
+ fallback_capturer_->SelectScreen(id);
+
+ return valid;
+}
+
+void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) {
+ excluded_window_ = (HWND)excluded_window;
+ if (excluded_window_ && magnifier_initialized_) {
+ set_window_filter_list_func_(
+ magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
+ }
+}
+
+bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) {
+ assert(magnifier_initialized_);
+
+ // Set the magnifier control to cover the captured rect. The content of the
+ // magnifier control will be the captured image.
+ BOOL result = SetWindowPos(magnifier_window_,
+ NULL,
+ rect.left(), rect.top(),
+ rect.width(), rect.height(),
+ 0);
+ if (!result) {
+ LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError()
+ << ". Rect = {" << rect.left() << ", " << rect.top()
+ << ", " << rect.right() << ", " << rect.bottom() << "}";
+ return false;
+ }
+
+ magnifier_capture_succeeded_ = false;
+
+ RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()};
+
+ // OnCaptured will be called via OnMagImageScalingCallback and fill in the
+ // frame before set_window_source_func_ returns.
+ result = set_window_source_func_(magnifier_window_, native_rect);
+
+ if (!result) {
+ LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: " << GetLastError()
+ << ". Rect = {" << rect.left() << ", " << rect.top()
+ << ", " << rect.right() << ", " << rect.bottom() << "}";
+ return false;
+ }
+
+ return magnifier_capture_succeeded_;
+}
+
+BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback(
+ HWND hwnd,
+ void* srcdata,
+ MAGIMAGEHEADER srcheader,
+ void* destdata,
+ MAGIMAGEHEADER destheader,
+ RECT unclipped,
+ RECT clipped,
+ HRGN dirty) {
+ assert(tls_index_.Value() != TLS_OUT_OF_INDEXES);
+
+ ScreenCapturerWinMagnifier* owner =
+ reinterpret_cast<ScreenCapturerWinMagnifier*>(
+ TlsGetValue(tls_index_.Value()));
+
+ owner->OnCaptured(srcdata, srcheader);
+
+ return TRUE;
+}
+
+bool ScreenCapturerWinMagnifier::InitializeMagnifier() {
+ assert(!magnifier_initialized_);
+
+ desktop_dc_ = GetDC(NULL);
+
+ mag_lib_handle_ = LoadLibrary(L"Magnification.dll");
+ if (!mag_lib_handle_)
+ return false;
+
+ // Initialize Magnification API function pointers.
+ mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>(
+ GetProcAddress(mag_lib_handle_, "MagInitialize"));
+ mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>(
+ GetProcAddress(mag_lib_handle_, "MagUninitialize"));
+ set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>(
+ GetProcAddress(mag_lib_handle_, "MagSetWindowSource"));
+ set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>(
+ GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList"));
+ set_image_scaling_callback_func_ =
+ reinterpret_cast<MagSetImageScalingCallbackFunc>(
+ GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback"));
+
+ if (!mag_initialize_func_ || !mag_uninitialize_func_ ||
+ !set_window_source_func_ || !set_window_filter_list_func_ ||
+ !set_image_scaling_callback_func_) {
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "library functions missing.";
+ return false;
+ }
+
+ BOOL result = mag_initialize_func_();
+ if (!result) {
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "error from MagInitialize " << GetLastError();
+ return false;
+ }
+
+ HMODULE hInstance = NULL;
+ result = GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<char*>(&DefWindowProc),
+ &hInstance);
+ if (!result) {
+ mag_uninitialize_func_();
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "error from GetModulehandleExA " << GetLastError();
+ return false;
+ }
+
+ // Register the host window class. See the MSDN documentation of the
+ // Magnification API for more infomation.
+ WNDCLASSEX wcex = {};
+ wcex.cbSize = sizeof(WNDCLASSEX);
+ wcex.lpfnWndProc = &DefWindowProc;
+ wcex.hInstance = hInstance;
+ wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wcex.lpszClassName = kMagnifierHostClass;
+
+ // Ignore the error which may happen when the class is already registered.
+ RegisterClassEx(&wcex);
+
+ // Create the host window.
+ host_window_ = CreateWindowEx(WS_EX_LAYERED,
+ kMagnifierHostClass,
+ kHostWindowName,
+ 0,
+ 0, 0, 0, 0,
+ NULL,
+ NULL,
+ hInstance,
+ NULL);
+ if (!host_window_) {
+ mag_uninitialize_func_();
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "error from creating host window " << GetLastError();
+ return false;
+ }
+
+ // Create the magnifier control.
+ magnifier_window_ = CreateWindow(kMagnifierWindowClass,
+ kMagnifierWindowName,
+ WS_CHILD | WS_VISIBLE,
+ 0, 0, 0, 0,
+ host_window_,
+ NULL,
+ hInstance,
+ NULL);
+ if (!magnifier_window_) {
+ mag_uninitialize_func_();
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "error from creating magnifier window "
+ << GetLastError();
+ return false;
+ }
+
+ // Hide the host window.
+ ShowWindow(host_window_, SW_HIDE);
+
+ // Set the scaling callback to receive captured image.
+ result = set_image_scaling_callback_func_(
+ magnifier_window_,
+ &ScreenCapturerWinMagnifier::OnMagImageScalingCallback);
+ if (!result) {
+ mag_uninitialize_func_();
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "error from MagSetImageScalingCallback "
+ << GetLastError();
+ return false;
+ }
+
+ if (excluded_window_) {
+ result = set_window_filter_list_func_(
+ magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
+ if (!result) {
+ mag_uninitialize_func_();
+ LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
+ << "error from MagSetWindowFilterList "
+ << GetLastError();
+ return false;
+ }
+ }
+
+ if (tls_index_.Value() == TLS_OUT_OF_INDEXES) {
+ // More than one threads may get here at the same time, but only one will
+ // write to tls_index_ using CompareExchange.
+ DWORD new_tls_index = TlsAlloc();
+ if (!tls_index_.CompareExchange(new_tls_index, TLS_OUT_OF_INDEXES))
+ TlsFree(new_tls_index);
+ }
+
+ assert(tls_index_.Value() != TLS_OUT_OF_INDEXES);
+ TlsSetValue(tls_index_.Value(), this);
+
+ magnifier_initialized_ = true;
+ return true;
+}
+
+void ScreenCapturerWinMagnifier::OnCaptured(void* data,
+ const MAGIMAGEHEADER& header) {
+ DesktopFrame* current_frame = queue_.current_frame();
+
+ // Verify the format.
+ // TODO(jiayl): support capturing sources with pixel formats other than RGBA.
+ int captured_bytes_per_pixel = header.cbSize / header.width / header.height;
+ if (header.format != GUID_WICPixelFormat32bppRGBA ||
+ header.width != static_cast<UINT>(current_frame->size().width()) ||
+ header.height != static_cast<UINT>(current_frame->size().height()) ||
+ header.stride != static_cast<UINT>(current_frame->stride()) ||
+ captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) {
+ LOG_F(LS_WARNING) << "Output format does not match the captured format: "
+ << "width = " << header.width << ", "
+ << "height = " << header.height << ", "
+ << "stride = " << header.stride << ", "
+ << "bpp = " << captured_bytes_per_pixel << ", "
+ << "pixel format RGBA ? "
+ << (header.format == GUID_WICPixelFormat32bppRGBA) << ".";
+ return;
+ }
+
+ // Copy the data into the frame.
+ current_frame->CopyPixelsFrom(
+ reinterpret_cast<uint8_t*>(data),
+ header.stride,
+ DesktopRect::MakeXYWH(0, 0, header.width, header.height));
+
+ magnifier_capture_succeeded_ = true;
+}
+
+void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary(
+ const DesktopSize& size) {
+ // 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_.current_frame()->size().equals(size)) {
+ size_t buffer_size =
+ size.width() * size.height() * DesktopFrame::kBytesPerPixel;
+ SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size);
+
+ rtc::scoped_ptr<DesktopFrame> buffer;
+ if (shared_memory) {
+ buffer.reset(new SharedMemoryDesktopFrame(
+ size, size.width() * DesktopFrame::kBytesPerPixel, shared_memory));
+ } else {
+ buffer.reset(new BasicDesktopFrame(size));
+ }
+ queue_.ReplaceCurrentFrame(buffer.release());
+ }
+}
+
+void ScreenCapturerWinMagnifier::StartFallbackCapturer() {
+ assert(fallback_capturer_);
+ if (!fallback_capturer_started_) {
+ fallback_capturer_started_ = true;
+
+ fallback_capturer_->Start(callback_);
+ fallback_capturer_->SelectScreen(current_screen_id_);
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h
new file mode 100644
index 0000000000..f084e25442
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h
@@ -0,0 +1,153 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_
+
+#include <windows.h>
+#include <magnification.h>
+#include <wincodec.h>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
+#include "webrtc/modules/desktop_capture/screen_capturer.h"
+#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
+#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
+#include "webrtc/system_wrappers/include/atomic32.h"
+
+namespace webrtc {
+
+class DesktopFrame;
+class DesktopRect;
+class Differ;
+
+// Captures the screen using the Magnification API to support window exclusion.
+// Each capturer must run on a dedicated thread because it uses thread local
+// storage for redirecting the library callback. Also the thread must have a UI
+// message loop to handle the window messages for the magnifier window.
+class ScreenCapturerWinMagnifier : public ScreenCapturer {
+ public:
+ // |fallback_capturer| will be used to capture the screen if a non-primary
+ // screen is being captured, or the OS does not support Magnification API, or
+ // the magnifier capturer fails (e.g. in Windows8 Metro mode).
+ explicit ScreenCapturerWinMagnifier(
+ rtc::scoped_ptr<ScreenCapturer> fallback_capturer);
+ virtual ~ScreenCapturerWinMagnifier();
+
+ // Overridden from ScreenCapturer:
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+ bool GetScreenList(ScreenList* screens) override;
+ bool SelectScreen(ScreenId id) override;
+ void SetExcludedWindow(WindowId window) override;
+
+ private:
+ typedef BOOL(WINAPI* MagImageScalingCallback)(HWND hwnd,
+ void* srcdata,
+ MAGIMAGEHEADER srcheader,
+ void* destdata,
+ MAGIMAGEHEADER destheader,
+ RECT unclipped,
+ RECT clipped,
+ HRGN dirty);
+ typedef BOOL(WINAPI* MagInitializeFunc)(void);
+ typedef BOOL(WINAPI* MagUninitializeFunc)(void);
+ typedef BOOL(WINAPI* MagSetWindowSourceFunc)(HWND hwnd, RECT rect);
+ typedef BOOL(WINAPI* MagSetWindowFilterListFunc)(HWND hwnd,
+ DWORD dwFilterMode,
+ int count,
+ HWND* pHWND);
+ typedef BOOL(WINAPI* MagSetImageScalingCallbackFunc)(
+ HWND hwnd,
+ MagImageScalingCallback callback);
+
+ static BOOL WINAPI OnMagImageScalingCallback(HWND hwnd,
+ void* srcdata,
+ MAGIMAGEHEADER srcheader,
+ void* destdata,
+ MAGIMAGEHEADER destheader,
+ RECT unclipped,
+ RECT clipped,
+ HRGN dirty);
+
+ // Captures the screen within |rect| in the desktop coordinates. Returns true
+ // if succeeded.
+ // It can only capture the primary screen for now. The magnification library
+ // crashes under some screen configurations (e.g. secondary screen on top of
+ // primary screen) if it tries to capture a non-primary screen. The caller
+ // must make sure not calling it on non-primary screens.
+ bool CaptureImage(const DesktopRect& rect);
+
+ // Helper method for setting up the magnifier control. Returns true if
+ // succeeded.
+ bool InitializeMagnifier();
+
+ // Called by OnMagImageScalingCallback to output captured data.
+ void OnCaptured(void* data, const MAGIMAGEHEADER& header);
+
+ // Makes sure the current frame exists and matches |size|.
+ void CreateCurrentFrameIfNecessary(const DesktopSize& size);
+
+ // Start the fallback capturer and select the screen.
+ void StartFallbackCapturer();
+
+ static Atomic32 tls_index_;
+
+ rtc::scoped_ptr<ScreenCapturer> fallback_capturer_;
+ bool fallback_capturer_started_;
+ Callback* callback_;
+ ScreenId current_screen_id_;
+ std::wstring current_device_key_;
+ HWND excluded_window_;
+
+ // A thread-safe list of invalid rectangles, and the size of the most
+ // recently captured screen.
+ ScreenCapturerHelper helper_;
+
+ // Queue of the frames buffers.
+ ScreenCaptureFrameQueue queue_;
+
+ // Class to calculate the difference between two screen bitmaps.
+ rtc::scoped_ptr<Differ> differ_;
+
+ // Used to suppress duplicate logging of SetThreadExecutionState errors.
+ bool set_thread_execution_state_failed_;
+
+ ScopedThreadDesktop desktop_;
+
+ // Used for getting the screen dpi.
+ HDC desktop_dc_;
+
+ HMODULE mag_lib_handle_;
+ MagInitializeFunc mag_initialize_func_;
+ MagUninitializeFunc mag_uninitialize_func_;
+ MagSetWindowSourceFunc set_window_source_func_;
+ MagSetWindowFilterListFunc set_window_filter_list_func_;
+ MagSetImageScalingCallbackFunc set_image_scaling_callback_func_;
+
+ // The hidden window hosting the magnifier control.
+ HWND host_window_;
+ // The magnifier control that captures the screen.
+ HWND magnifier_window_;
+
+ // True if the magnifier control has been successfully initialized.
+ bool magnifier_initialized_;
+
+ // True if the last OnMagImageScalingCallback was called and handled
+ // successfully. Reset at the beginning of each CaptureImage call.
+ bool magnifier_capture_succeeded_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinMagnifier);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_
diff --git a/webrtc/modules/desktop_capture/win/window_capture_utils.cc b/webrtc/modules/desktop_capture/win/window_capture_utils.cc
new file mode 100644
index 0000000000..83922ea7f8
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/window_capture_utils.cc
@@ -0,0 +1,69 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/win/window_capture_utils.h"
+
+namespace webrtc {
+
+bool
+GetCroppedWindowRect(HWND window,
+ DesktopRect* cropped_rect,
+ DesktopRect* original_rect) {
+ RECT rect;
+ if (!GetWindowRect(window, &rect)) {
+ return false;
+ }
+ WINDOWPLACEMENT window_placement;
+ window_placement.length = sizeof(window_placement);
+ if (!GetWindowPlacement(window, &window_placement)) {
+ return false;
+ }
+
+ *original_rect = DesktopRect::MakeLTRB(
+ rect.left, rect.top, rect.right, rect.bottom);
+
+ if (window_placement.showCmd == SW_SHOWMAXIMIZED) {
+ DesktopSize border = DesktopSize(GetSystemMetrics(SM_CXSIZEFRAME),
+ GetSystemMetrics(SM_CYSIZEFRAME));
+ *cropped_rect = DesktopRect::MakeLTRB(
+ rect.left + border.width(),
+ rect.top,
+ rect.right - border.width(),
+ rect.bottom - border.height());
+ } else {
+ *cropped_rect = *original_rect;
+ }
+ return true;
+}
+
+AeroChecker::AeroChecker() : dwmapi_library_(nullptr), func_(nullptr) {
+ // Try to load dwmapi.dll dynamically since it is not available on XP.
+ dwmapi_library_ = LoadLibrary(L"dwmapi.dll");
+ if (dwmapi_library_) {
+ func_ = reinterpret_cast<DwmIsCompositionEnabledFunc>(
+ GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled"));
+ }
+}
+
+AeroChecker::~AeroChecker() {
+ if (dwmapi_library_) {
+ FreeLibrary(dwmapi_library_);
+ }
+}
+
+bool AeroChecker::IsAeroEnabled() {
+ BOOL result = FALSE;
+ if (func_) {
+ func_(&result);
+ }
+ return result != FALSE;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/win/window_capture_utils.h b/webrtc/modules/desktop_capture/win/window_capture_utils.h
new file mode 100644
index 0000000000..7c80490f60
--- /dev/null
+++ b/webrtc/modules/desktop_capture/win/window_capture_utils.h
@@ -0,0 +1,40 @@
+/*
+ * 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 <windows.h>
+
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+
+namespace webrtc {
+
+// Output the window rect, with the left/right/bottom frame border cropped if
+// the window is maximized. |cropped_rect| is the cropped rect relative to the
+// desktop. |original_rect| is the original rect returned from GetWindowRect.
+// Returns true if all API calls succeeded.
+bool GetCroppedWindowRect(HWND window,
+ DesktopRect* cropped_rect,
+ DesktopRect* original_rect);
+
+typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
+class AeroChecker {
+ public:
+ AeroChecker();
+ ~AeroChecker();
+
+ bool IsAeroEnabled();
+
+ private:
+ HMODULE dwmapi_library_;
+ DwmIsCompositionEnabledFunc func_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(AeroChecker);
+};
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer.cc b/webrtc/modules/desktop_capture/window_capturer.cc
new file mode 100644
index 0000000000..c5176d5e60
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer.cc
@@ -0,0 +1,22 @@
+ /*
+ * 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 "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+
+namespace webrtc {
+
+// static
+WindowCapturer* WindowCapturer::Create() {
+ return Create(DesktopCaptureOptions::CreateDefault());
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer.h b/webrtc/modules/desktop_capture/window_capturer.h
new file mode 100644
index 0000000000..9ba441a8ec
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer.h
@@ -0,0 +1,61 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class DesktopCaptureOptions;
+
+class WindowCapturer : public DesktopCapturer {
+ public:
+ typedef webrtc::WindowId WindowId;
+
+ struct Window {
+ WindowId id;
+
+ // Title of the window in UTF-8 encoding.
+ std::string title;
+ };
+
+ typedef std::vector<Window> WindowList;
+
+ static WindowCapturer* Create(const DesktopCaptureOptions& options);
+
+ // TODO(sergeyu): Remove this method. crbug.com/172183
+ static WindowCapturer* Create();
+
+ virtual ~WindowCapturer() {}
+
+ // Get list of windows. Returns false in case of a failure.
+ virtual bool GetWindowList(WindowList* windows) = 0;
+
+ // Select window to be captured. Returns false in case of a failure (e.g. if
+ // there is no window with the specified id).
+ virtual bool SelectWindow(WindowId id) = 0;
+
+ // Bring the selected window to the front. Returns false in case of a
+ // failure or no window selected.
+ virtual bool BringSelectedWindowToFront() = 0;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_
+
diff --git a/webrtc/modules/desktop_capture/window_capturer_mac.mm b/webrtc/modules/desktop_capture/window_capturer_mac.mm
new file mode 100644
index 0000000000..806fc5c1d4
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_mac.mm
@@ -0,0 +1,241 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include <assert.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <Cocoa/Cocoa.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "webrtc/base/macutils.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
+#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h"
+#include "webrtc/modules/desktop_capture/mac/window_list_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/system_wrappers/include/tick_util.h"
+
+namespace webrtc {
+
+namespace {
+
+// Returns true if the window exists.
+bool IsWindowValid(CGWindowID id) {
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL);
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+ bool valid = window_array && CFArrayGetCount(window_array);
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+
+ return valid;
+}
+
+class WindowCapturerMac : public WindowCapturer {
+ public:
+ explicit WindowCapturerMac(rtc::scoped_refptr<FullScreenChromeWindowDetector>
+ full_screen_chrome_window_detector);
+ virtual ~WindowCapturerMac();
+
+ // WindowCapturer interface.
+ bool GetWindowList(WindowList* windows) override;
+ bool SelectWindow(WindowId id) override;
+ bool BringSelectedWindowToFront() override;
+
+ // DesktopCapturer interface.
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+
+ private:
+ Callback* callback_;
+
+ // The window being captured.
+ CGWindowID window_id_;
+
+ rtc::scoped_refptr<FullScreenChromeWindowDetector>
+ full_screen_chrome_window_detector_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerMac);
+};
+
+WindowCapturerMac::WindowCapturerMac(rtc::scoped_refptr<
+ FullScreenChromeWindowDetector> full_screen_chrome_window_detector)
+ : callback_(NULL),
+ window_id_(0),
+ full_screen_chrome_window_detector_(full_screen_chrome_window_detector) {
+}
+
+WindowCapturerMac::~WindowCapturerMac() {
+}
+
+bool WindowCapturerMac::GetWindowList(WindowList* windows) {
+ // 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 (!rtc::ToUtf8(window_title, &(window.title)) ||
+ window.title.empty()) {
+ continue;
+ }
+ windows->push_back(window);
+ }
+ }
+
+ CFRelease(window_array);
+ return true;
+}
+
+bool WindowCapturerMac::SelectWindow(WindowId id) {
+ if (!IsWindowValid(id))
+ return false;
+ window_id_ = id;
+ return true;
+}
+
+bool WindowCapturerMac::BringSelectedWindowToFront() {
+ if (!window_id_)
+ return false;
+
+ CGWindowID ids[1];
+ ids[0] = window_id_;
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+ if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
+ // Could not find the window. It might have been closed.
+ LOG(LS_INFO) << "Window not found";
+ CFRelease(window_id_array);
+ return false;
+ }
+
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowOwnerPID));
+
+ int pid;
+ CFNumberGetValue(pid_ref, kCFNumberIntType, &pid);
+
+ // TODO(jiayl): this will bring the process main window to the front. We
+ // should find a way to bring only the window to the front.
+ bool result =
+ [[NSRunningApplication runningApplicationWithProcessIdentifier: pid]
+ activateWithOptions: NSApplicationActivateIgnoringOtherApps];
+
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+ return result;
+}
+
+void WindowCapturerMac::Start(Callback* callback) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+}
+
+void WindowCapturerMac::Capture(const DesktopRegion& region) {
+ if (!IsWindowValid(window_id_)) {
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ CGWindowID on_screen_window = window_id_;
+ if (full_screen_chrome_window_detector_) {
+ CGWindowID full_screen_window =
+ full_screen_chrome_window_detector_->FindFullScreenWindow(window_id_);
+
+ if (full_screen_window != kCGNullWindowID)
+ on_screen_window = full_screen_window;
+ }
+
+ CGImageRef window_image = CGWindowListCreateImage(
+ CGRectNull, kCGWindowListOptionIncludingWindow,
+ on_screen_window, kCGWindowImageBoundsIgnoreFraming);
+
+ if (!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);
+ CFDataRef cf_data = CGDataProviderCopyData(provider);
+ DesktopFrame* frame = new BasicDesktopFrame(
+ DesktopSize(width, height));
+
+ int src_stride = CGImageGetBytesPerRow(window_image);
+ const uint8_t* src_data = CFDataGetBytePtr(cf_data);
+ for (int y = 0; y < height; ++y) {
+ memcpy(frame->data() + frame->stride() * y, src_data + src_stride * y,
+ DesktopFrame::kBytesPerPixel * width);
+ }
+
+ CFRelease(cf_data);
+ CFRelease(window_image);
+
+ frame->mutable_updated_region()->SetRect(
+ DesktopRect::MakeSize(frame->size()));
+
+ callback_->OnCaptureCompleted(frame);
+
+ if (full_screen_chrome_window_detector_)
+ full_screen_chrome_window_detector_->UpdateWindowListIfNeeded(window_id_);
+}
+
+} // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
+ return new WindowCapturerMac(options.full_screen_chrome_window_detector());
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer_null.cc b/webrtc/modules/desktop_capture/window_capturer_null.cc
new file mode 100755
index 0000000000..b74f17e39b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_null.cc
@@ -0,0 +1,82 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include <assert.h>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace webrtc {
+
+namespace {
+
+class WindowCapturerNull : public WindowCapturer {
+ public:
+ WindowCapturerNull();
+ virtual ~WindowCapturerNull();
+
+ // WindowCapturer interface.
+ bool GetWindowList(WindowList* windows) override;
+ bool SelectWindow(WindowId id) override;
+ bool BringSelectedWindowToFront() override;
+
+ // DesktopCapturer interface.
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+
+ private:
+ Callback* callback_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerNull);
+};
+
+WindowCapturerNull::WindowCapturerNull()
+ : callback_(NULL) {
+}
+
+WindowCapturerNull::~WindowCapturerNull() {
+}
+
+bool WindowCapturerNull::GetWindowList(WindowList* windows) {
+ // Not implemented yet.
+ return false;
+}
+
+bool WindowCapturerNull::SelectWindow(WindowId id) {
+ // Not implemented yet.
+ return false;
+}
+
+bool WindowCapturerNull::BringSelectedWindowToFront() {
+ // Not implemented yet.
+ return false;
+}
+
+void WindowCapturerNull::Start(Callback* callback) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+}
+
+void WindowCapturerNull::Capture(const DesktopRegion& region) {
+ // Not implemented yet.
+ callback_->OnCaptureCompleted(NULL);
+}
+
+} // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
+ return new WindowCapturerNull();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer_unittest.cc b/webrtc/modules/desktop_capture/window_capturer_unittest.cc
new file mode 100644
index 0000000000..445a4e9848
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_unittest.cc
@@ -0,0 +1,90 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+class WindowCapturerTest : public testing::Test,
+ public DesktopCapturer::Callback {
+ public:
+ void SetUp() override {
+ capturer_.reset(
+ WindowCapturer::Create(DesktopCaptureOptions::CreateDefault()));
+ }
+
+ void TearDown() override {}
+
+ // DesktopCapturer::Callback interface
+ SharedMemory* CreateSharedMemory(size_t size) override { return NULL; }
+
+ void OnCaptureCompleted(DesktopFrame* frame) override { frame_.reset(frame); }
+
+ protected:
+ rtc::scoped_ptr<WindowCapturer> capturer_;
+ rtc::scoped_ptr<DesktopFrame> frame_;
+};
+
+// Verify that we can enumerate windows.
+TEST_F(WindowCapturerTest, Enumerate) {
+ WindowCapturer::WindowList windows;
+ EXPECT_TRUE(capturer_->GetWindowList(&windows));
+
+ // Verify that window titles are set.
+ for (WindowCapturer::WindowList::iterator it = windows.begin();
+ it != windows.end(); ++it) {
+ EXPECT_FALSE(it->title.empty());
+ }
+}
+
+// Verify we can capture a window.
+//
+// TODO(sergeyu): Currently this test just looks at the windows that already
+// exist. Ideally it should create a test window and capture from it, but there
+// is no easy cross-platform way to create new windows (potentially we could
+// have a python script showing Tk dialog, but launching code will differ
+// between platforms).
+TEST_F(WindowCapturerTest, Capture) {
+ WindowCapturer::WindowList windows;
+ capturer_->Start(this);
+ EXPECT_TRUE(capturer_->GetWindowList(&windows));
+
+ // Verify that we can select and capture each window.
+ for (WindowCapturer::WindowList::iterator it = windows.begin();
+ it != windows.end(); ++it) {
+ frame_.reset();
+ if (capturer_->SelectWindow(it->id)) {
+ capturer_->Capture(DesktopRegion());
+ }
+
+ // If we failed to capture a window make sure it no longer exists.
+ if (!frame_.get()) {
+ WindowCapturer::WindowList new_list;
+ EXPECT_TRUE(capturer_->GetWindowList(&new_list));
+ for (WindowCapturer::WindowList::iterator new_list_it = new_list.begin();
+ new_list_it != new_list.end(); ++new_list_it) {
+ EXPECT_FALSE(it->id == new_list_it->id);
+ }
+ continue;
+ }
+
+ EXPECT_GT(frame_->size().width(), 0);
+ EXPECT_GT(frame_->size().height(), 0);
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer_win.cc b/webrtc/modules/desktop_capture/window_capturer_win.cc
new file mode 100644
index 0000000000..c0d71167a5
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_win.cc
@@ -0,0 +1,257 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include <assert.h>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/checks.h"
+#include "webrtc/base/win32.h"
+#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
+#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
+ WindowCapturer::WindowList* list =
+ reinterpret_cast<WindowCapturer::WindowList*>(param);
+
+ // Skip windows that are invisible, minimized, have no title, or are owned,
+ // unless they have the app window style set.
+ int len = GetWindowTextLength(hwnd);
+ HWND owner = GetWindow(hwnd, GW_OWNER);
+ LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
+ if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
+ (owner && !(exstyle & WS_EX_APPWINDOW))) {
+ return TRUE;
+ }
+
+ // Skip the Program Manager window and the Start button.
+ const size_t kClassLength = 256;
+ WCHAR class_name[kClassLength];
+ const int class_name_length = GetClassName(hwnd, class_name, kClassLength);
+ RTC_DCHECK(class_name_length)
+ << "Error retrieving the application's class name";
+
+ // Skip Program Manager window and the Start button. This is the same logic
+ // that's used in Win32WindowPicker in libjingle. Consider filtering other
+ // windows as well (e.g. toolbars).
+ if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0)
+ return TRUE;
+
+ // Windows 8 introduced a "Modern App" identified by their class name being
+ // either ApplicationFrameWindow or windows.UI.Core.coreWindow. The
+ // associated windows cannot be captured, so we skip them.
+ // http://crbug.com/526883.
+ if (rtc::IsWindows8OrLater() &&
+ (wcscmp(class_name, L"ApplicationFrameWindow") == 0 ||
+ wcscmp(class_name, L"Windows.UI.Core.CoreWindow") == 0)) {
+ return TRUE;
+ }
+
+ WindowCapturer::Window window;
+ window.id = reinterpret_cast<WindowCapturer::WindowId>(hwnd);
+
+ const size_t kTitleLength = 500;
+ WCHAR window_title[kTitleLength];
+ // Truncate the title if it's longer than kTitleLength.
+ GetWindowText(hwnd, window_title, kTitleLength);
+ window.title = rtc::ToUtf8(window_title);
+
+ // Skip windows when we failed to convert the title or it is empty.
+ if (window.title.empty())
+ return TRUE;
+
+ list->push_back(window);
+
+ return TRUE;
+}
+
+class WindowCapturerWin : public WindowCapturer {
+ public:
+ WindowCapturerWin();
+ virtual ~WindowCapturerWin();
+
+ // WindowCapturer interface.
+ bool GetWindowList(WindowList* windows) override;
+ bool SelectWindow(WindowId id) override;
+ bool BringSelectedWindowToFront() override;
+
+ // DesktopCapturer interface.
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+
+ private:
+ Callback* callback_;
+
+ // HWND and HDC for the currently selected window or NULL if window is not
+ // selected.
+ HWND window_;
+
+ DesktopSize previous_size_;
+
+ AeroChecker aero_checker_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
+};
+
+WindowCapturerWin::WindowCapturerWin()
+ : callback_(NULL),
+ window_(NULL) {
+}
+
+WindowCapturerWin::~WindowCapturerWin() {
+}
+
+bool WindowCapturerWin::GetWindowList(WindowList* windows) {
+ WindowList result;
+ LPARAM param = reinterpret_cast<LPARAM>(&result);
+ if (!EnumWindows(&WindowsEnumerationHandler, param))
+ return false;
+ windows->swap(result);
+ return true;
+}
+
+bool WindowCapturerWin::SelectWindow(WindowId id) {
+ HWND window = reinterpret_cast<HWND>(id);
+ if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
+ return false;
+ window_ = window;
+ previous_size_.set(0, 0);
+ return true;
+}
+
+bool WindowCapturerWin::BringSelectedWindowToFront() {
+ if (!window_)
+ return false;
+
+ if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_))
+ return false;
+
+ return SetForegroundWindow(window_) != 0;
+}
+
+void WindowCapturerWin::Start(Callback* callback) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+}
+
+void WindowCapturerWin::Capture(const DesktopRegion& region) {
+ if (!window_) {
+ LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ // Stop capturing if the window has been closed or hidden.
+ if (!IsWindow(window_) || !IsWindowVisible(window_)) {
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ // Return a 1x1 black frame if the window is minimized, to match the behavior
+ // on Mac.
+ if (IsIconic(window_)) {
+ BasicDesktopFrame* frame = new BasicDesktopFrame(DesktopSize(1, 1));
+ memset(frame->data(), 0, frame->stride() * frame->size().height());
+
+ previous_size_ = frame->size();
+ callback_->OnCaptureCompleted(frame);
+ return;
+ }
+
+ DesktopRect original_rect;
+ DesktopRect cropped_rect;
+ if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) {
+ LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ HDC window_dc = GetWindowDC(window_);
+ if (!window_dc) {
+ LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ rtc::scoped_ptr<DesktopFrameWin> frame(
+ DesktopFrameWin::Create(cropped_rect.size(), NULL, window_dc));
+ if (!frame.get()) {
+ ReleaseDC(window_, window_dc);
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ HDC mem_dc = CreateCompatibleDC(window_dc);
+ HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
+ BOOL result = FALSE;
+
+ // When desktop composition (Aero) is enabled each window is rendered to a
+ // private buffer allowing BitBlt() to get the window content even if the
+ // window is occluded. PrintWindow() is slower but lets rendering the window
+ // contents to an off-screen device context when Aero is not available.
+ // PrintWindow() is not supported by some applications.
+ //
+ // If Aero is enabled, we prefer BitBlt() because it's faster and avoids
+ // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
+ // render occluding windows on top of the desired window.
+ //
+ // When composition is enabled the DC returned by GetWindowDC() doesn't always
+ // have window frame rendered correctly. Windows renders it only once and then
+ // caches the result between captures. We hack it around by calling
+ // PrintWindow() whenever window size changes, including the first time of
+ // capturing - it somehow affects what we get from BitBlt() on the subsequent
+ // captures.
+
+ if (!aero_checker_.IsAeroEnabled() || !previous_size_.equals(frame->size())) {
+ result = PrintWindow(window_, mem_dc, 0);
+ }
+
+ // Aero is enabled or PrintWindow() failed, use BitBlt.
+ if (!result) {
+ result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
+ window_dc,
+ cropped_rect.left() - original_rect.left(),
+ cropped_rect.top() - original_rect.top(),
+ SRCCOPY);
+ }
+
+ SelectObject(mem_dc, previous_object);
+ DeleteDC(mem_dc);
+ ReleaseDC(window_, window_dc);
+
+ previous_size_ = frame->size();
+
+ frame->mutable_updated_region()->SetRect(
+ DesktopRect::MakeSize(frame->size()));
+
+ if (!result) {
+ LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
+ frame.reset();
+ }
+
+ callback_->OnCaptureCompleted(frame.release());
+}
+
+} // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
+ return new WindowCapturerWin();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer_x11.cc b/webrtc/modules/desktop_capture/window_capturer_x11.cc
new file mode 100755
index 0000000000..f0d2c1284c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_x11.cc
@@ -0,0 +1,436 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include <assert.h>
+#include <string.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/Xutil.h>
+
+#include <algorithm>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
+#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
+#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+// Convenience wrapper for XGetWindowProperty() results.
+template <class PropertyType>
+class XWindowProperty {
+ public:
+ XWindowProperty(Display* display, Window window, Atom property)
+ : is_valid_(false),
+ size_(0),
+ data_(NULL) {
+ const int kBitsPerByte = 8;
+ Atom actual_type;
+ int actual_format;
+ unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
+ int status = XGetWindowProperty(display, window, property, 0L, ~0L, False,
+ AnyPropertyType, &actual_type,
+ &actual_format, &size_,
+ &bytes_after, &data_);
+ if (status != Success) {
+ data_ = NULL;
+ return;
+ }
+ if (sizeof(PropertyType) * kBitsPerByte != actual_format) {
+ size_ = 0;
+ return;
+ }
+
+ is_valid_ = true;
+ }
+
+ ~XWindowProperty() {
+ if (data_)
+ XFree(data_);
+ }
+
+ // True if we got properly value successfully.
+ bool is_valid() const { return is_valid_; }
+
+ // Size and value of the property.
+ size_t size() const { return size_; }
+ const PropertyType* data() const {
+ return reinterpret_cast<PropertyType*>(data_);
+ }
+ PropertyType* data() {
+ return reinterpret_cast<PropertyType*>(data_);
+ }
+
+ private:
+ bool is_valid_;
+ unsigned long size_; // NOLINT: type required by XGetWindowProperty
+ unsigned char* data_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XWindowProperty);
+};
+
+class WindowCapturerLinux : public WindowCapturer,
+ public SharedXDisplay::XEventHandler {
+ public:
+ WindowCapturerLinux(const DesktopCaptureOptions& options);
+ virtual ~WindowCapturerLinux();
+
+ // WindowCapturer interface.
+ bool GetWindowList(WindowList* windows) override;
+ bool SelectWindow(WindowId id) override;
+ bool BringSelectedWindowToFront() override;
+
+ // DesktopCapturer interface.
+ void Start(Callback* callback) override;
+ void Capture(const DesktopRegion& region) override;
+
+ // SharedXDisplay::XEventHandler interface.
+ bool HandleXEvent(const XEvent& event) override;
+
+ private:
+ Display* display() { return x_display_->display(); }
+
+ // Iterates through |window| hierarchy to find first visible window, i.e. one
+ // that has WM_STATE property set to NormalState.
+ // See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
+ ::Window GetApplicationWindow(::Window window);
+
+ // Returns true if the |window| is a desktop element.
+ bool IsDesktopElement(::Window window);
+
+ // Returns window title for the specified X |window|.
+ bool GetWindowTitle(::Window window, std::string* title);
+
+ Callback* callback_;
+
+ rtc::scoped_refptr<SharedXDisplay> x_display_;
+
+ Atom wm_state_atom_;
+ Atom window_type_atom_;
+ Atom normal_window_type_atom_;
+ bool has_composite_extension_;
+
+ ::Window selected_window_;
+ XServerPixelBuffer x_server_pixel_buffer_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
+};
+
+WindowCapturerLinux::WindowCapturerLinux(const DesktopCaptureOptions& options)
+ : callback_(NULL),
+ x_display_(options.x_display()),
+ has_composite_extension_(false),
+ selected_window_(0) {
+ // Create Atoms so we don't need to do it every time they are used.
+ wm_state_atom_ = XInternAtom(display(), "WM_STATE", True);
+ window_type_atom_ = XInternAtom(display(), "_NET_WM_WINDOW_TYPE", True);
+ normal_window_type_atom_ = XInternAtom(
+ display(), "_NET_WM_WINDOW_TYPE_NORMAL", True);
+
+ int event_base, error_base, major_version, minor_version;
+ if (XCompositeQueryExtension(display(), &event_base, &error_base) &&
+ XCompositeQueryVersion(display(), &major_version, &minor_version) &&
+ // XCompositeNameWindowPixmap() requires version 0.2
+ (major_version > 0 || minor_version >= 2)) {
+ has_composite_extension_ = true;
+ } else {
+ LOG(LS_INFO) << "Xcomposite extension not available or too old.";
+ }
+
+ x_display_->AddEventHandler(ConfigureNotify, this);
+}
+
+WindowCapturerLinux::~WindowCapturerLinux() {
+ x_display_->RemoveEventHandler(ConfigureNotify, this);
+}
+
+bool WindowCapturerLinux::GetWindowList(WindowList* windows) {
+ WindowList result;
+
+ XErrorTrap error_trap(display());
+
+ int num_screens = XScreenCount(display());
+ for (int screen = 0; screen < num_screens; ++screen) {
+ ::Window root_window = XRootWindow(display(), screen);
+ ::Window parent;
+ ::Window *children;
+ unsigned int num_children;
+ int status = XQueryTree(display(), root_window, &root_window, &parent,
+ &children, &num_children);
+ if (status == 0) {
+ LOG(LS_ERROR) << "Failed to query for child windows for screen "
+ << screen;
+ continue;
+ }
+
+ for (unsigned int i = 0; i < num_children; ++i) {
+ // Iterate in reverse order to return windows from front to back.
+ ::Window app_window =
+ GetApplicationWindow(children[num_children - 1 - i]);
+ if (app_window && !IsDesktopElement(app_window)) {
+ Window w;
+ w.id = app_window;
+ if (GetWindowTitle(app_window, &w.title))
+ result.push_back(w);
+ }
+ }
+
+ if (children)
+ XFree(children);
+ }
+
+ windows->swap(result);
+
+ return true;
+}
+
+bool WindowCapturerLinux::SelectWindow(WindowId id) {
+ if (!x_server_pixel_buffer_.Init(display(), id))
+ return false;
+
+ // Tell the X server to send us window resizing events.
+ XSelectInput(display(), id, StructureNotifyMask);
+
+ selected_window_ = id;
+
+ // In addition to needing X11 server-side support for Xcomposite, it actually
+ // needs to be turned on for the window. If the user has modern
+ // hardware/drivers but isn't using a compositing window manager, that won't
+ // be the case. Here we automatically turn it on.
+
+ // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11
+ // remembers who has requested this and will turn it off for us when we exit.
+ XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic);
+
+ return true;
+}
+
+bool WindowCapturerLinux::BringSelectedWindowToFront() {
+ if (!selected_window_)
+ return false;
+
+ unsigned int num_children;
+ ::Window* children;
+ ::Window parent;
+ ::Window root;
+ // Find the root window to pass event to.
+ int status = XQueryTree(
+ display(), selected_window_, &root, &parent, &children, &num_children);
+ if (status == 0) {
+ LOG(LS_ERROR) << "Failed to query for the root window.";
+ return false;
+ }
+
+ if (children)
+ XFree(children);
+
+ XRaiseWindow(display(), selected_window_);
+
+ // Some window managers (e.g., metacity in GNOME) consider it illegal to
+ // raise a window without also giving it input focus with
+ // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
+ Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True);
+ if (atom != None) {
+ XEvent xev;
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.window = selected_window_;
+ xev.xclient.message_type = atom;
+
+ // The format member is set to 8, 16, or 32 and specifies whether the
+ // data should be viewed as a list of bytes, shorts, or longs.
+ xev.xclient.format = 32;
+
+ memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l));
+
+ XSendEvent(display(),
+ root,
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+ }
+ XFlush(display());
+ return true;
+}
+
+void WindowCapturerLinux::Start(Callback* callback) {
+ assert(!callback_);
+ assert(callback);
+
+ callback_ = callback;
+}
+
+void WindowCapturerLinux::Capture(const DesktopRegion& region) {
+ if (!x_server_pixel_buffer_.IsWindowValid()) {
+ LOG(LS_INFO) << "The window is no longer valid.";
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ x_display_->ProcessPendingXEvents();
+
+ if (!has_composite_extension_) {
+ // Without the Xcomposite extension we capture when the whole window is
+ // visible on screen and not covered by any other window. This is not
+ // something we want so instead, just bail out.
+ LOG(LS_INFO) << "No Xcomposite extension detected.";
+ callback_->OnCaptureCompleted(NULL);
+ return;
+ }
+
+ DesktopFrame* frame =
+ new BasicDesktopFrame(x_server_pixel_buffer_.window_size());
+
+ x_server_pixel_buffer_.Synchronize();
+ x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
+ frame);
+
+ frame->mutable_updated_region()->SetRect(
+ DesktopRect::MakeSize(frame->size()));
+
+ callback_->OnCaptureCompleted(frame);
+}
+
+bool WindowCapturerLinux::HandleXEvent(const XEvent& event) {
+ if (event.type == ConfigureNotify) {
+ XConfigureEvent xce = event.xconfigure;
+ if (!DesktopSize(xce.width, xce.height).equals(
+ x_server_pixel_buffer_.window_size())) {
+ if (!x_server_pixel_buffer_.Init(display(), selected_window_)) {
+ LOG(LS_ERROR) << "Failed to initialize pixel buffer after resizing.";
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+::Window WindowCapturerLinux::GetApplicationWindow(::Window window) {
+ // Get WM_STATE property of the window.
+ XWindowProperty<uint32_t> window_state(display(), window, wm_state_atom_);
+
+ // WM_STATE is considered to be set to WithdrawnState when it missing.
+ int32_t state = window_state.is_valid() ?
+ *window_state.data() : WithdrawnState;
+
+ if (state == NormalState) {
+ // Window has WM_STATE==NormalState. Return it.
+ return window;
+ } else if (state == IconicState) {
+ // Window is in minimized. Skip it.
+ return 0;
+ }
+
+ // If the window is in WithdrawnState then look at all of its children.
+ ::Window root, parent;
+ ::Window *children;
+ unsigned int num_children;
+ if (!XQueryTree(display(), window, &root, &parent, &children,
+ &num_children)) {
+ LOG(LS_ERROR) << "Failed to query for child windows although window"
+ << "does not have a valid WM_STATE.";
+ return 0;
+ }
+ ::Window app_window = 0;
+ for (unsigned int i = 0; i < num_children; ++i) {
+ app_window = GetApplicationWindow(children[i]);
+ if (app_window)
+ break;
+ }
+
+ if (children)
+ XFree(children);
+ return app_window;
+}
+
+bool WindowCapturerLinux::IsDesktopElement(::Window window) {
+ if (window == 0)
+ return false;
+
+ // First look for _NET_WM_WINDOW_TYPE. The standard
+ // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
+ // says this hint *should* be present on all windows, and we use the existence
+ // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
+ // a desktop element (that is, only "normal" windows should be shareable).
+ XWindowProperty<uint32_t> window_type(display(), window, window_type_atom_);
+ if (window_type.is_valid() && window_type.size() > 0) {
+ uint32_t* end = window_type.data() + window_type.size();
+ bool is_normal = (end != std::find(
+ window_type.data(), end, normal_window_type_atom_));
+ return !is_normal;
+ }
+
+ // Fall back on using the hint.
+ XClassHint class_hint;
+ Status status = XGetClassHint(display(), window, &class_hint);
+ bool result = false;
+ if (status == 0) {
+ // No hints, assume this is a normal application window.
+ return result;
+ }
+
+ if (strcmp("gnome-panel", class_hint.res_name) == 0 ||
+ strcmp("desktop_window", class_hint.res_name) == 0) {
+ result = true;
+ }
+ XFree(class_hint.res_name);
+ XFree(class_hint.res_class);
+ return result;
+}
+
+bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) {
+ int status;
+ bool result = false;
+ XTextProperty window_name;
+ window_name.value = NULL;
+ if (window) {
+ status = XGetWMName(display(), window, &window_name);
+ if (status && window_name.value && window_name.nitems) {
+ int cnt;
+ char **list = NULL;
+ status = Xutf8TextPropertyToTextList(display(), &window_name, &list,
+ &cnt);
+ if (status >= Success && cnt && *list) {
+ if (cnt > 1) {
+ LOG(LS_INFO) << "Window has " << cnt
+ << " text properties, only using the first one.";
+ }
+ *title = *list;
+ result = true;
+ }
+ if (list)
+ XFreeStringList(list);
+ }
+ if (window_name.value)
+ XFree(window_name.value);
+ }
+ return result;
+}
+
+} // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
+ if (!options.x_display())
+ return NULL;
+ return new WindowCapturerLinux(options);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/x11/shared_x_display.cc b/webrtc/modules/desktop_capture/x11/shared_x_display.cc
new file mode 100644
index 0000000000..3eb5eb10a9
--- /dev/null
+++ b/webrtc/modules/desktop_capture/x11/shared_x_display.cc
@@ -0,0 +1,87 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/x11/shared_x_display.h"
+
+#include <algorithm>
+
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+SharedXDisplay::SharedXDisplay(Display* display)
+ : display_(display) {
+ assert(display_);
+}
+
+SharedXDisplay::~SharedXDisplay() {
+ assert(event_handlers_.empty());
+ XCloseDisplay(display_);
+}
+
+// static
+rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::Create(
+ const std::string& display_name) {
+ Display* display =
+ XOpenDisplay(display_name.empty() ? NULL : display_name.c_str());
+ if (!display) {
+ LOG(LS_ERROR) << "Unable to open display";
+ return NULL;
+ }
+ return new SharedXDisplay(display);
+}
+
+// static
+rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::CreateDefault() {
+ return Create(std::string());
+}
+
+void SharedXDisplay::AddEventHandler(int type, XEventHandler* handler) {
+ event_handlers_[type].push_back(handler);
+}
+
+void SharedXDisplay::RemoveEventHandler(int type, XEventHandler* handler) {
+ EventHandlersMap::iterator handlers = event_handlers_.find(type);
+ if (handlers == event_handlers_.end())
+ return;
+
+ std::vector<XEventHandler*>::iterator new_end =
+ std::remove(handlers->second.begin(), handlers->second.end(), handler);
+ handlers->second.erase(new_end, handlers->second.end());
+
+ // Check if no handlers left for this event.
+ if (handlers->second.empty())
+ event_handlers_.erase(handlers);
+}
+
+void SharedXDisplay::ProcessPendingXEvents() {
+ // Hold reference to |this| to prevent it from being destroyed while
+ // processing events.
+ rtc::scoped_refptr<SharedXDisplay> self(this);
+
+ // Find the number of events that are outstanding "now." We don't just loop
+ // on XPending because we want to guarantee this terminates.
+ int events_to_process = XPending(display());
+ XEvent e;
+
+ for (int i = 0; i < events_to_process; i++) {
+ XNextEvent(display(), &e);
+ EventHandlersMap::iterator handlers = event_handlers_.find(e.type);
+ if (handlers == event_handlers_.end())
+ continue;
+ for (std::vector<XEventHandler*>::iterator it = handlers->second.begin();
+ it != handlers->second.end(); ++it) {
+ if ((*it)->HandleXEvent(e))
+ break;
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/x11/shared_x_display.h b/webrtc/modules/desktop_capture/x11/shared_x_display.h
new file mode 100644
index 0000000000..d905b9e51c
--- /dev/null
+++ b/webrtc/modules/desktop_capture/x11/shared_x_display.h
@@ -0,0 +1,84 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_
+
+#include <map>
+#include <vector>
+
+#include <assert.h>
+#include <X11/Xlib.h>
+
+#include <string>
+
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/system_wrappers/include/atomic32.h"
+
+namespace webrtc {
+
+// A ref-counted object to store XDisplay connection.
+class SharedXDisplay {
+ public:
+ class XEventHandler {
+ public:
+ virtual ~XEventHandler() {}
+
+ // Processes XEvent. Returns true if the event has been handled.
+ virtual bool HandleXEvent(const XEvent& event) = 0;
+ };
+
+ // Takes ownership of |display|.
+ explicit SharedXDisplay(Display* display);
+
+ // Creates a new X11 Display for the |display_name|. NULL is returned if X11
+ // connection failed. Equivalent to CreateDefault() when |display_name| is
+ // empty.
+ static rtc::scoped_refptr<SharedXDisplay> Create(
+ const std::string& display_name);
+
+ // Creates X11 Display connection for the default display (e.g. specified in
+ // DISPLAY). NULL is returned if X11 connection failed.
+ static rtc::scoped_refptr<SharedXDisplay> CreateDefault();
+
+ void AddRef() { ++ref_count_; }
+ void Release() {
+ if (--ref_count_ == 0)
+ delete this;
+ }
+
+ Display* display() { return display_; }
+
+ // Adds a new event |handler| for XEvent's of |type|.
+ void AddEventHandler(int type, XEventHandler* handler);
+
+ // Removes event |handler| added using |AddEventHandler|. Doesn't do anything
+ // if |handler| is not registered.
+ void RemoveEventHandler(int type, XEventHandler* handler);
+
+ // Processes pending XEvents, calling corresponding event handlers.
+ void ProcessPendingXEvents();
+
+ private:
+ typedef std::map<int, std::vector<XEventHandler*> > EventHandlersMap;
+
+ ~SharedXDisplay();
+
+ Atomic32 ref_count_;
+ Display* display_;
+
+ EventHandlersMap event_handlers_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(SharedXDisplay);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_
diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.cc b/webrtc/modules/desktop_capture/x11/x_error_trap.cc
new file mode 100644
index 0000000000..458fbe61a0
--- /dev/null
+++ b/webrtc/modules/desktop_capture/x11/x_error_trap.cc
@@ -0,0 +1,69 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/x11/x_error_trap.h"
+
+#include <assert.h>
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#endif // !defined(TOOLKIT_GTK)
+
+namespace webrtc {
+
+namespace {
+
+#if !defined(TOOLKIT_GTK)
+
+// TODO(sergeyu): This code is not thread safe. Fix it. Bug 2202.
+static bool g_xserver_error_trap_enabled = false;
+static int g_last_xserver_error_code = 0;
+
+int XServerErrorHandler(Display* display, XErrorEvent* error_event) {
+ assert(g_xserver_error_trap_enabled);
+ g_last_xserver_error_code = error_event->error_code;
+ return 0;
+}
+
+#endif // !defined(TOOLKIT_GTK)
+
+} // namespace
+
+XErrorTrap::XErrorTrap(Display* display)
+ : original_error_handler_(NULL),
+ enabled_(true) {
+#if defined(TOOLKIT_GTK)
+ gdk_error_trap_push();
+#else // !defined(TOOLKIT_GTK)
+ assert(!g_xserver_error_trap_enabled);
+ original_error_handler_ = XSetErrorHandler(&XServerErrorHandler);
+ g_xserver_error_trap_enabled = true;
+ g_last_xserver_error_code = 0;
+#endif // !defined(TOOLKIT_GTK)
+}
+
+int XErrorTrap::GetLastErrorAndDisable() {
+ enabled_ = false;
+#if defined(TOOLKIT_GTK)
+ return gdk_error_trap_push();
+#else // !defined(TOOLKIT_GTK)
+ assert(g_xserver_error_trap_enabled);
+ XSetErrorHandler(original_error_handler_);
+ g_xserver_error_trap_enabled = false;
+ return g_last_xserver_error_code;
+#endif // !defined(TOOLKIT_GTK)
+}
+
+XErrorTrap::~XErrorTrap() {
+ if (enabled_)
+ GetLastErrorAndDisable();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.h b/webrtc/modules/desktop_capture/x11/x_error_trap.h
new file mode 100644
index 0000000000..f1f6e11c63
--- /dev/null
+++ b/webrtc/modules/desktop_capture/x11/x_error_trap.h
@@ -0,0 +1,39 @@
+/*
+ * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_
+
+#include <X11/Xlib.h>
+
+#include "webrtc/base/constructormagic.h"
+
+namespace webrtc {
+
+// Helper class that registers X Window error handler. Caller can use
+// GetLastErrorAndDisable() to get the last error that was caught, if any.
+class XErrorTrap {
+ public:
+ explicit XErrorTrap(Display* display);
+ ~XErrorTrap();
+
+ // Returns last error and removes unregisters the error handler.
+ int GetLastErrorAndDisable();
+
+ private:
+ XErrorHandler original_error_handler_;
+ bool enabled_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XErrorTrap);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_
diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc
new file mode 100644
index 0000000000..bcfcb7e027
--- /dev/null
+++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc
@@ -0,0 +1,337 @@
+/*
+ * 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 "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+#include <sys/shm.h>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace {
+
+// Returns the number of bits |mask| has to be shifted left so its last
+// (most-significant) bit set becomes the most-significant bit of the word.
+// When |mask| is 0 the function returns 31.
+uint32_t MaskToShift(uint32_t mask) {
+ int shift = 0;
+ if ((mask & 0xffff0000u) == 0) {
+ mask <<= 16;
+ shift += 16;
+ }
+ if ((mask & 0xff000000u) == 0) {
+ mask <<= 8;
+ shift += 8;
+ }
+ if ((mask & 0xf0000000u) == 0) {
+ mask <<= 4;
+ shift += 4;
+ }
+ if ((mask & 0xc0000000u) == 0) {
+ mask <<= 2;
+ shift += 2;
+ }
+ if ((mask & 0x80000000u) == 0)
+ shift += 1;
+
+ return shift;
+}
+
+// Returns true if |image| is in RGB format.
+bool IsXImageRGBFormat(XImage* image) {
+ return image->bits_per_pixel == 32 &&
+ image->red_mask == 0xff0000 &&
+ image->green_mask == 0xff00 &&
+ image->blue_mask == 0xff;
+}
+
+} // namespace
+
+namespace webrtc {
+
+XServerPixelBuffer::XServerPixelBuffer()
+ : display_(NULL), window_(0),
+ x_image_(NULL),
+ shm_segment_info_(NULL), shm_pixmap_(0), shm_gc_(NULL) {
+}
+
+XServerPixelBuffer::~XServerPixelBuffer() {
+ Release();
+}
+
+void XServerPixelBuffer::Release() {
+ if (x_image_) {
+ XDestroyImage(x_image_);
+ x_image_ = NULL;
+ }
+ if (shm_pixmap_) {
+ XFreePixmap(display_, shm_pixmap_);
+ shm_pixmap_ = 0;
+ }
+ if (shm_gc_) {
+ XFreeGC(display_, shm_gc_);
+ shm_gc_ = NULL;
+ }
+ if (shm_segment_info_) {
+ if (shm_segment_info_->shmaddr != reinterpret_cast<char*>(-1))
+ shmdt(shm_segment_info_->shmaddr);
+ if (shm_segment_info_->shmid != -1)
+ shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
+ delete shm_segment_info_;
+ shm_segment_info_ = NULL;
+ }
+ window_ = 0;
+}
+
+bool XServerPixelBuffer::Init(Display* display, Window window) {
+ Release();
+ display_ = display;
+
+ XWindowAttributes attributes;
+ {
+ XErrorTrap error_trap(display_);
+ if (!XGetWindowAttributes(display_, window, &attributes) ||
+ error_trap.GetLastErrorAndDisable() != 0) {
+ return false;
+ }
+ }
+
+ window_size_ = DesktopSize(attributes.width, attributes.height);
+ window_ = window;
+ InitShm(attributes);
+
+ return true;
+}
+
+void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
+ Visual* default_visual = attributes.visual;
+ int default_depth = attributes.depth;
+
+ int major, minor;
+ Bool have_pixmaps;
+ if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
+ // Shared memory not supported. CaptureRect will use the XImage API instead.
+ return;
+ }
+
+ bool using_shm = false;
+ shm_segment_info_ = new XShmSegmentInfo;
+ shm_segment_info_->shmid = -1;
+ shm_segment_info_->shmaddr = reinterpret_cast<char*>(-1);
+ shm_segment_info_->readOnly = False;
+ x_image_ = XShmCreateImage(display_, default_visual, default_depth, ZPixmap,
+ 0, shm_segment_info_, window_size_.width(),
+ window_size_.height());
+ if (x_image_) {
+ shm_segment_info_->shmid = shmget(
+ IPC_PRIVATE, x_image_->bytes_per_line * x_image_->height,
+ IPC_CREAT | 0600);
+ if (shm_segment_info_->shmid != -1) {
+ shm_segment_info_->shmaddr = x_image_->data =
+ reinterpret_cast<char*>(shmat(shm_segment_info_->shmid, 0, 0));
+ if (x_image_->data != reinterpret_cast<char*>(-1)) {
+ XErrorTrap error_trap(display_);
+ using_shm = XShmAttach(display_, shm_segment_info_);
+ XSync(display_, False);
+ if (error_trap.GetLastErrorAndDisable() != 0)
+ using_shm = false;
+ if (using_shm) {
+ LOG(LS_VERBOSE) << "Using X shared memory segment "
+ << shm_segment_info_->shmid;
+ }
+ }
+ } else {
+ LOG(LS_WARNING) << "Failed to get shared memory segment. "
+ "Performance may be degraded.";
+ }
+ }
+
+ if (!using_shm) {
+ LOG(LS_WARNING) << "Not using shared memory. Performance may be degraded.";
+ Release();
+ return;
+ }
+
+ if (have_pixmaps)
+ have_pixmaps = InitPixmaps(default_depth);
+
+ shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
+ shm_segment_info_->shmid = -1;
+
+ LOG(LS_VERBOSE) << "Using X shared memory extension v"
+ << major << "." << minor
+ << " with" << (have_pixmaps ? "" : "out") << " pixmaps.";
+}
+
+bool XServerPixelBuffer::InitPixmaps(int depth) {
+ if (XShmPixmapFormat(display_) != ZPixmap)
+ return false;
+
+ {
+ XErrorTrap error_trap(display_);
+ shm_pixmap_ = XShmCreatePixmap(display_, window_,
+ shm_segment_info_->shmaddr,
+ shm_segment_info_,
+ window_size_.width(),
+ window_size_.height(), depth);
+ XSync(display_, False);
+ if (error_trap.GetLastErrorAndDisable() != 0) {
+ // |shm_pixmap_| is not not valid because the request was not processed
+ // by the X Server, so zero it.
+ shm_pixmap_ = 0;
+ return false;
+ }
+ }
+
+ {
+ XErrorTrap error_trap(display_);
+ XGCValues shm_gc_values;
+ shm_gc_values.subwindow_mode = IncludeInferiors;
+ shm_gc_values.graphics_exposures = False;
+ shm_gc_ = XCreateGC(display_, window_,
+ GCSubwindowMode | GCGraphicsExposures,
+ &shm_gc_values);
+ XSync(display_, False);
+ if (error_trap.GetLastErrorAndDisable() != 0) {
+ XFreePixmap(display_, shm_pixmap_);
+ shm_pixmap_ = 0;
+ shm_gc_ = 0; // See shm_pixmap_ comment above.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool XServerPixelBuffer::IsWindowValid() const {
+ XWindowAttributes attributes;
+ {
+ XErrorTrap error_trap(display_);
+ if (!XGetWindowAttributes(display_, window_, &attributes) ||
+ error_trap.GetLastErrorAndDisable() != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void XServerPixelBuffer::Synchronize() {
+ if (shm_segment_info_ && !shm_pixmap_) {
+ // XShmGetImage can fail if the display is being reconfigured.
+ XErrorTrap error_trap(display_);
+ XShmGetImage(display_, window_, x_image_, 0, 0, AllPlanes);
+ }
+}
+
+void XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
+ DesktopFrame* frame) {
+ assert(rect.right() <= window_size_.width());
+ assert(rect.bottom() <= window_size_.height());
+
+ uint8_t* data;
+
+ if (shm_segment_info_) {
+ if (shm_pixmap_) {
+ XCopyArea(display_, window_, shm_pixmap_, shm_gc_,
+ rect.left(), rect.top(), rect.width(), rect.height(),
+ rect.left(), rect.top());
+ XSync(display_, False);
+ }
+ data = reinterpret_cast<uint8_t*>(x_image_->data) +
+ rect.top() * x_image_->bytes_per_line +
+ rect.left() * x_image_->bits_per_pixel / 8;
+ } else {
+ if (x_image_)
+ XDestroyImage(x_image_);
+ x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
+ rect.width(), rect.height(), AllPlanes, ZPixmap);
+ data = reinterpret_cast<uint8_t*>(x_image_->data);
+ }
+
+ if (IsXImageRGBFormat(x_image_)) {
+ FastBlit(data, rect, frame);
+ } else {
+ SlowBlit(data, rect, frame);
+ }
+}
+
+void XServerPixelBuffer::FastBlit(uint8_t* image,
+ const DesktopRect& rect,
+ DesktopFrame* frame) {
+ uint8_t* src_pos = image;
+ int src_stride = x_image_->bytes_per_line;
+ int dst_x = rect.left(), dst_y = rect.top();
+
+ uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
+ dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
+
+ int height = rect.height();
+ int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
+ for (int y = 0; y < height; ++y) {
+ memcpy(dst_pos, src_pos, row_bytes);
+ src_pos += src_stride;
+ dst_pos += frame->stride();
+ }
+}
+
+void XServerPixelBuffer::SlowBlit(uint8_t* image,
+ const DesktopRect& rect,
+ DesktopFrame* frame) {
+ int src_stride = x_image_->bytes_per_line;
+ int dst_x = rect.left(), dst_y = rect.top();
+ int width = rect.width(), height = rect.height();
+
+ uint32_t red_mask = x_image_->red_mask;
+ uint32_t green_mask = x_image_->red_mask;
+ uint32_t blue_mask = x_image_->blue_mask;
+
+ uint32_t red_shift = MaskToShift(red_mask);
+ uint32_t green_shift = MaskToShift(green_mask);
+ uint32_t blue_shift = MaskToShift(blue_mask);
+
+ int bits_per_pixel = x_image_->bits_per_pixel;
+
+ uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
+ uint8_t* src_pos = image;
+ dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
+ // TODO(hclam): Optimize, perhaps using MMX code or by converting to
+ // YUV directly.
+ // TODO(sergeyu): This code doesn't handle XImage byte order properly and
+ // won't work with 24bpp images. Fix it.
+ for (int y = 0; y < height; y++) {
+ uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
+ uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
+ uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
+ for (int x = 0; x < width; x++) {
+ // Dereference through an appropriately-aligned pointer.
+ uint32_t pixel;
+ if (bits_per_pixel == 32) {
+ pixel = src_pos_32[x];
+ } else if (bits_per_pixel == 16) {
+ pixel = src_pos_16[x];
+ } else {
+ pixel = src_pos[x];
+ }
+ uint32_t r = (pixel & red_mask) << red_shift;
+ uint32_t g = (pixel & green_mask) << green_shift;
+ uint32_t b = (pixel & blue_mask) << blue_shift;
+ // Write as 32-bit RGB.
+ dst_pos_32[x] = ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) |
+ ((b >> 24) & 0xff);
+ }
+ dst_pos += frame->stride();
+ src_pos += src_stride;
+ }
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h
new file mode 100644
index 0000000000..d1e6632f08
--- /dev/null
+++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+// Don't include this file in any .h files because it pulls in some X headers.
+
+#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_
+
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+
+#include <X11/Xutil.h>
+#include <X11/extensions/XShm.h>
+
+namespace webrtc {
+
+class DesktopFrame;
+
+// A class to allow the X server's pixel buffer to be accessed as efficiently
+// as possible.
+class XServerPixelBuffer {
+ public:
+ XServerPixelBuffer();
+ ~XServerPixelBuffer();
+
+ void Release();
+
+ // Allocate (or reallocate) the pixel buffer for |window|. Returns false in
+ // case of an error (e.g. window doesn't exist).
+ bool Init(Display* display, Window window);
+
+ bool is_initialized() { return window_ != 0; }
+
+ // Returns the size of the window the buffer was initialized for.
+ const DesktopSize& window_size() { return window_size_; }
+
+ // Returns true if the window can be found.
+ bool IsWindowValid() const;
+
+ // If shared memory is being used without pixmaps, synchronize this pixel
+ // buffer with the root window contents (otherwise, this is a no-op).
+ // This is to avoid doing a full-screen capture for each individual
+ // rectangle in the capture list, when it only needs to be done once at the
+ // beginning.
+ void Synchronize();
+
+ // Capture the specified rectangle and stores it in the |frame|. In the case
+ // where the full-screen data is captured by Synchronize(), this simply
+ // returns the pointer without doing any more work. The caller must ensure
+ // that |rect| is not larger than window_size().
+ void CaptureRect(const DesktopRect& rect, DesktopFrame* frame);
+
+ private:
+ void InitShm(const XWindowAttributes& attributes);
+ bool InitPixmaps(int depth);
+
+ // We expose two forms of blitting to handle variations in the pixel format.
+ // In FastBlit(), the operation is effectively a memcpy.
+ void FastBlit(uint8_t* image,
+ const DesktopRect& rect,
+ DesktopFrame* frame);
+ void SlowBlit(uint8_t* image,
+ const DesktopRect& rect,
+ DesktopFrame* frame);
+
+ Display* display_;
+ Window window_;
+ DesktopSize window_size_;
+ XImage* x_image_;
+ XShmSegmentInfo* shm_segment_info_;
+ Pixmap shm_pixmap_;
+ GC shm_gc_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XServerPixelBuffer);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_