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