Window capturer implementation for Windows.

R=alexeypa@chromium.org, andrew@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/1477004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@4064 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi
index b9778dc..d34cd64 100644
--- a/webrtc/modules/desktop_capture/desktop_capture.gypi
+++ b/webrtc/modules/desktop_capture/desktop_capture.gypi
@@ -7,50 +7,79 @@
 # 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/source/system_wrappers.gyp:system_wrappers',
-      ],
-      'direct_dependent_settings': {
-        # Headers may use include path relative to webrtc root and depend on
-        # WEBRTC_WIN define, so we need to make sure dependent targets have
-        # these settings.
-        #
-        # TODO(sergeyu): Move these settings to common.gypi
-        'include_dirs': [
-          '../../..',
-        ],
-        'conditions': [
-          ['OS=="win"', {
-            'defines': [
-              'WEBRTC_WIN',
-            ],
-          }],
-        ],
-      },
-      'sources': [
-        "desktop_capturer.h",
-        "desktop_frame.cc",
-        "desktop_frame.h",
-        "desktop_frame_win.cc",
-        "desktop_frame_win.h",
-        "desktop_geometry.cc",
-        "desktop_geometry.h",
-        "desktop_region.cc",
-        "desktop_region.h",
-        "shared_memory.cc",
-        "shared_memory.h",
-      ],
-      'conditions': [
-        ['OS!="win"', {
-          'sources/': [
-            ['exclude', '_win(_unittest)?\\.(cc|h)$'],
+  'variables': {
+    'conditions': [
+      # Desktop capturer is supported only on Windows, OSX and Linux.
+      ['OS=="win" or OS=="mac" or OS=="linux"', {
+        'desktop_capture_enabled%': 1,
+      }, {
+        'desktop_capture_enabled%': 0,
+      }],
+    ],
+  },
+  'conditions': [
+    ['desktop_capture_enabled==1', {
+      'targets': [
+        {
+          'target_name': 'desktop_capture',
+          'type': 'static_library',
+          'dependencies': [
+            '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers',
           ],
-        }],
-      ],
-    },
-  ], # targets
+          'direct_dependent_settings': {
+            # Headers may use include path relative to webrtc root and depend on
+            # WEBRTC_WIN define, so we need to make sure dependent targets have
+            # these settings.
+            #
+            # TODO(sergeyu): Move these settings to common.gypi
+            'include_dirs': [
+              '../../..',
+            ],
+            'conditions': [
+              ['OS=="win"', {
+                'defines': [
+                  'WEBRTC_WIN',
+                ],
+              }],
+            ],
+          },
+          'sources': [
+            "desktop_capturer.h",
+            "desktop_frame.cc",
+            "desktop_frame.h",
+            "desktop_frame_win.cc",
+            "desktop_frame_win.h",
+            "desktop_geometry.cc",
+            "desktop_geometry.h",
+            "desktop_region.cc",
+            "desktop_region.h",
+            "shared_memory.cc",
+            "shared_memory.h",
+            "window_capturer.h",
+            "window_capturer_linux.cc",
+            "window_capturer_mac.cc",
+            "window_capturer_win.cc",
+          ],
+        },
+      ],  # targets
+    }],  # desktop_capture_enabled==1
+    ['desktop_capture_enabled==1 and include_tests==1', {
+      'targets': [
+        {
+          'target_name': 'desktop_capture_unittests',
+          'type': 'executable',
+          'dependencies': [
+            'desktop_capture',
+            '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers',
+            '<(webrtc_root)/test/test.gyp:test_support',
+            '<(webrtc_root)/test/test.gyp:test_support_main',
+            '<(DEPTH)/testing/gtest.gyp:gtest',
+          ],
+          'sources': [
+            "window_capturer_unittest.cc",
+          ],
+        },
+      ], # targets
+    }],  # desktop_capture_enabled==1 && include_tests==1
+  ],
 }
diff --git a/webrtc/modules/desktop_capture/window_capturer.h b/webrtc/modules/desktop_capture/window_capturer.h
new file mode 100644
index 0000000..65b27ed
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer.h
@@ -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.
+ */
+
+#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_
+
+#include <vector>
+#include <string>
+
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/system_wrappers/interface/constructor_magic.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class WindowCapturer : public DesktopCapturer {
+ public:
+#if defined(WEBRTC_LINUX) || defined(WEBRTC_MAC)
+  typedef unsigned int WindowId;
+#elif defined(WEBRTC_WIN)
+  typedef void* WindowId;
+#endif
+
+  struct Window {
+    WindowId id;
+
+    // Title of the window in UTF-8 encoding.
+    std::string title;
+  };
+
+  typedef std::vector<Window> WindowList;
+
+  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;
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_
+
diff --git a/webrtc/modules/desktop_capture/window_capturer_linux.cc b/webrtc/modules/desktop_capture/window_capturer_linux.cc
new file mode 100755
index 0000000..5229009
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_linux.cc
@@ -0,0 +1,76 @@
+/*
+ *  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 <cassert>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace webrtc {
+
+namespace {
+
+class WindowCapturerLinux : public WindowCapturer {
+ public:
+  WindowCapturerLinux();
+  virtual ~WindowCapturerLinux();
+
+  // WindowCapturer interface.
+  virtual bool GetWindowList(WindowList* windows) OVERRIDE;
+  virtual bool SelectWindow(WindowId id) OVERRIDE;
+
+  // DesktopCapturer interface.
+  virtual void Start(Callback* callback) OVERRIDE;
+  virtual void Capture(const DesktopRegion& region) OVERRIDE;
+
+ private:
+  Callback* callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
+};
+
+WindowCapturerLinux::WindowCapturerLinux()
+    : callback_(NULL) {
+}
+
+WindowCapturerLinux::~WindowCapturerLinux() {
+}
+
+bool WindowCapturerLinux::GetWindowList(WindowList* windows) {
+  // Not implemented yet.
+  return false;
+}
+
+bool WindowCapturerLinux::SelectWindow(WindowId id) {
+  // Not implemented yet.
+  return false;
+}
+
+void WindowCapturerLinux::Start(Callback* callback) {
+  assert(!callback_);
+  assert(callback);
+
+  callback_ = callback;
+}
+
+void WindowCapturerLinux::Capture(const DesktopRegion& region) {
+  // Not implemented yet.
+  callback_->OnCaptureCompleted(NULL);
+}
+
+}  // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create() {
+  return new WindowCapturerLinux();
+}
+
+}  // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/window_capturer_mac.cc b/webrtc/modules/desktop_capture/window_capturer_mac.cc
new file mode 100755
index 0000000..a372c3b
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_mac.cc
@@ -0,0 +1,76 @@
+/*
+ *  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 <cassert>
+
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace webrtc {
+
+namespace {
+
+class WindowCapturerMac : public WindowCapturer {
+ public:
+  WindowCapturerMac();
+  virtual ~WindowCapturerMac();
+
+  // WindowCapturer interface.
+  virtual bool GetWindowList(WindowList* windows) OVERRIDE;
+  virtual bool SelectWindow(WindowId id) OVERRIDE;
+
+  // DesktopCapturer interface.
+  virtual void Start(Callback* callback) OVERRIDE;
+  virtual void Capture(const DesktopRegion& region) OVERRIDE;
+
+ private:
+  Callback* callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowCapturerMac);
+};
+
+WindowCapturerMac::WindowCapturerMac()
+    : callback_(NULL) {
+}
+
+WindowCapturerMac::~WindowCapturerMac() {
+}
+
+bool WindowCapturerMac::GetWindowList(WindowList* windows) {
+  // Not implemented yet.
+  return false;
+}
+
+bool WindowCapturerMac::SelectWindow(WindowId id) {
+  // Not implemented yet.
+  return false;
+}
+
+void WindowCapturerMac::Start(Callback* callback) {
+  assert(!callback_);
+  assert(callback);
+
+  callback_ = callback;
+}
+
+void WindowCapturerMac::Capture(const DesktopRegion& region) {
+  // Not implemented yet.
+  callback_->OnCaptureCompleted(NULL);
+}
+
+}  // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create() {
+  return new WindowCapturerMac();
+}
+
+}  // 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 0000000..6f75f21
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_unittest.cc
@@ -0,0 +1,96 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+
+#include "gtest/gtest.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/desktop_region.h"
+#include "webrtc/system_wrappers/interface/logging.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+namespace webrtc {
+
+class WindowCapturerTest : public testing::Test,
+                           public DesktopCapturer::Callback {
+ public:
+  void SetUp() OVERRIDE {
+    capturer_.reset(WindowCapturer::Create());
+  }
+
+  void TearDown() OVERRIDE {
+  }
+
+  // DesktopCapturer::Callback interface
+  virtual SharedMemory* CreateSharedMemory(size_t size) OVERRIDE {
+    return NULL;
+  }
+
+  virtual void OnCaptureCompleted(DesktopFrame* frame) OVERRIDE {
+    frame_.reset(frame);
+  }
+
+ protected:
+  scoped_ptr<WindowCapturer> capturer_;
+  scoped_ptr<DesktopFrame> frame_;
+};
+
+// Verify that we can enumerate windows.
+TEST_F(WindowCapturerTest, Enumerate) {
+  WindowCapturer::WindowList windows;
+  EXPECT_TRUE(capturer_->GetWindowList(&windows));
+
+  // Assume that there is at least one window.
+  EXPECT_GT(windows.size(), 0U);
+
+  // Verify that window titles are set.
+  for (WindowCapturer::WindowList::iterator it = windows.begin();
+       it != windows.end(); ++it) {
+    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 = windows.begin();
+           new_list_it != windows.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 0000000..f9316c1
--- /dev/null
+++ b/webrtc/modules/desktop_capture/window_capturer_win.cc
@@ -0,0 +1,233 @@
+/*
+ *  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 <cassert>
+#include <windows.h>
+
+#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
+#include "webrtc/system_wrappers/interface/logging.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+namespace webrtc {
+
+namespace {
+
+typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
+
+// Coverts a zero-terminated UTF-16 string to UTF-8. Returns an empty string if
+// error occurs.
+std::string Utf16ToUtf8(const WCHAR* str) {
+  int len_utf8 = WideCharToMultiByte(CP_UTF8, 0, str, -1,
+                                     NULL, 0, NULL, NULL);
+  if (len_utf8 <= 0)
+    return std::string();
+  std::string result(len_utf8, '\0');
+  int rv = WideCharToMultiByte(CP_UTF8, 0, str, -1,
+                               &*(result.begin()), len_utf8, NULL, NULL);
+  if (rv != len_utf8)
+    assert(false);
+
+  return result;
+}
+
+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];
+  GetClassName(hwnd, class_name, kClassLength);
+  // 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;
+
+  WindowCapturer::Window window;
+  window.id = 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 = Utf16ToUtf8(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.
+  virtual bool GetWindowList(WindowList* windows) OVERRIDE;
+  virtual bool SelectWindow(WindowId id) OVERRIDE;
+
+  // DesktopCapturer interface.
+  virtual void Start(Callback* callback) OVERRIDE;
+  virtual void Capture(const DesktopRegion& region) OVERRIDE;
+
+ private:
+  bool IsAeroEnabled();
+
+  Callback* callback_;
+
+  // HWND and HDC for the currently selected window or NULL if window is not
+  // selected.
+  HWND window_;
+  HDC window_dc_;
+
+  // dwmapi.dll is used to determine if desktop compositing is enabled.
+  HMODULE dwmapi_library_;
+  DwmIsCompositionEnabledFunc is_composition_enabled_func_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
+};
+
+WindowCapturerWin::WindowCapturerWin()
+    : callback_(NULL),
+      window_(NULL),
+      window_dc_(NULL) {
+  // Try to load dwmapi.dll dynamically since it is not available on XP.
+  dwmapi_library_ = LoadLibrary(L"dwmapi.dll");
+  if (dwmapi_library_) {
+    is_composition_enabled_func_ =
+        reinterpret_cast<DwmIsCompositionEnabledFunc>(
+            GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled"));
+    assert(is_composition_enabled_func_);
+  } else {
+    is_composition_enabled_func_ = NULL;
+  }
+}
+
+WindowCapturerWin::~WindowCapturerWin() {
+  if (dwmapi_library_)
+    FreeLibrary(dwmapi_library_);
+}
+
+bool WindowCapturerWin::IsAeroEnabled() {
+  BOOL result = FALSE;
+  if (is_composition_enabled_func_)
+    is_composition_enabled_func_(&result);
+  return result != FALSE;
+}
+
+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) {
+  if (window_dc_)
+    ReleaseDC(window_, window_dc_);
+
+  window_ = reinterpret_cast<HWND>(id);
+  window_dc_ = GetWindowDC(window_);
+  if (!window_dc_) {
+    LOG(LS_WARNING) << "Failed to select window: " << GetLastError();
+    window_ = NULL;
+    return false;
+  }
+
+  return true;
+}
+
+void WindowCapturerWin::Start(Callback* callback) {
+  assert(!callback_);
+  assert(callback);
+
+  callback_ = callback;
+}
+
+void WindowCapturerWin::Capture(const DesktopRegion& region) {
+  if (!window_dc_) {
+    LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
+    callback_->OnCaptureCompleted(NULL);
+    return;
+  }
+
+  assert(window_);
+
+  RECT rect;
+  if (!GetWindowRect(window_, &rect)) {
+    LOG(LS_WARNING) << "Failed to get window size: " << GetLastError();
+    callback_->OnCaptureCompleted(NULL);
+    return;
+  }
+
+  scoped_ptr<DesktopFrameWin> frame(DesktopFrameWin::Create(
+      DesktopSize(rect.right - rect.left, rect.bottom - rect.top),
+      NULL, window_dc_));
+
+  HDC mem_dc = CreateCompatibleDC(window_dc_);
+  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.
+
+  if (!IsAeroEnabled())
+    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_, 0, 0, SRCCOPY);
+  }
+
+  DeleteDC(mem_dc);
+
+  if (!result) {
+    LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
+    frame.reset();
+  }
+
+  callback_->OnCaptureCompleted(frame.release());
+}
+
+}  // namespace
+
+// static
+WindowCapturer* WindowCapturer::Create() {
+  return new WindowCapturerWin();
+}
+
+}  // namespace webrtc