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::__anon4678f0710111::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::__anon4678f0710111::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 // Update the list of available sources and override source to capture if
158 // FullScreenWindowDetector returns not zero
159 if (full_screen_window_detector_) {
160 full_screen_window_detector_->UpdateWindowListIfNeeded(
161 selected_window(),
162 [win_capturer](DesktopCapturer::SourceList* sources) {
163 return win_capturer->GetSourceList(sources);
164 });
165 }
166 win_capturer->SelectSource(GetWindowToCapture());
167 }
168
169 CroppingWindowCapturer::CaptureFrame();
170 }
171
ShouldUseScreenCapturer()172 bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
173 if (!rtc::IsWindows8OrLater() && window_capture_helper_.IsAeroEnabled()) {
174 return false;
175 }
176
177 const HWND selected = reinterpret_cast<HWND>(GetWindowToCapture());
178 // Check if the window is visible on current desktop.
179 if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
180 return false;
181 }
182
183 // Check if the window is a translucent layered window.
184 const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
185 if (window_ex_style & WS_EX_LAYERED) {
186 COLORREF color_ref_key = 0;
187 BYTE alpha = 0;
188 DWORD flags = 0;
189
190 // GetLayeredWindowAttributes fails if the window was setup with
191 // UpdateLayeredWindow. We have no way to know the opacity of the window in
192 // that case. This happens for Stiky Note (crbug/412726).
193 if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
194 return false;
195
196 // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
197 // the previous GetLayeredWindowAttributes to fail. So we only need to check
198 // the window wide color key or alpha.
199 if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
200 return false;
201 }
202 }
203
204 if (!GetWindowRect(selected, &window_region_rect_)) {
205 return false;
206 }
207
208 DesktopRect content_rect;
209 if (!GetWindowContentRect(selected, &content_rect)) {
210 return false;
211 }
212
213 DesktopRect region_rect;
214 // Get the window region and check if it is rectangular.
215 const int region_type =
216 GetWindowRegionTypeWithBoundary(selected, ®ion_rect);
217
218 // Do not use the screen capturer if the region is empty or not rectangular.
219 if (region_type == COMPLEXREGION || region_type == NULLREGION) {
220 return false;
221 }
222
223 if (region_type == SIMPLEREGION) {
224 // The |region_rect| returned from GetRgnBox() is always in window
225 // coordinate.
226 region_rect.Translate(window_region_rect_.left(),
227 window_region_rect_.top());
228 // MSDN: The window region determines the area *within* the window where the
229 // system permits drawing.
230 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
231 //
232 // |region_rect| should always be inside of |window_region_rect_|. So after
233 // the intersection, |window_region_rect_| == |region_rect|. If so, what's
234 // the point of the intersecting operations? Why cannot we directly retrieve
235 // |window_region_rect_| from GetWindowRegionTypeWithBoundary() function?
236 // TODO(zijiehe): Figure out the purpose of these intersections.
237 window_region_rect_.IntersectWith(region_rect);
238 content_rect.IntersectWith(region_rect);
239 }
240
241 // Check if the client area is out of the screen area. When the window is
242 // maximized, only its client area is visible in the screen, the border will
243 // be hidden. So we are using |content_rect| here.
244 if (!GetFullscreenRect().ContainsRect(content_rect)) {
245 return false;
246 }
247
248 // Check if the window is occluded by any other window, excluding the child
249 // windows, context menus, and |excluded_window_|.
250 // |content_rect| is preferred, see the comments on
251 // IsWindowIntersectWithSelectedWindow().
252 TopWindowVerifierContext context(selected,
253 reinterpret_cast<HWND>(excluded_window()),
254 content_rect, &window_capture_helper_);
255 return context.IsTopWindow();
256 }
257
GetWindowRectInVirtualScreen()258 DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
259 TRACE_EVENT0("webrtc",
260 "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
261 DesktopRect window_rect;
262 HWND hwnd = reinterpret_cast<HWND>(GetWindowToCapture());
263 if (!GetCroppedWindowRect(hwnd, /*avoid_cropping_border*/ false, &window_rect,
264 /*original_rect*/ nullptr)) {
265 RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
266 return window_rect;
267 }
268 window_rect.IntersectWith(window_region_rect_);
269
270 // Convert |window_rect| to be relative to the top-left of the virtual screen.
271 DesktopRect screen_rect(GetFullscreenRect());
272 window_rect.IntersectWith(screen_rect);
273 window_rect.Translate(-screen_rect.left(), -screen_rect.top());
274 return window_rect;
275 }
276
GetWindowToCapture() const277 WindowId CroppingWindowCapturerWin::GetWindowToCapture() const {
278 const auto selected_source = selected_window();
279 const auto full_screen_source =
280 full_screen_window_detector_
281 ? full_screen_window_detector_->FindFullScreenWindow(selected_source)
282 : 0;
283 return full_screen_source ? full_screen_source : selected_source;
284 }
285
286 } // namespace
287
288 // static
CreateCapturer(const DesktopCaptureOptions & options)289 std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
290 const DesktopCaptureOptions& options) {
291 std::unique_ptr<DesktopCapturer> capturer(
292 new CroppingWindowCapturerWin(options));
293 if (capturer && options.detect_updated_region()) {
294 capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
295 }
296
297 return capturer;
298 }
299
300 } // namespace webrtc
301