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