1 /*
2 * Copyright (c) 2014 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/cropping_window_capturer.h"
12 #include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
13 #include "modules/desktop_capture/win/screen_capture_utils.h"
14 #include "modules/desktop_capture/win/selected_window_context.h"
15 #include "modules/desktop_capture/win/window_capture_utils.h"
16 #include "rtc_base/logging.h"
17 #include "rtc_base/trace_event.h"
18 #include "rtc_base/win32.h"
19
20 namespace webrtc {
21
22 namespace {
23
24 // Used to pass input data for verifying the selected window is on top.
25 struct TopWindowVerifierContext : public SelectedWindowContext {
TopWindowVerifierContextwebrtc::__anona23449c80111::TopWindowVerifierContext26 TopWindowVerifierContext(HWND selected_window,
27 HWND excluded_window,
28 DesktopRect selected_window_rect,
29 WindowCaptureHelperWin* window_capture_helper)
30 : SelectedWindowContext(selected_window,
31 selected_window_rect,
32 window_capture_helper),
33 excluded_window(excluded_window) {
34 RTC_DCHECK_NE(selected_window, excluded_window);
35 }
36
37 // Determines whether the selected window is on top (not occluded by any
38 // windows except for those it owns or any excluded window).
IsTopWindowwebrtc::__anona23449c80111::TopWindowVerifierContext39 bool IsTopWindow() {
40 if (!IsSelectedWindowValid()) {
41 return false;
42 }
43
44 // Enumerate all top-level windows above the selected window in Z-order,
45 // checking whether any overlaps it. This uses FindWindowEx rather than
46 // EnumWindows because the latter excludes certain system windows (e.g. the
47 // Start menu & other taskbar menus) that should be detected here to avoid
48 // inadvertent capture.
49 int num_retries = 0;
50 while (true) {
51 HWND hwnd = nullptr;
52 while ((hwnd = FindWindowEx(nullptr, hwnd, nullptr, nullptr))) {
53 if (hwnd == selected_window()) {
54 // Windows are enumerated in top-down Z-order, so we can stop
55 // enumerating upon reaching the selected window & report it's on top.
56 return true;
57 }
58
59 // Ignore the excluded window.
60 if (hwnd == excluded_window) {
61 continue;
62 }
63
64 // Ignore windows that aren't visible on the current desktop.
65 if (!window_capture_helper()->IsWindowVisibleOnCurrentDesktop(hwnd)) {
66 continue;
67 }
68
69 // Ignore Chrome notification windows, especially the notification for
70 // the ongoing window sharing. Notes:
71 // - This only works with notifications from Chrome, not other Apps.
72 // - All notifications from Chrome will be ignored.
73 // - This may cause part or whole of notification window being cropped
74 // into the capturing of the target window if there is overlapping.
75 if (window_capture_helper()->IsWindowChromeNotification(hwnd)) {
76 continue;
77 }
78
79 // Ignore windows owned by the selected window since we want to capture
80 // them.
81 if (IsWindowOwnedBySelectedWindow(hwnd)) {
82 continue;
83 }
84
85 // Check whether this window intersects with the selected window.
86 if (IsWindowOverlappingSelectedWindow(hwnd)) {
87 // If intersection is not empty, the selected window is not on top.
88 return false;
89 }
90 }
91
92 DWORD lastError = GetLastError();
93 if (lastError == ERROR_SUCCESS) {
94 // The enumeration completed successfully without finding the selected
95 // window (which may have been closed).
96 RTC_LOG(LS_WARNING) << "Failed to find selected window (only expected "
97 "if it was closed)";
98 RTC_DCHECK(!IsWindow(selected_window()));
99 return false;
100 } else if (lastError == ERROR_INVALID_WINDOW_HANDLE) {
101 // This error may occur if a window is closed around the time it's
102 // enumerated; retry the enumeration in this case up to 10 times
103 // (this should be a rare race & unlikely to recur).
104 if (++num_retries <= 10) {
105 RTC_LOG(LS_WARNING) << "Enumeration failed due to race with a window "
106 "closing; retrying - retry #"
107 << num_retries;
108 continue;
109 } else {
110 RTC_LOG(LS_ERROR)
111 << "Exhausted retry allowance around window enumeration failures "
112 "due to races with windows closing";
113 }
114 }
115
116 // The enumeration failed with an unexpected error (or more repeats of
117 // an infrequently-expected error than anticipated). After logging this &
118 // firing an assert when enabled, report that the selected window isn't
119 // topmost to avoid inadvertent capture of other windows.
120 RTC_LOG(LS_ERROR) << "Failed to enumerate windows: " << lastError;
121 RTC_DCHECK(false);
122 return false;
123 }
124 }
125
126 const HWND excluded_window;
127 };
128
129 class CroppingWindowCapturerWin : public CroppingWindowCapturer {
130 public:
CroppingWindowCapturerWin(const DesktopCaptureOptions & options)131 explicit CroppingWindowCapturerWin(const DesktopCaptureOptions& options)
132 : CroppingWindowCapturer(options),
133 full_screen_window_detector_(options.full_screen_window_detector()) {}
134
135 void CaptureFrame() override;
136
137 private:
138 bool ShouldUseScreenCapturer() override;
139 DesktopRect GetWindowRectInVirtualScreen() override;
140
141 // Returns either selected by user sourceId or sourceId provided by
142 // FullScreenWindowDetector
143 WindowId GetWindowToCapture() const;
144
145 // The region from GetWindowRgn in the desktop coordinate if the region is
146 // rectangular, or the rect from GetWindowRect if the region is not set.
147 DesktopRect window_region_rect_;
148
149 WindowCaptureHelperWin window_capture_helper_;
150
151 rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_;
152 };
153
CaptureFrame()154 void CroppingWindowCapturerWin::CaptureFrame() {
155 DesktopCapturer* win_capturer = window_capturer();
156 if (win_capturer) {
157 // Feed the actual list of windows into full screen window detector.
158 if (full_screen_window_detector_) {
159 full_screen_window_detector_->UpdateWindowListIfNeeded(
160 selected_window(), [this](DesktopCapturer::SourceList* sources) {
161 // Get the list of top level windows, including ones with empty
162 // title. win_capturer_->GetSourceList can't be used here
163 // cause it filters out the windows with empty titles and
164 // it uses responsiveness check which could lead to performance
165 // issues.
166 SourceList result;
167 if (!webrtc::GetWindowList(GetWindowListFlags::kNone, &result))
168 return false;
169
170 // Filter out windows not visible on current desktop
171 auto it = std::remove_if(
172 result.begin(), result.end(), [this](const auto& source) {
173 HWND hwnd = reinterpret_cast<HWND>(source.id);
174 return !window_capture_helper_
175 .IsWindowVisibleOnCurrentDesktop(hwnd);
176 });
177 result.erase(it, result.end());
178
179 sources->swap(result);
180 return true;
181 });
182 }
183 win_capturer->SelectSource(GetWindowToCapture());
184 }
185
186 CroppingWindowCapturer::CaptureFrame();
187 }
188
ShouldUseScreenCapturer()189 bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
190 if (!rtc::IsWindows8OrLater() && window_capture_helper_.IsAeroEnabled()) {
191 return false;
192 }
193
194 const HWND selected = reinterpret_cast<HWND>(GetWindowToCapture());
195 // Check if the window is visible on current desktop.
196 if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
197 return false;
198 }
199
200 // Check if the window is a translucent layered window.
201 const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
202 if (window_ex_style & WS_EX_LAYERED) {
203 COLORREF color_ref_key = 0;
204 BYTE alpha = 0;
205 DWORD flags = 0;
206
207 // GetLayeredWindowAttributes fails if the window was setup with
208 // UpdateLayeredWindow. We have no way to know the opacity of the window in
209 // that case. This happens for Stiky Note (crbug/412726).
210 if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
211 return false;
212
213 // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
214 // the previous GetLayeredWindowAttributes to fail. So we only need to check
215 // the window wide color key or alpha.
216 if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
217 return false;
218 }
219 }
220
221 if (!GetWindowRect(selected, &window_region_rect_)) {
222 return false;
223 }
224
225 DesktopRect content_rect;
226 if (!GetWindowContentRect(selected, &content_rect)) {
227 return false;
228 }
229
230 DesktopRect region_rect;
231 // Get the window region and check if it is rectangular.
232 const int region_type =
233 GetWindowRegionTypeWithBoundary(selected, ®ion_rect);
234
235 // Do not use the screen capturer if the region is empty or not rectangular.
236 if (region_type == COMPLEXREGION || region_type == NULLREGION) {
237 return false;
238 }
239
240 if (region_type == SIMPLEREGION) {
241 // The |region_rect| returned from GetRgnBox() is always in window
242 // coordinate.
243 region_rect.Translate(window_region_rect_.left(),
244 window_region_rect_.top());
245 // MSDN: The window region determines the area *within* the window where the
246 // system permits drawing.
247 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
248 //
249 // |region_rect| should always be inside of |window_region_rect_|. So after
250 // the intersection, |window_region_rect_| == |region_rect|. If so, what's
251 // the point of the intersecting operations? Why cannot we directly retrieve
252 // |window_region_rect_| from GetWindowRegionTypeWithBoundary() function?
253 // TODO(zijiehe): Figure out the purpose of these intersections.
254 window_region_rect_.IntersectWith(region_rect);
255 content_rect.IntersectWith(region_rect);
256 }
257
258 // Check if the client area is out of the screen area. When the window is
259 // maximized, only its client area is visible in the screen, the border will
260 // be hidden. So we are using |content_rect| here.
261 if (!GetFullscreenRect().ContainsRect(content_rect)) {
262 return false;
263 }
264
265 // Check if the window is occluded by any other window, excluding the child
266 // windows, context menus, and |excluded_window_|.
267 // |content_rect| is preferred, see the comments on
268 // IsWindowIntersectWithSelectedWindow().
269 TopWindowVerifierContext context(selected,
270 reinterpret_cast<HWND>(excluded_window()),
271 content_rect, &window_capture_helper_);
272 return context.IsTopWindow();
273 }
274
GetWindowRectInVirtualScreen()275 DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
276 TRACE_EVENT0("webrtc",
277 "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
278 DesktopRect window_rect;
279 HWND hwnd = reinterpret_cast<HWND>(GetWindowToCapture());
280 if (!GetCroppedWindowRect(hwnd, /*avoid_cropping_border*/ false, &window_rect,
281 /*original_rect*/ nullptr)) {
282 RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
283 return window_rect;
284 }
285 window_rect.IntersectWith(window_region_rect_);
286
287 // Convert |window_rect| to be relative to the top-left of the virtual screen.
288 DesktopRect screen_rect(GetFullscreenRect());
289 window_rect.IntersectWith(screen_rect);
290 window_rect.Translate(-screen_rect.left(), -screen_rect.top());
291 return window_rect;
292 }
293
GetWindowToCapture() const294 WindowId CroppingWindowCapturerWin::GetWindowToCapture() const {
295 const auto selected_source = selected_window();
296 const auto full_screen_source =
297 full_screen_window_detector_
298 ? full_screen_window_detector_->FindFullScreenWindow(selected_source)
299 : 0;
300 return full_screen_source ? full_screen_source : selected_source;
301 }
302
303 } // namespace
304
305 // static
CreateCapturer(const DesktopCaptureOptions & options)306 std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
307 const DesktopCaptureOptions& options) {
308 std::unique_ptr<DesktopCapturer> capturer(
309 new CroppingWindowCapturerWin(options));
310 if (capturer && options.detect_updated_region()) {
311 capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
312 }
313
314 return capturer;
315 }
316
317 } // namespace webrtc
318