1/*
2 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/modules/desktop_capture/window_capturer.h"
12
13#include <assert.h>
14
15#include "webrtc/base/win32.h"
16#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
17#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
18#include "webrtc/system_wrappers/interface/logging.h"
19#include "webrtc/system_wrappers/interface/scoped_ptr.h"
20
21namespace webrtc {
22
23namespace {
24
25typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
26
27BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
28  WindowCapturer::WindowList* list =
29      reinterpret_cast<WindowCapturer::WindowList*>(param);
30
31  // Skip windows that are invisible, minimized, have no title, or are owned,
32  // unless they have the app window style set.
33  int len = GetWindowTextLength(hwnd);
34  HWND owner = GetWindow(hwnd, GW_OWNER);
35  LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
36  if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
37      (owner && !(exstyle & WS_EX_APPWINDOW))) {
38    return TRUE;
39  }
40
41  // Skip the Program Manager window and the Start button.
42  const size_t kClassLength = 256;
43  WCHAR class_name[kClassLength];
44  GetClassName(hwnd, class_name, kClassLength);
45  // Skip Program Manager window and the Start button. This is the same logic
46  // that's used in Win32WindowPicker in libjingle. Consider filtering other
47  // windows as well (e.g. toolbars).
48  if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0)
49    return TRUE;
50
51  WindowCapturer::Window window;
52  window.id = reinterpret_cast<WindowCapturer::WindowId>(hwnd);
53
54  const size_t kTitleLength = 500;
55  WCHAR window_title[kTitleLength];
56  // Truncate the title if it's longer than kTitleLength.
57  GetWindowText(hwnd, window_title, kTitleLength);
58  window.title = rtc::ToUtf8(window_title);
59
60  // Skip windows when we failed to convert the title or it is empty.
61  if (window.title.empty())
62    return TRUE;
63
64  list->push_back(window);
65
66  return TRUE;
67}
68
69class WindowCapturerWin : public WindowCapturer {
70 public:
71  WindowCapturerWin();
72  virtual ~WindowCapturerWin();
73
74  // WindowCapturer interface.
75  virtual bool GetWindowList(WindowList* windows) OVERRIDE;
76  virtual bool SelectWindow(WindowId id) OVERRIDE;
77  virtual bool BringSelectedWindowToFront() OVERRIDE;
78
79  // DesktopCapturer interface.
80  virtual void Start(Callback* callback) OVERRIDE;
81  virtual void Capture(const DesktopRegion& region) OVERRIDE;
82
83 private:
84  bool IsAeroEnabled();
85
86  Callback* callback_;
87
88  // HWND and HDC for the currently selected window or NULL if window is not
89  // selected.
90  HWND window_;
91
92  // dwmapi.dll is used to determine if desktop compositing is enabled.
93  HMODULE dwmapi_library_;
94  DwmIsCompositionEnabledFunc is_composition_enabled_func_;
95
96  DesktopSize previous_size_;
97
98  DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
99};
100
101WindowCapturerWin::WindowCapturerWin()
102    : callback_(NULL),
103      window_(NULL) {
104  // Try to load dwmapi.dll dynamically since it is not available on XP.
105  dwmapi_library_ = LoadLibrary(L"dwmapi.dll");
106  if (dwmapi_library_) {
107    is_composition_enabled_func_ =
108        reinterpret_cast<DwmIsCompositionEnabledFunc>(
109            GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled"));
110    assert(is_composition_enabled_func_);
111  } else {
112    is_composition_enabled_func_ = NULL;
113  }
114}
115
116WindowCapturerWin::~WindowCapturerWin() {
117  if (dwmapi_library_)
118    FreeLibrary(dwmapi_library_);
119}
120
121bool WindowCapturerWin::IsAeroEnabled() {
122  BOOL result = FALSE;
123  if (is_composition_enabled_func_)
124    is_composition_enabled_func_(&result);
125  return result != FALSE;
126}
127
128bool WindowCapturerWin::GetWindowList(WindowList* windows) {
129  WindowList result;
130  LPARAM param = reinterpret_cast<LPARAM>(&result);
131  if (!EnumWindows(&WindowsEnumerationHandler, param))
132    return false;
133  windows->swap(result);
134  return true;
135}
136
137bool WindowCapturerWin::SelectWindow(WindowId id) {
138  HWND window = reinterpret_cast<HWND>(id);
139  if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
140    return false;
141  window_ = window;
142  previous_size_.set(0, 0);
143  return true;
144}
145
146bool WindowCapturerWin::BringSelectedWindowToFront() {
147  if (!window_)
148    return false;
149
150  if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_))
151    return false;
152
153  return SetForegroundWindow(window_) != 0;
154}
155
156void WindowCapturerWin::Start(Callback* callback) {
157  assert(!callback_);
158  assert(callback);
159
160  callback_ = callback;
161}
162
163void WindowCapturerWin::Capture(const DesktopRegion& region) {
164  if (!window_) {
165    LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
166    callback_->OnCaptureCompleted(NULL);
167    return;
168  }
169
170  // Stop capturing if the window has been closed or hidden.
171  if (!IsWindow(window_) || !IsWindowVisible(window_)) {
172    callback_->OnCaptureCompleted(NULL);
173    return;
174  }
175
176  // Return a 1x1 black frame if the window is minimized, to match the behavior
177  // on Mac.
178  if (IsIconic(window_)) {
179    BasicDesktopFrame* frame = new BasicDesktopFrame(DesktopSize(1, 1));
180    memset(frame->data(), 0, frame->stride() * frame->size().height());
181
182    previous_size_ = frame->size();
183    callback_->OnCaptureCompleted(frame);
184    return;
185  }
186
187  DesktopRect original_rect;
188  DesktopRect cropped_rect;
189  if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) {
190    LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
191    callback_->OnCaptureCompleted(NULL);
192    return;
193  }
194
195  HDC window_dc = GetWindowDC(window_);
196  if (!window_dc) {
197    LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
198    callback_->OnCaptureCompleted(NULL);
199    return;
200  }
201
202  scoped_ptr<DesktopFrameWin> frame(DesktopFrameWin::Create(
203      cropped_rect.size(), NULL, window_dc));
204  if (!frame.get()) {
205    ReleaseDC(window_, window_dc);
206    callback_->OnCaptureCompleted(NULL);
207    return;
208  }
209
210  HDC mem_dc = CreateCompatibleDC(window_dc);
211  HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
212  BOOL result = FALSE;
213
214  // When desktop composition (Aero) is enabled each window is rendered to a
215  // private buffer allowing BitBlt() to get the window content even if the
216  // window is occluded. PrintWindow() is slower but lets rendering the window
217  // contents to an off-screen device context when Aero is not available.
218  // PrintWindow() is not supported by some applications.
219  //
220  // If Aero is enabled, we prefer BitBlt() because it's faster and avoids
221  // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
222  // render occluding windows on top of the desired window.
223  //
224  // When composition is enabled the DC returned by GetWindowDC() doesn't always
225  // have window frame rendered correctly. Windows renders it only once and then
226  // caches the result between captures. We hack it around by calling
227  // PrintWindow() whenever window size changes, including the first time of
228  // capturing - it somehow affects what we get from BitBlt() on the subsequent
229  // captures.
230
231  if (!IsAeroEnabled() || !previous_size_.equals(frame->size())) {
232    result = PrintWindow(window_, mem_dc, 0);
233  }
234
235  // Aero is enabled or PrintWindow() failed, use BitBlt.
236  if (!result) {
237    result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
238                    window_dc,
239                    cropped_rect.left() - original_rect.left(),
240                    cropped_rect.top() - original_rect.top(),
241                    SRCCOPY);
242  }
243
244  SelectObject(mem_dc, previous_object);
245  DeleteDC(mem_dc);
246  ReleaseDC(window_, window_dc);
247
248  previous_size_ = frame->size();
249
250  frame->mutable_updated_region()->SetRect(
251      DesktopRect::MakeSize(frame->size()));
252
253  if (!result) {
254    LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
255    frame.reset();
256  }
257
258  callback_->OnCaptureCompleted(frame.release());
259}
260
261}  // namespace
262
263// static
264WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
265  return new WindowCapturerWin();
266}
267
268}  // namespace webrtc
269