blob: 3cb1048861ec8b117bd50a274330238656061220 [file] [log] [blame]
/*
* 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/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/interface/logging.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
namespace webrtc {
namespace {
typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
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 = 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.
virtual bool GetWindowList(WindowList* windows) OVERRIDE;
virtual bool SelectWindow(WindowId id) OVERRIDE;
virtual bool BringSelectedWindowToFront() 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_;
// dwmapi.dll is used to determine if desktop compositing is enabled.
HMODULE dwmapi_library_;
DwmIsCompositionEnabledFunc is_composition_enabled_func_;
DesktopSize previous_size_;
DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
};
WindowCapturerWin::WindowCapturerWin()
: callback_(NULL),
window_(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) {
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;
}
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 (!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