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::__anoncf3b6d6a0111::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::__anoncf3b6d6a0111::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, &region_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