blob: 01b4e4db142f0498a3568b7702399e3ede0fe70f [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
pbos@webrtc.org12dc1a32013-08-05 16:22:53 +000011#include <assert.h>
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000012
kwiberg2bb3afa2016-03-16 15:58:08 -070013#include <memory>
14
zijiehe98903d22016-11-10 21:57:10 -080015#include "webrtc/modules/desktop_capture/desktop_capturer.h"
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000016#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
Zijie He12827112017-08-29 11:19:13 -070017#include "webrtc/modules/desktop_capture/window_finder_win.h"
18#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
jiayl@webrtc.orgc8ac17c2014-03-20 00:06:41 +000019#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
Edward Lemurc20978e2017-07-06 19:44:34 +020020#include "webrtc/rtc_base/checks.h"
21#include "webrtc/rtc_base/constructormagic.h"
22#include "webrtc/rtc_base/logging.h"
23#include "webrtc/rtc_base/win32.h"
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000024
25namespace webrtc {
26
27namespace {
28
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000029BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
zijiehefce49052016-11-07 15:25:18 -080030 DesktopCapturer::SourceList* list =
31 reinterpret_cast<DesktopCapturer::SourceList*>(param);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000032
33 // Skip windows that are invisible, minimized, have no title, or are owned,
34 // unless they have the app window style set.
35 int len = GetWindowTextLength(hwnd);
36 HWND owner = GetWindow(hwnd, GW_OWNER);
37 LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
38 if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
39 (owner && !(exstyle & WS_EX_APPWINDOW))) {
40 return TRUE;
41 }
42
43 // Skip the Program Manager window and the Start button.
44 const size_t kClassLength = 256;
45 WCHAR class_name[kClassLength];
gyzhou371dc7e2015-10-02 15:36:30 -070046 const int class_name_length = GetClassName(hwnd, class_name, kClassLength);
47 RTC_DCHECK(class_name_length)
48 << "Error retrieving the application's class name";
49
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000050 // Skip Program Manager window and the Start button. This is the same logic
51 // that's used in Win32WindowPicker in libjingle. Consider filtering other
52 // windows as well (e.g. toolbars).
53 if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0)
54 return TRUE;
55
gyzhou371dc7e2015-10-02 15:36:30 -070056 // Windows 8 introduced a "Modern App" identified by their class name being
57 // either ApplicationFrameWindow or windows.UI.Core.coreWindow. The
58 // associated windows cannot be captured, so we skip them.
59 // http://crbug.com/526883.
60 if (rtc::IsWindows8OrLater() &&
61 (wcscmp(class_name, L"ApplicationFrameWindow") == 0 ||
62 wcscmp(class_name, L"Windows.UI.Core.CoreWindow") == 0)) {
63 return TRUE;
64 }
65
zijiehefce49052016-11-07 15:25:18 -080066 DesktopCapturer::Source window;
zijieheb68d6552016-10-28 17:35:11 -070067 window.id = reinterpret_cast<WindowId>(hwnd);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000068
69 const size_t kTitleLength = 500;
70 WCHAR window_title[kTitleLength];
71 // Truncate the title if it's longer than kTitleLength.
72 GetWindowText(hwnd, window_title, kTitleLength);
jiayl@webrtc.org047abc92014-08-18 20:48:15 +000073 window.title = rtc::ToUtf8(window_title);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000074
75 // Skip windows when we failed to convert the title or it is empty.
76 if (window.title.empty())
77 return TRUE;
78
79 list->push_back(window);
80
81 return TRUE;
82}
83
Zijie He4fe66072017-08-30 16:58:14 -070084// Retrieves the rectangle of the window rect which is drawable by either OS or
85// the owner application. The returned DesktopRect is in system coordinates.
86// This function returns false if native APIs fail.
87//
88// When |window| is maximized, its borders and shadow effect will be ignored by
89// OS and leave black. So we prefer to use GetCroppedWindowRect() when capturing
90// its content to avoid the black area in the final DesktopFrame. But when the
91// window is in normal mode, borders and shadow should be included.
92bool GetWindowDrawableRect(HWND window,
93 DesktopRect* drawable_rect,
94 DesktopRect* original_rect) {
95 if (!GetWindowRect(window, original_rect)) {
96 return false;
97 }
98
99 bool is_maximized = false;
100 if (!IsWindowMaximized(window, &is_maximized)) {
101 return false;
102 }
103
104 if (is_maximized) {
105 return GetCroppedWindowRect(
106 window, drawable_rect, /* original_rect */ nullptr);
107 }
108 *drawable_rect = *original_rect;
109 return true;
110}
111
zijiehe98903d22016-11-10 21:57:10 -0800112class WindowCapturerWin : public DesktopCapturer {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000113 public:
114 WindowCapturerWin();
sergeyue1831212016-10-26 13:15:42 -0700115 ~WindowCapturerWin() override;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000116
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000117 // DesktopCapturer interface.
kjellander@webrtc.org14665ff2015-03-04 12:58:35 +0000118 void Start(Callback* callback) override;
zijiehe91902cb2016-10-13 16:47:49 -0700119 void CaptureFrame() override;
zijiehefce49052016-11-07 15:25:18 -0800120 bool GetSourceList(SourceList* sources) override;
121 bool SelectSource(SourceId id) override;
122 bool FocusOnSelectedSource() override;
Zijie He12827112017-08-29 11:19:13 -0700123 bool IsOccluded(const DesktopVector& pos) override;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000124
125 private:
sergeyu5d910282016-06-07 16:41:58 -0700126 Callback* callback_ = nullptr;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000127
sergeyu5d910282016-06-07 16:41:58 -0700128 // HWND and HDC for the currently selected window or nullptr if window is not
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000129 // selected.
sergeyu5d910282016-06-07 16:41:58 -0700130 HWND window_ = nullptr;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000131
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000132 DesktopSize previous_size_;
133
Jiayang Liud848d5e2015-07-16 08:49:38 -0700134 AeroChecker aero_checker_;
135
gyzhou77f3e0d2016-02-23 08:57:45 -0800136 // This map is used to avoid flickering for the case when SelectWindow() calls
137 // are interleaved with Capture() calls.
138 std::map<HWND, DesktopSize> window_size_map_;
139
Zijie He12827112017-08-29 11:19:13 -0700140 WindowFinderWin window_finder_;
141
henrikg3c089d72015-09-16 05:37:44 -0700142 RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000143};
144
sergeyu5d910282016-06-07 16:41:58 -0700145WindowCapturerWin::WindowCapturerWin() {}
146WindowCapturerWin::~WindowCapturerWin() {}
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000147
zijiehefce49052016-11-07 15:25:18 -0800148bool WindowCapturerWin::GetSourceList(SourceList* sources) {
149 SourceList result;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000150 LPARAM param = reinterpret_cast<LPARAM>(&result);
Zijie Heb010a322017-08-07 15:25:01 -0700151 // EnumWindows only enumerates root windows.
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000152 if (!EnumWindows(&WindowsEnumerationHandler, param))
153 return false;
zijiehefce49052016-11-07 15:25:18 -0800154 sources->swap(result);
gyzhou77f3e0d2016-02-23 08:57:45 -0800155
156 std::map<HWND, DesktopSize> new_map;
zijiehefce49052016-11-07 15:25:18 -0800157 for (const auto& item : *sources) {
gyzhou77f3e0d2016-02-23 08:57:45 -0800158 HWND hwnd = reinterpret_cast<HWND>(item.id);
159 new_map[hwnd] = window_size_map_[hwnd];
160 }
161 window_size_map_.swap(new_map);
162
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000163 return true;
164}
165
zijiehefce49052016-11-07 15:25:18 -0800166bool WindowCapturerWin::SelectSource(SourceId id) {
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000167 HWND window = reinterpret_cast<HWND>(id);
168 if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000169 return false;
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000170 window_ = window;
gyzhou77f3e0d2016-02-23 08:57:45 -0800171 // When a window is not in the map, window_size_map_[window] will create an
172 // item with DesktopSize (0, 0).
173 previous_size_ = window_size_map_[window];
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000174 return true;
175}
176
zijiehefce49052016-11-07 15:25:18 -0800177bool WindowCapturerWin::FocusOnSelectedSource() {
jiayl@webrtc.org886c94f2014-03-18 17:10:36 +0000178 if (!window_)
179 return false;
180
181 if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_))
182 return false;
183
zijiehebdb8df82016-10-20 16:44:17 -0700184 return BringWindowToTop(window_) != FALSE &&
185 SetForegroundWindow(window_) != FALSE;
jiayl@webrtc.org886c94f2014-03-18 17:10:36 +0000186}
187
Zijie He12827112017-08-29 11:19:13 -0700188bool WindowCapturerWin::IsOccluded(const DesktopVector& pos) {
189 DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left());
190 return reinterpret_cast<HWND>(window_finder_.GetWindowUnderPoint(sys_pos))
191 != window_;
192}
193
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000194void WindowCapturerWin::Start(Callback* callback) {
195 assert(!callback_);
196 assert(callback);
197
198 callback_ = callback;
199}
200
zijiehe91902cb2016-10-13 16:47:49 -0700201void WindowCapturerWin::CaptureFrame() {
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000202 if (!window_) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000203 LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
sergeyu5d910282016-06-07 16:41:58 -0700204 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000205 return;
206 }
207
gyzhouc94bd9b2015-11-10 07:33:51 -0800208 // Stop capturing if the window has been closed.
209 if (!IsWindow(window_)) {
sergeyu5d910282016-06-07 16:41:58 -0700210 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
sergeyu@chromium.org958cdf62013-09-24 22:10:13 +0000211 return;
212 }
213
Zijie Heaf5686a2017-08-25 11:36:52 -0700214 DesktopRect cropped_rect;
Zijie He4fe66072017-08-30 16:58:14 -0700215 DesktopRect original_rect;
216 if (!GetWindowDrawableRect(window_, &cropped_rect, &original_rect)) {
217 LOG(LS_WARNING) << "Failed to get drawable window area: " << GetLastError();
Zijie Heaf5686a2017-08-25 11:36:52 -0700218 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
219 return;
220 }
221
gyzhouc94bd9b2015-11-10 07:33:51 -0800222 // Return a 1x1 black frame if the window is minimized or invisible, to match
223 // behavior on mace. Window can be temporarily invisible during the
224 // transition of full screen mode on/off.
Zijie Heaf5686a2017-08-25 11:36:52 -0700225 if (original_rect.is_empty() ||
226 IsIconic(window_) ||
227 !IsWindowVisible(window_)) {
sergeyu5d910282016-06-07 16:41:58 -0700228 std::unique_ptr<DesktopFrame> frame(
229 new BasicDesktopFrame(DesktopSize(1, 1)));
jiayl@webrtc.org89959962014-09-11 19:33:58 +0000230 memset(frame->data(), 0, frame->stride() * frame->size().height());
231
232 previous_size_ = frame->size();
gyzhou77f3e0d2016-02-23 08:57:45 -0800233 window_size_map_[window_] = previous_size_;
sergeyu5d910282016-06-07 16:41:58 -0700234 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
jiayl@webrtc.org89959962014-09-11 19:33:58 +0000235 return;
236 }
237
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000238 HDC window_dc = GetWindowDC(window_);
239 if (!window_dc) {
240 LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
sergeyu5d910282016-06-07 16:41:58 -0700241 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
sergeyu@chromium.org6a5cc9d2013-09-12 19:17:26 +0000242 return;
243 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000244
Zijie Heaf5686a2017-08-25 11:36:52 -0700245 DesktopSize window_dc_size;
246 if (GetDcSize(window_dc, &window_dc_size)) {
247 // The |window_dc_size| is used to detect the scaling of the original
248 // window. If the application does not support high-DPI settings, it will
249 // be scaled by Windows according to the scaling setting.
250 // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8
251 // So the size of the |window_dc|, i.e. the bitmap we can retrieve from
252 // PrintWindow() or BitBlt() function, will be smaller than
253 // |original_rect| and |cropped_rect|. Part of the captured desktop frame
254 // will be black. See
255 // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for
256 // details.
257
258 // If |window_dc_size| is smaller than |window_rect|, let's resize both
259 // |original_rect| and |cropped_rect| according to the scaling factor.
260 const double vertical_scale =
261 static_cast<double>(window_dc_size.width()) / original_rect.width();
262 const double horizontal_scale =
263 static_cast<double>(window_dc_size.height()) / original_rect.height();
264 original_rect.Scale(vertical_scale, horizontal_scale);
265 cropped_rect.Scale(vertical_scale, horizontal_scale);
266 }
267
kwiberg2bb3afa2016-03-16 15:58:08 -0700268 std::unique_ptr<DesktopFrameWin> frame(
sergeyu5d910282016-06-07 16:41:58 -0700269 DesktopFrameWin::Create(cropped_rect.size(), nullptr, window_dc));
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000270 if (!frame.get()) {
271 ReleaseDC(window_, window_dc);
sergeyu5d910282016-06-07 16:41:58 -0700272 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000273 return;
274 }
275
276 HDC mem_dc = CreateCompatibleDC(window_dc);
sergeyu@chromium.orgad3035f2014-02-07 21:24:04 +0000277 HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000278 BOOL result = FALSE;
279
280 // When desktop composition (Aero) is enabled each window is rendered to a
281 // private buffer allowing BitBlt() to get the window content even if the
282 // window is occluded. PrintWindow() is slower but lets rendering the window
283 // contents to an off-screen device context when Aero is not available.
284 // PrintWindow() is not supported by some applications.
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000285 //
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000286 // If Aero is enabled, we prefer BitBlt() because it's faster and avoids
287 // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
288 // render occluding windows on top of the desired window.
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000289 //
290 // When composition is enabled the DC returned by GetWindowDC() doesn't always
291 // have window frame rendered correctly. Windows renders it only once and then
292 // caches the result between captures. We hack it around by calling
jiayl@webrtc.orgf0fc72f2014-02-27 16:43:12 +0000293 // PrintWindow() whenever window size changes, including the first time of
294 // capturing - it somehow affects what we get from BitBlt() on the subsequent
295 // captures.
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000296
Jiayang Liud848d5e2015-07-16 08:49:38 -0700297 if (!aero_checker_.IsAeroEnabled() || !previous_size_.equals(frame->size())) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000298 result = PrintWindow(window_, mem_dc, 0);
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000299 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000300
301 // Aero is enabled or PrintWindow() failed, use BitBlt.
302 if (!result) {
303 result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
jiayl@webrtc.orgc8ac17c2014-03-20 00:06:41 +0000304 window_dc,
305 cropped_rect.left() - original_rect.left(),
306 cropped_rect.top() - original_rect.top(),
307 SRCCOPY);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000308 }
309
sergeyu@chromium.orgad3035f2014-02-07 21:24:04 +0000310 SelectObject(mem_dc, previous_object);
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000311 DeleteDC(mem_dc);
312 ReleaseDC(window_, window_dc);
313
314 previous_size_ = frame->size();
gyzhou77f3e0d2016-02-23 08:57:45 -0800315 window_size_map_[window_] = previous_size_;
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000316
sergeyu@chromium.orgd4028752014-08-15 23:13:23 +0000317 frame->mutable_updated_region()->SetRect(
318 DesktopRect::MakeSize(frame->size()));
Zijie He7fdf8572017-09-01 11:49:16 -0700319 frame->set_top_left(
320 cropped_rect.top_left().subtract(GetFullscreenRect().top_left()));
sergeyu@chromium.orgd4028752014-08-15 23:13:23 +0000321
zijiehed41af462017-04-20 14:53:36 -0700322 if (result) {
323 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
324 } else {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000325 LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
zijiehed41af462017-04-20 14:53:36 -0700326 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000327 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000328}
329
330} // namespace
331
332// static
zijiehe54fd5792016-11-02 14:49:35 -0700333std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer(
334 const DesktopCaptureOptions& options) {
335 return std::unique_ptr<DesktopCapturer>(new WindowCapturerWin());
336}
337
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000338} // namespace webrtc