diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index b9778dcdcb..d34cd64b4a 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 0000000000..65b27eda07 --- /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 +#include + +#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 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 0000000000..5229009c06 --- /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 + +#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 0000000000..a372c3ba93 --- /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 + +#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 0000000000..6f75f21341 --- /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 capturer_; + scoped_ptr 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 0000000000..f9316c14d1 --- /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 +#include + +#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(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( + 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(&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(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 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