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
21 namespace webrtc {
22
23 namespace {
24
25 typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
26
WindowsEnumerationHandler(HWND hwnd,LPARAM param)27 BOOL 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
69 class 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
WindowCapturerWin()101 WindowCapturerWin::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
~WindowCapturerWin()116 WindowCapturerWin::~WindowCapturerWin() {
117 if (dwmapi_library_)
118 FreeLibrary(dwmapi_library_);
119 }
120
IsAeroEnabled()121 bool WindowCapturerWin::IsAeroEnabled() {
122 BOOL result = FALSE;
123 if (is_composition_enabled_func_)
124 is_composition_enabled_func_(&result);
125 return result != FALSE;
126 }
127
GetWindowList(WindowList * windows)128 bool 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
SelectWindow(WindowId id)137 bool 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
BringSelectedWindowToFront()146 bool 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
Start(Callback * callback)156 void WindowCapturerWin::Start(Callback* callback) {
157 assert(!callback_);
158 assert(callback);
159
160 callback_ = callback;
161 }
162
Capture(const DesktopRegion & region)163 void 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
Create(const DesktopCaptureOptions & options)264 WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
265 return new WindowCapturerWin();
266 }
267
268 } // namespace webrtc
269