• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2020 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 "modules/desktop_capture/win/window_capturer_win_gdi.h"
12 
13 #include <cmath>
14 #include <map>
15 #include <memory>
16 #include <utility>
17 #include <vector>
18 
19 #include "modules/desktop_capture/cropped_desktop_frame.h"
20 #include "modules/desktop_capture/desktop_capture_metrics_helper.h"
21 #include "modules/desktop_capture/desktop_capture_types.h"
22 #include "modules/desktop_capture/desktop_capturer.h"
23 #include "modules/desktop_capture/desktop_frame_win.h"
24 #include "modules/desktop_capture/win/screen_capture_utils.h"
25 #include "modules/desktop_capture/win/selected_window_context.h"
26 #include "rtc_base/arraysize.h"
27 #include "rtc_base/checks.h"
28 #include "rtc_base/logging.h"
29 #include "rtc_base/string_utils.h"
30 #include "rtc_base/time_utils.h"
31 #include "rtc_base/trace_event.h"
32 #include "rtc_base/win/windows_version.h"
33 #include "system_wrappers/include/metrics.h"
34 
35 namespace webrtc {
36 
37 // Used to pass input/output data during the EnumWindows call to collect
38 // owned/pop-up windows that should be captured.
39 struct OwnedWindowCollectorContext : public SelectedWindowContext {
OwnedWindowCollectorContextwebrtc::OwnedWindowCollectorContext40   OwnedWindowCollectorContext(HWND selected_window,
41                               DesktopRect selected_window_rect,
42                               WindowCaptureHelperWin* window_capture_helper,
43                               std::vector<HWND>* owned_windows)
44       : SelectedWindowContext(selected_window,
45                               selected_window_rect,
46                               window_capture_helper),
47         owned_windows(owned_windows) {}
48 
49   std::vector<HWND>* owned_windows;
50 };
51 
52 // Called via EnumWindows for each root window; adds owned/pop-up windows that
53 // should be captured to a vector it's passed.
OwnedWindowCollector(HWND hwnd,LPARAM param)54 BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) {
55   OwnedWindowCollectorContext* context =
56       reinterpret_cast<OwnedWindowCollectorContext*>(param);
57   if (hwnd == context->selected_window()) {
58     // Windows are enumerated in top-down z-order, so we can stop enumerating
59     // upon reaching the selected window.
60     return FALSE;
61   }
62 
63   // Skip windows that aren't visible pop-up windows.
64   if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) ||
65       !context->window_capture_helper()->IsWindowVisibleOnCurrentDesktop(
66           hwnd)) {
67     return TRUE;
68   }
69 
70   // Owned windows that intersect the selected window should be captured.
71   if (context->IsWindowOwnedBySelectedWindow(hwnd) &&
72       context->IsWindowOverlappingSelectedWindow(hwnd)) {
73     // Skip windows that draw shadows around menus. These "SysShadow" windows
74     // would otherwise be captured as solid black bars with no transparency
75     // gradient (since this capturer doesn't detect / respect variations in the
76     // window alpha channel). Any other semi-transparent owned windows will be
77     // captured fully-opaque. This seems preferable to excluding them (at least
78     // when they have content aside from a solid fill color / visual adornment;
79     // e.g. some tooltips have the transparent style set).
80     if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) {
81       const WCHAR kSysShadow[] = L"SysShadow";
82       const size_t kClassLength = arraysize(kSysShadow);
83       WCHAR class_name[kClassLength];
84       const int class_name_length =
85           GetClassNameW(hwnd, class_name, kClassLength);
86       if (class_name_length == kClassLength - 1 &&
87           wcscmp(class_name, kSysShadow) == 0) {
88         return TRUE;
89       }
90     }
91 
92     context->owned_windows->push_back(hwnd);
93   }
94 
95   return TRUE;
96 }
97 
WindowCapturerWinGdi(bool enumerate_current_process_windows)98 WindowCapturerWinGdi::WindowCapturerWinGdi(
99     bool enumerate_current_process_windows)
100     : enumerate_current_process_windows_(enumerate_current_process_windows) {}
~WindowCapturerWinGdi()101 WindowCapturerWinGdi::~WindowCapturerWinGdi() {}
102 
GetSourceList(SourceList * sources)103 bool WindowCapturerWinGdi::GetSourceList(SourceList* sources) {
104   if (!window_capture_helper_.EnumerateCapturableWindows(
105           sources, enumerate_current_process_windows_))
106     return false;
107 
108   std::map<HWND, DesktopSize> new_map;
109   for (const auto& item : *sources) {
110     HWND hwnd = reinterpret_cast<HWND>(item.id);
111     new_map[hwnd] = window_size_map_[hwnd];
112   }
113   window_size_map_.swap(new_map);
114 
115   return true;
116 }
117 
SelectSource(SourceId id)118 bool WindowCapturerWinGdi::SelectSource(SourceId id) {
119   HWND window = reinterpret_cast<HWND>(id);
120   if (!IsWindowValidAndVisible(window))
121     return false;
122 
123   window_ = window;
124   // When a window is not in the map, window_size_map_[window] will create an
125   // item with DesktopSize (0, 0).
126   previous_size_ = window_size_map_[window];
127   return true;
128 }
129 
FocusOnSelectedSource()130 bool WindowCapturerWinGdi::FocusOnSelectedSource() {
131   if (!window_)
132     return false;
133 
134   if (!IsWindowValidAndVisible(window_))
135     return false;
136 
137   return BringWindowToTop(window_) && SetForegroundWindow(window_);
138 }
139 
IsOccluded(const DesktopVector & pos)140 bool WindowCapturerWinGdi::IsOccluded(const DesktopVector& pos) {
141   DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left());
142   HWND hwnd =
143       reinterpret_cast<HWND>(window_finder_.GetWindowUnderPoint(sys_pos));
144 
145   return hwnd != window_ &&
146          std::find(owned_windows_.begin(), owned_windows_.end(), hwnd) ==
147              owned_windows_.end();
148 }
149 
Start(Callback * callback)150 void WindowCapturerWinGdi::Start(Callback* callback) {
151   RTC_DCHECK(!callback_);
152   RTC_DCHECK(callback);
153   RecordCapturerImpl(DesktopCapturerId::kWindowCapturerWinGdi);
154 
155   callback_ = callback;
156 }
157 
CaptureFrame()158 void WindowCapturerWinGdi::CaptureFrame() {
159   RTC_DCHECK(callback_);
160   int64_t capture_start_time_nanos = rtc::TimeNanos();
161 
162   CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true);
163   if (!results.frame) {
164     // Don't return success if we have no frame.
165     results.result = results.result == Result::SUCCESS ? Result::ERROR_TEMPORARY
166                                                        : results.result;
167     callback_->OnCaptureResult(results.result, nullptr);
168     return;
169   }
170 
171   int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
172                         rtc::kNumNanosecsPerMillisec;
173   RTC_HISTOGRAM_COUNTS_1000(
174       "WebRTC.DesktopCapture.Win.WindowGdiCapturerFrameTime", capture_time_ms);
175   results.frame->set_capture_time_ms(capture_time_ms);
176   results.frame->set_capturer_id(DesktopCapturerId::kWindowCapturerWinGdi);
177   callback_->OnCaptureResult(results.result, std::move(results.frame));
178 }
179 
CaptureFrame(bool capture_owned_windows)180 WindowCapturerWinGdi::CaptureResults WindowCapturerWinGdi::CaptureFrame(
181     bool capture_owned_windows) {
182   TRACE_EVENT0("webrtc", "WindowCapturerWinGdi::CaptureFrame");
183 
184   if (!window_) {
185     RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
186     return {Result::ERROR_PERMANENT, nullptr};
187   }
188 
189   // Stop capturing if the window has been closed.
190   if (!IsWindow(window_)) {
191     RTC_LOG(LS_ERROR) << "Target window has been closed.";
192     return {Result::ERROR_PERMANENT, nullptr};
193   }
194 
195   // Determine the window region excluding any resize border, and including
196   // any visible border if capturing an owned window / dialog. (Don't include
197   // any visible border for the selected window for consistency with
198   // CroppingWindowCapturerWin, which would expose a bit of the background
199   // through the partially-transparent border.)
200   const bool avoid_cropping_border = !capture_owned_windows;
201   DesktopRect cropped_rect;
202   DesktopRect original_rect;
203 
204   if (!GetCroppedWindowRect(window_, avoid_cropping_border, &cropped_rect,
205                             &original_rect)) {
206     RTC_LOG(LS_WARNING) << "Failed to get drawable window area: "
207                         << GetLastError();
208     return {Result::ERROR_TEMPORARY, nullptr};
209   }
210 
211   // Return a 1x1 black frame if the window is minimized or invisible on current
212   // desktop, to match behavior on mace. Window can be temporarily invisible
213   // during the transition of full screen mode on/off.
214   if (original_rect.is_empty() ||
215       !window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) {
216     std::unique_ptr<DesktopFrame> frame(
217         new BasicDesktopFrame(DesktopSize(1, 1)));
218 
219     previous_size_ = frame->size();
220     window_size_map_[window_] = previous_size_;
221     return {Result::SUCCESS, std::move(frame)};
222   }
223 
224   HDC window_dc = GetWindowDC(window_);
225   if (!window_dc) {
226     RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
227     return {Result::ERROR_TEMPORARY, nullptr};
228   }
229 
230   DesktopRect unscaled_cropped_rect = cropped_rect;
231   double horizontal_scale = 1.0;
232   double vertical_scale = 1.0;
233 
234   DesktopSize window_dc_size;
235   if (GetDcSize(window_dc, &window_dc_size)) {
236     // The `window_dc_size` is used to detect the scaling of the original
237     // window. If the application does not support high-DPI settings, it will
238     // be scaled by Windows according to the scaling setting.
239     // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8
240     // So the size of the `window_dc`, i.e. the bitmap we can retrieve from
241     // PrintWindow() or BitBlt() function, will be smaller than
242     // `original_rect` and `cropped_rect`. Part of the captured desktop frame
243     // will be black. See
244     // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for
245     // details.
246 
247     // If `window_dc_size` is smaller than `window_rect`, let's resize both
248     // `original_rect` and `cropped_rect` according to the scaling factor.
249     // This will adjust the width and height of the two rects.
250     horizontal_scale =
251         static_cast<double>(window_dc_size.width()) / original_rect.width();
252     vertical_scale =
253         static_cast<double>(window_dc_size.height()) / original_rect.height();
254     original_rect.Scale(horizontal_scale, vertical_scale);
255     cropped_rect.Scale(horizontal_scale, vertical_scale);
256 
257     // Translate `cropped_rect` to the left so that its position within
258     // `original_rect` remains accurate after scaling.
259     // See crbug.com/1083527 for more info.
260     int translate_left = static_cast<int>(std::round(
261         (cropped_rect.left() - original_rect.left()) * (horizontal_scale - 1)));
262     int translate_top = static_cast<int>(std::round(
263         (cropped_rect.top() - original_rect.top()) * (vertical_scale - 1)));
264     cropped_rect.Translate(translate_left, translate_top);
265   }
266 
267   std::unique_ptr<DesktopFrameWin> frame(
268       DesktopFrameWin::Create(original_rect.size(), nullptr, window_dc));
269   if (!frame.get()) {
270     RTC_LOG(LS_WARNING) << "Failed to create frame.";
271     ReleaseDC(window_, window_dc);
272     return {Result::ERROR_TEMPORARY, nullptr};
273   }
274 
275   HDC mem_dc = CreateCompatibleDC(window_dc);
276   HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
277   BOOL result = FALSE;
278 
279   // When desktop composition (Aero) is enabled each window is rendered to a
280   // private buffer allowing BitBlt() to get the window content even if the
281   // window is occluded. PrintWindow() is slower but lets rendering the window
282   // contents to an off-screen device context when Aero is not available.
283   // PrintWindow() is not supported by some applications.
284   //
285   // If Aero is enabled, we prefer BitBlt() because it's faster and avoids
286   // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
287   // render occluding windows on top of the desired window.
288   //
289   // When composition is enabled the DC returned by GetWindowDC() doesn't always
290   // have window frame rendered correctly. Windows renders it only once and then
291   // caches the result between captures. We hack it around by calling
292   // PrintWindow() whenever window size changes, including the first time of
293   // capturing - it somehow affects what we get from BitBlt() on the subsequent
294   // captures.
295   //
296   // For Windows 8.1 and later, we want to always use PrintWindow when the
297   // cropping screen capturer falls back to the window capturer. I.e.
298   // on Windows 8.1 and later, PrintWindow is only used when the window is
299   // occluded. When the window is not occluded, it is much faster to capture
300   // the screen and to crop it to the window position and size.
301   if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) {
302     // Special flag that makes PrintWindow to work on Windows 8.1 and later.
303     // Indeed certain apps (e.g. those using DirectComposition rendering) can't
304     // be captured using BitBlt or PrintWindow without this flag. Note that on
305     // Windows 8.0 this flag is not supported so the block below will fallback
306     // to the other call to PrintWindow. It seems to be very tricky to detect
307     // Windows 8.0 vs 8.1 so a try/fallback is more approriate here.
308     const UINT flags = PW_RENDERFULLCONTENT;
309     result = PrintWindow(window_, mem_dc, flags);
310   }
311 
312   if (!result && (!window_capture_helper_.IsAeroEnabled() ||
313                   !previous_size_.equals(frame->size()))) {
314     result = PrintWindow(window_, mem_dc, 0);
315   }
316 
317   // Aero is enabled or PrintWindow() failed, use BitBlt.
318   if (!result) {
319     result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
320                     window_dc, 0, 0, SRCCOPY);
321   }
322 
323   SelectObject(mem_dc, previous_object);
324   DeleteDC(mem_dc);
325   ReleaseDC(window_, window_dc);
326 
327   previous_size_ = frame->size();
328   window_size_map_[window_] = previous_size_;
329 
330   frame->mutable_updated_region()->SetRect(
331       DesktopRect::MakeSize(frame->size()));
332   frame->set_top_left(
333       original_rect.top_left().subtract(GetFullscreenRect().top_left()));
334 
335   if (!result) {
336     RTC_LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
337     return {Result::ERROR_TEMPORARY, nullptr};
338   }
339 
340   // Rect for the data is relative to the first pixel of the frame.
341   cropped_rect.Translate(-original_rect.left(), -original_rect.top());
342   std::unique_ptr<DesktopFrame> cropped_frame =
343       CreateCroppedDesktopFrame(std::move(frame), cropped_rect);
344   RTC_DCHECK(cropped_frame);
345 
346   if (capture_owned_windows) {
347     // If any owned/pop-up windows overlap the selected window, capture them
348     // and copy/composite their contents into the frame.
349     owned_windows_.clear();
350     OwnedWindowCollectorContext context(window_, unscaled_cropped_rect,
351                                         &window_capture_helper_,
352                                         &owned_windows_);
353 
354     if (context.IsSelectedWindowValid()) {
355       EnumWindows(OwnedWindowCollector, reinterpret_cast<LPARAM>(&context));
356 
357       if (!owned_windows_.empty()) {
358         if (!owned_window_capturer_) {
359           owned_window_capturer_ = std::make_unique<WindowCapturerWinGdi>(
360               enumerate_current_process_windows_);
361         }
362 
363         // Owned windows are stored in top-down z-order, so this iterates in
364         // reverse to capture / draw them in bottom-up z-order
365         for (auto it = owned_windows_.rbegin(); it != owned_windows_.rend();
366              it++) {
367           HWND hwnd = *it;
368           if (owned_window_capturer_->SelectSource(
369                   reinterpret_cast<SourceId>(hwnd))) {
370             CaptureResults results = owned_window_capturer_->CaptureFrame(
371                 /*capture_owned_windows*/ false);
372 
373             if (results.result != DesktopCapturer::Result::SUCCESS) {
374               // Simply log any error capturing an owned/pop-up window without
375               // bubbling it up to the caller (an expected error here is that
376               // the owned/pop-up window was closed; any unexpected errors won't
377               // fail the outer capture).
378               RTC_LOG(LS_INFO) << "Capturing owned window failed (previous "
379                                   "error/warning pertained to that)";
380             } else {
381               // Copy / composite the captured frame into the outer frame. This
382               // may no-op if they no longer intersect (if the owned window was
383               // moved outside the owner bounds since scheduled for capture.)
384               cropped_frame->CopyIntersectingPixelsFrom(
385                   *results.frame, horizontal_scale, vertical_scale);
386             }
387           }
388         }
389       }
390     }
391   }
392 
393   return {Result::SUCCESS, std::move(cropped_frame)};
394 }
395 
396 // static
CreateRawWindowCapturer(const DesktopCaptureOptions & options)397 std::unique_ptr<DesktopCapturer> WindowCapturerWinGdi::CreateRawWindowCapturer(
398     const DesktopCaptureOptions& options) {
399   return std::unique_ptr<DesktopCapturer>(
400       new WindowCapturerWinGdi(options.enumerate_current_process_windows()));
401 }
402 
403 }  // namespace webrtc
404