blob: 54b2768aa803fe265d90a6b1f40884aafc795f88 [file] [log] [blame]
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +00001/*
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
pbos@webrtc.org12dc1a32013-08-05 16:22:53 +000013#include <assert.h>
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000014
kwiberg@webrtc.org00b8f6b2015-02-26 14:34:55 +000015#include "webrtc/base/scoped_ptr.h"
gyzhou371dc7e2015-10-02 15:36:30 -070016#include "webrtc/base/checks.h"
jiayl@webrtc.org047abc92014-08-18 20:48:15 +000017#include "webrtc/base/win32.h"
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000018#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
jiayl@webrtc.orgc8ac17c2014-03-20 00:06:41 +000019#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
Henrik Kjellander98f53512015-10-28 18:17:40 +010020#include "webrtc/system_wrappers/include/logging.h"
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000021
22namespace webrtc {
23
24namespace {
25
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000026BOOL 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];
gyzhou371dc7e2015-10-02 15:36:30 -070043 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
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000047 // 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
gyzhou371dc7e2015-10-02 15:36:30 -070053 // 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
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000063 WindowCapturer::Window window;
sergeyu@chromium.orga590b412013-06-17 20:02:21 +000064 window.id = reinterpret_cast<WindowCapturer::WindowId>(hwnd);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000065
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);
jiayl@webrtc.org047abc92014-08-18 20:48:15 +000070 window.title = rtc::ToUtf8(window_title);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000071
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
81class WindowCapturerWin : public WindowCapturer {
82 public:
83 WindowCapturerWin();
84 virtual ~WindowCapturerWin();
85
86 // WindowCapturer interface.
kjellander@webrtc.org14665ff2015-03-04 12:58:35 +000087 bool GetWindowList(WindowList* windows) override;
88 bool SelectWindow(WindowId id) override;
89 bool BringSelectedWindowToFront() override;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000090
91 // DesktopCapturer interface.
kjellander@webrtc.org14665ff2015-03-04 12:58:35 +000092 void Start(Callback* callback) override;
93 void Capture(const DesktopRegion& region) override;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000094
95 private:
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000096 Callback* callback_;
97
98 // HWND and HDC for the currently selected window or NULL if window is not
99 // selected.
100 HWND window_;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000101
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000102 DesktopSize previous_size_;
103
Jiayang Liud848d5e2015-07-16 08:49:38 -0700104 AeroChecker aero_checker_;
105
henrikg3c089d72015-09-16 05:37:44 -0700106 RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000107};
108
109WindowCapturerWin::WindowCapturerWin()
110 : callback_(NULL),
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000111 window_(NULL) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000112}
113
114WindowCapturerWin::~WindowCapturerWin() {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000115}
116
117bool 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
126bool WindowCapturerWin::SelectWindow(WindowId id) {
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000127 HWND window = reinterpret_cast<HWND>(id);
128 if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000129 return false;
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000130 window_ = window;
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000131 previous_size_.set(0, 0);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000132 return true;
133}
134
jiayl@webrtc.org886c94f2014-03-18 17:10:36 +0000135bool 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
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000145void WindowCapturerWin::Start(Callback* callback) {
146 assert(!callback_);
147 assert(callback);
148
149 callback_ = callback;
150}
151
152void WindowCapturerWin::Capture(const DesktopRegion& region) {
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000153 if (!window_) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000154 LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
155 callback_->OnCaptureCompleted(NULL);
156 return;
157 }
158
gyzhouc94bd9b2015-11-10 07:33:51 -0800159 // Stop capturing if the window has been closed.
160 if (!IsWindow(window_)) {
sergeyu@chromium.org958cdf62013-09-24 22:10:13 +0000161 callback_->OnCaptureCompleted(NULL);
162 return;
163 }
164
gyzhouc94bd9b2015-11-10 07:33:51 -0800165 // 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_)) {
jiayl@webrtc.orgd91608d2014-09-17 16:12:49 +0000169 BasicDesktopFrame* frame = new BasicDesktopFrame(DesktopSize(1, 1));
jiayl@webrtc.org89959962014-09-11 19:33:58 +0000170 memset(frame->data(), 0, frame->stride() * frame->size().height());
171
172 previous_size_ = frame->size();
173 callback_->OnCaptureCompleted(frame);
174 return;
175 }
176
jiayl@webrtc.orgc8ac17c2014-03-20 00:06:41 +0000177 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();
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000181 callback_->OnCaptureCompleted(NULL);
182 return;
183 }
184
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000185 HDC window_dc = GetWindowDC(window_);
186 if (!window_dc) {
187 LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
sergeyu@chromium.org6a5cc9d2013-09-12 19:17:26 +0000188 callback_->OnCaptureCompleted(NULL);
189 return;
190 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000191
kwiberg@webrtc.org00b8f6b2015-02-26 14:34:55 +0000192 rtc::scoped_ptr<DesktopFrameWin> frame(
193 DesktopFrameWin::Create(cropped_rect.size(), NULL, window_dc));
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000194 if (!frame.get()) {
195 ReleaseDC(window_, window_dc);
196 callback_->OnCaptureCompleted(NULL);
197 return;
198 }
199
200 HDC mem_dc = CreateCompatibleDC(window_dc);
sergeyu@chromium.orgad3035f2014-02-07 21:24:04 +0000201 HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000202 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.
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000209 //
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000210 // 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.
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000213 //
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
jiayl@webrtc.orgf0fc72f2014-02-27 16:43:12 +0000217 // 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.
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000220
Jiayang Liud848d5e2015-07-16 08:49:38 -0700221 if (!aero_checker_.IsAeroEnabled() || !previous_size_.equals(frame->size())) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000222 result = PrintWindow(window_, mem_dc, 0);
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000223 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000224
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(),
jiayl@webrtc.orgc8ac17c2014-03-20 00:06:41 +0000228 window_dc,
229 cropped_rect.left() - original_rect.left(),
230 cropped_rect.top() - original_rect.top(),
231 SRCCOPY);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000232 }
233
sergeyu@chromium.orgad3035f2014-02-07 21:24:04 +0000234 SelectObject(mem_dc, previous_object);
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000235 DeleteDC(mem_dc);
236 ReleaseDC(window_, window_dc);
237
238 previous_size_ = frame->size();
239
sergeyu@chromium.orgd4028752014-08-15 23:13:23 +0000240 frame->mutable_updated_region()->SetRect(
241 DesktopRect::MakeSize(frame->size()));
242
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000243 if (!result) {
244 LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
245 frame.reset();
246 }
247
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000248 callback_->OnCaptureCompleted(frame.release());
249}
250
251} // namespace
252
253// static
sergeyu@chromium.org894e6fe2013-10-12 22:40:05 +0000254WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000255 return new WindowCapturerWin();
256}
257
258} // namespace webrtc