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/win/windows_version.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::__anon7aa77fe00111::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::__anon7aa77fe00111::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_NOTREACHED();
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 enumerate_current_process_windows_(
134 options.enumerate_current_process_windows()),
135 full_screen_window_detector_(options.full_screen_window_detector()) {}
136
137 void CaptureFrame() override;
138
139 private:
140 bool ShouldUseScreenCapturer() override;
141 DesktopRect GetWindowRectInVirtualScreen() override;
142
143 // Returns either selected by user sourceId or sourceId provided by
144 // FullScreenWindowDetector
145 WindowId GetWindowToCapture() const;
146
147 // The region from GetWindowRgn in the desktop coordinate if the region is
148 // rectangular, or the rect from GetWindowRect if the region is not set.
149 DesktopRect window_region_rect_;
150
151 WindowCaptureHelperWin window_capture_helper_;
152
153 bool enumerate_current_process_windows_;
154
155 rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_;
156 };
157
CaptureFrame()158 void CroppingWindowCapturerWin::CaptureFrame() {
159 DesktopCapturer* win_capturer = window_capturer();
160 if (win_capturer) {
161 // Feed the actual list of windows into full screen window detector.
162 if (full_screen_window_detector_) {
163 full_screen_window_detector_->UpdateWindowListIfNeeded(
164 selected_window(), [this](DesktopCapturer::SourceList* sources) {
165 // Get the list of top level windows, including ones with empty
166 // title. win_capturer_->GetSourceList can't be used here
167 // cause it filters out the windows with empty titles and
168 // it uses responsiveness check which could lead to performance
169 // issues.
170 SourceList result;
171 int window_list_flags =
172 enumerate_current_process_windows_
173 ? GetWindowListFlags::kNone
174 : GetWindowListFlags::kIgnoreCurrentProcessWindows;
175
176 if (!webrtc::GetWindowList(window_list_flags, &result))
177 return false;
178
179 // Filter out windows not visible on current desktop
180 auto it = std::remove_if(
181 result.begin(), result.end(), [this](const auto& source) {
182 HWND hwnd = reinterpret_cast<HWND>(source.id);
183 return !window_capture_helper_
184 .IsWindowVisibleOnCurrentDesktop(hwnd);
185 });
186 result.erase(it, result.end());
187
188 sources->swap(result);
189 return true;
190 });
191 }
192 win_capturer->SelectSource(GetWindowToCapture());
193 }
194
195 CroppingWindowCapturer::CaptureFrame();
196 }
197
ShouldUseScreenCapturer()198 bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
199 if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN8 &&
200 window_capture_helper_.IsAeroEnabled()) {
201 return false;
202 }
203
204 const HWND selected = reinterpret_cast<HWND>(GetWindowToCapture());
205 // Check if the window is visible on current desktop.
206 if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
207 return false;
208 }
209
210 // Check if the window is a translucent layered window.
211 const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
212 if (window_ex_style & WS_EX_LAYERED) {
213 COLORREF color_ref_key = 0;
214 BYTE alpha = 0;
215 DWORD flags = 0;
216
217 // GetLayeredWindowAttributes fails if the window was setup with
218 // UpdateLayeredWindow. We have no way to know the opacity of the window in
219 // that case. This happens for Stiky Note (crbug/412726).
220 if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
221 return false;
222
223 // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
224 // the previous GetLayeredWindowAttributes to fail. So we only need to check
225 // the window wide color key or alpha.
226 if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
227 return false;
228 }
229 }
230
231 if (!GetWindowRect(selected, &window_region_rect_)) {
232 return false;
233 }
234
235 DesktopRect content_rect;
236 if (!GetWindowContentRect(selected, &content_rect)) {
237 return false;
238 }
239
240 DesktopRect region_rect;
241 // Get the window region and check if it is rectangular.
242 const int region_type =
243 GetWindowRegionTypeWithBoundary(selected, ®ion_rect);
244
245 // Do not use the screen capturer if the region is empty or not rectangular.
246 if (region_type == COMPLEXREGION || region_type == NULLREGION) {
247 return false;
248 }
249
250 if (region_type == SIMPLEREGION) {
251 // The `region_rect` returned from GetRgnBox() is always in window
252 // coordinate.
253 region_rect.Translate(window_region_rect_.left(),
254 window_region_rect_.top());
255 // MSDN: The window region determines the area *within* the window where the
256 // system permits drawing.
257 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
258 //
259 // `region_rect` should always be inside of `window_region_rect_`. So after
260 // the intersection, `window_region_rect_` == `region_rect`. If so, what's
261 // the point of the intersecting operations? Why cannot we directly retrieve
262 // `window_region_rect_` from GetWindowRegionTypeWithBoundary() function?
263 // TODO(zijiehe): Figure out the purpose of these intersections.
264 window_region_rect_.IntersectWith(region_rect);
265 content_rect.IntersectWith(region_rect);
266 }
267
268 // Check if the client area is out of the screen area. When the window is
269 // maximized, only its client area is visible in the screen, the border will
270 // be hidden. So we are using `content_rect` here.
271 if (!GetFullscreenRect().ContainsRect(content_rect)) {
272 return false;
273 }
274
275 // Check if the window is occluded by any other window, excluding the child
276 // windows, context menus, and `excluded_window_`.
277 // `content_rect` is preferred, see the comments on
278 // IsWindowIntersectWithSelectedWindow().
279 TopWindowVerifierContext context(selected,
280 reinterpret_cast<HWND>(excluded_window()),
281 content_rect, &window_capture_helper_);
282 return context.IsTopWindow();
283 }
284
GetWindowRectInVirtualScreen()285 DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
286 TRACE_EVENT0("webrtc",
287 "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
288 DesktopRect window_rect;
289 HWND hwnd = reinterpret_cast<HWND>(GetWindowToCapture());
290 if (!GetCroppedWindowRect(hwnd, /*avoid_cropping_border*/ false, &window_rect,
291 /*original_rect*/ nullptr)) {
292 RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
293 return window_rect;
294 }
295 window_rect.IntersectWith(window_region_rect_);
296
297 // Convert `window_rect` to be relative to the top-left of the virtual screen.
298 DesktopRect screen_rect(GetFullscreenRect());
299 window_rect.IntersectWith(screen_rect);
300 window_rect.Translate(-screen_rect.left(), -screen_rect.top());
301 return window_rect;
302 }
303
GetWindowToCapture() const304 WindowId CroppingWindowCapturerWin::GetWindowToCapture() const {
305 const auto selected_source = selected_window();
306 const auto full_screen_source =
307 full_screen_window_detector_
308 ? full_screen_window_detector_->FindFullScreenWindow(selected_source)
309 : 0;
310 return full_screen_source ? full_screen_source : selected_source;
311 }
312
313 } // namespace
314
315 // static
CreateCapturer(const DesktopCaptureOptions & options)316 std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
317 const DesktopCaptureOptions& options) {
318 std::unique_ptr<DesktopCapturer> capturer(
319 new CroppingWindowCapturerWin(options));
320 if (capturer && options.detect_updated_region()) {
321 capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
322 }
323
324 return capturer;
325 }
326
327 } // namespace webrtc
328