window_capturer_win_gdi.cc (16780B)
1 /* 2 * Copyright (c) 2020 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/win/window_capturer_win_gdi.h" 12 13 #include <algorithm> 14 #include <cmath> 15 #include <cstddef> 16 #include <cstdint> 17 #include <cwchar> 18 #include <map> 19 #include <memory> 20 #include <utility> 21 #include <vector> 22 23 #include "modules/desktop_capture/cropped_desktop_frame.h" 24 #include "modules/desktop_capture/desktop_capture_metrics_helper.h" 25 #include "modules/desktop_capture/desktop_capture_types.h" 26 #include "modules/desktop_capture/desktop_capturer.h" 27 #include "modules/desktop_capture/desktop_frame.h" 28 #include "modules/desktop_capture/desktop_frame_win.h" 29 #include "modules/desktop_capture/desktop_geometry.h" 30 #include "modules/desktop_capture/win/screen_capture_utils.h" 31 #include "modules/desktop_capture/win/selected_window_context.h" 32 #include "modules/desktop_capture/win/window_capture_utils.h" 33 #include "rtc_base/checks.h" 34 #include "rtc_base/logging.h" 35 #include "rtc_base/time_utils.h" 36 #include "rtc_base/trace_event.h" 37 #include "rtc_base/win/windows_version.h" 38 #include "system_wrappers/include/metrics.h" 39 40 namespace webrtc { 41 42 // Used to pass input/output data during the EnumWindows call to collect 43 // owned/pop-up windows that should be captured. 44 struct OwnedWindowCollectorContext : public SelectedWindowContext { 45 OwnedWindowCollectorContext(HWND selected_window, 46 DesktopRect selected_window_rect, 47 WindowCaptureHelperWin* window_capture_helper, 48 std::vector<HWND>* owned_windows) 49 : SelectedWindowContext(selected_window, 50 selected_window_rect, 51 window_capture_helper), 52 owned_windows(owned_windows) {} 53 54 std::vector<HWND>* owned_windows; 55 }; 56 57 // Called via EnumWindows for each root window; adds owned/pop-up windows that 58 // should be captured to a vector it's passed. 59 BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) { 60 OwnedWindowCollectorContext* context = 61 reinterpret_cast<OwnedWindowCollectorContext*>(param); 62 if (hwnd == context->selected_window()) { 63 // Windows are enumerated in top-down z-order, so we can stop enumerating 64 // upon reaching the selected window. 65 return FALSE; 66 } 67 68 // Skip windows that aren't visible pop-up windows. 69 if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) || 70 !context->window_capture_helper()->IsWindowVisibleOnCurrentDesktop( 71 hwnd)) { 72 return TRUE; 73 } 74 75 // Owned windows that intersect the selected window should be captured. 76 if (context->IsWindowOwnedBySelectedWindow(hwnd) && 77 context->IsWindowOverlappingSelectedWindow(hwnd)) { 78 // Skip windows that draw shadows around menus. These "SysShadow" windows 79 // would otherwise be captured as solid black bars with no transparency 80 // gradient (since this capturer doesn't detect / respect variations in the 81 // window alpha channel). Any other semi-transparent owned windows will be 82 // captured fully-opaque. This seems preferable to excluding them (at least 83 // when they have content aside from a solid fill color / visual adornment; 84 // e.g. some tooltips have the transparent style set). 85 if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) { 86 const WCHAR kSysShadow[] = L"SysShadow"; 87 const size_t kClassLength = std::size(kSysShadow); 88 WCHAR class_name[kClassLength]; 89 const int class_name_length = 90 GetClassNameW(hwnd, class_name, kClassLength); 91 if (class_name_length == kClassLength - 1 && 92 wcscmp(class_name, kSysShadow) == 0) { 93 return TRUE; 94 } 95 } 96 97 context->owned_windows->push_back(hwnd); 98 } 99 100 return TRUE; 101 } 102 103 WindowCapturerWinGdi::WindowCapturerWinGdi( 104 bool enumerate_current_process_windows) 105 : enumerate_current_process_windows_(enumerate_current_process_windows) {} 106 WindowCapturerWinGdi::~WindowCapturerWinGdi() {} 107 108 bool WindowCapturerWinGdi::GetSourceList(SourceList* sources) { 109 if (!window_capture_helper_.EnumerateCapturableWindows( 110 sources, enumerate_current_process_windows_)) 111 return false; 112 113 std::map<HWND, DesktopSize> new_map; 114 for (const auto& item : *sources) { 115 HWND hwnd = reinterpret_cast<HWND>(item.id); 116 new_map[hwnd] = window_size_map_[hwnd]; 117 } 118 window_size_map_.swap(new_map); 119 120 return true; 121 } 122 123 bool WindowCapturerWinGdi::SelectSource(SourceId id) { 124 HWND window = reinterpret_cast<HWND>(id); 125 if (!IsWindowValidAndVisible(window)) 126 return false; 127 128 window_ = window; 129 // When a window is not in the map, window_size_map_[window] will create an 130 // item with DesktopSize (0, 0). 131 previous_size_ = window_size_map_[window]; 132 return true; 133 } 134 135 bool WindowCapturerWinGdi::FocusOnSelectedSource() { 136 if (!window_) 137 return false; 138 139 if (!IsWindowValidAndVisible(window_)) 140 return false; 141 142 return BringWindowToTop(window_) && SetForegroundWindow(window_); 143 } 144 145 bool WindowCapturerWinGdi::IsOccluded(const DesktopVector& pos) { 146 DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left()); 147 HWND hwnd = 148 reinterpret_cast<HWND>(window_finder_.GetWindowUnderPoint(sys_pos)); 149 150 return hwnd != window_ && 151 std::find(owned_windows_.begin(), owned_windows_.end(), hwnd) == 152 owned_windows_.end(); 153 } 154 155 void WindowCapturerWinGdi::Start(Callback* callback) { 156 RTC_DCHECK(!callback_); 157 RTC_DCHECK(callback); 158 RecordCapturerImpl(DesktopCapturerId::kWindowCapturerWinGdi); 159 160 callback_ = callback; 161 } 162 163 void WindowCapturerWinGdi::CaptureFrame() { 164 RTC_DCHECK(callback_); 165 int64_t capture_start_time_nanos = TimeNanos(); 166 167 CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true); 168 if (!results.frame) { 169 // Don't return success if we have no frame. 170 results.result = results.result == Result::SUCCESS ? Result::ERROR_TEMPORARY 171 : results.result; 172 callback_->OnCaptureResult(results.result, nullptr); 173 return; 174 } 175 176 int capture_time_ms = 177 (TimeNanos() - capture_start_time_nanos) / kNumNanosecsPerMillisec; 178 RTC_HISTOGRAM_COUNTS_1000( 179 "WebRTC.DesktopCapture.Win.WindowGdiCapturerFrameTime", capture_time_ms); 180 results.frame->set_capture_time_ms(capture_time_ms); 181 results.frame->set_capturer_id(DesktopCapturerId::kWindowCapturerWinGdi); 182 callback_->OnCaptureResult(results.result, std::move(results.frame)); 183 } 184 185 WindowCapturerWinGdi::CaptureResults WindowCapturerWinGdi::CaptureFrame( 186 bool capture_owned_windows) { 187 TRACE_EVENT0("webrtc", "WindowCapturerWinGdi::CaptureFrame"); 188 189 if (!window_) { 190 RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError(); 191 return {Result::ERROR_PERMANENT, nullptr}; 192 } 193 194 // Stop capturing if the window has been closed. 195 if (!IsWindow(window_)) { 196 RTC_LOG(LS_ERROR) << "Target window has been closed."; 197 return {Result::ERROR_PERMANENT, nullptr}; 198 } 199 200 // Determine the window region excluding any resize border, and including 201 // any visible border if capturing an owned window / dialog. (Don't include 202 // any visible border for the selected window for consistency with 203 // CroppingWindowCapturerWin, which would expose a bit of the background 204 // through the partially-transparent border.) 205 const bool avoid_cropping_border = !capture_owned_windows; 206 DesktopRect cropped_rect; 207 DesktopRect original_rect; 208 209 if (!GetCroppedWindowRect(window_, avoid_cropping_border, &cropped_rect, 210 &original_rect)) { 211 RTC_LOG(LS_WARNING) << "Failed to get drawable window area: " 212 << GetLastError(); 213 return {Result::ERROR_TEMPORARY, nullptr}; 214 } 215 216 // Return a 1x1 black frame if the window is minimized or invisible on current 217 // desktop, to match behavior on mace. Window can be temporarily invisible 218 // during the transition of full screen mode on/off. 219 if (original_rect.is_empty() || 220 !window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) { 221 std::unique_ptr<DesktopFrame> frame( 222 new BasicDesktopFrame(DesktopSize(1, 1))); 223 224 previous_size_ = frame->size(); 225 window_size_map_[window_] = previous_size_; 226 return {Result::SUCCESS, std::move(frame)}; 227 } 228 229 HDC window_dc = GetWindowDC(window_); 230 if (!window_dc) { 231 RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError(); 232 return {Result::ERROR_TEMPORARY, nullptr}; 233 } 234 235 DesktopRect unscaled_cropped_rect = cropped_rect; 236 double horizontal_scale = 1.0; 237 double vertical_scale = 1.0; 238 239 DesktopSize window_dc_size; 240 if (GetDcSize(window_dc, &window_dc_size)) { 241 // The `window_dc_size` is used to detect the scaling of the original 242 // window. If the application does not support high-DPI settings, it will 243 // be scaled by Windows according to the scaling setting. 244 // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8 245 // So the size of the `window_dc`, i.e. the bitmap we can retrieve from 246 // PrintWindow() or BitBlt() function, will be smaller than 247 // `original_rect` and `cropped_rect`. Part of the captured desktop frame 248 // will be black. See 249 // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for 250 // details. 251 252 // If `window_dc_size` is smaller than `window_rect`, let's resize both 253 // `original_rect` and `cropped_rect` according to the scaling factor. 254 // This will adjust the width and height of the two rects. 255 horizontal_scale = 256 static_cast<double>(window_dc_size.width()) / original_rect.width(); 257 vertical_scale = 258 static_cast<double>(window_dc_size.height()) / original_rect.height(); 259 original_rect.Scale(horizontal_scale, vertical_scale); 260 cropped_rect.Scale(horizontal_scale, vertical_scale); 261 262 // Translate `cropped_rect` to the left so that its position within 263 // `original_rect` remains accurate after scaling. 264 // See crbug.com/1083527 for more info. 265 int translate_left = static_cast<int>(std::round( 266 (cropped_rect.left() - original_rect.left()) * (horizontal_scale - 1))); 267 int translate_top = static_cast<int>(std::round( 268 (cropped_rect.top() - original_rect.top()) * (vertical_scale - 1))); 269 cropped_rect.Translate(translate_left, translate_top); 270 } 271 272 std::unique_ptr<DesktopFrameWin> frame( 273 DesktopFrameWin::Create(original_rect.size(), nullptr, window_dc)); 274 if (!frame.get()) { 275 RTC_LOG(LS_WARNING) << "Failed to create frame."; 276 ReleaseDC(window_, window_dc); 277 return {Result::ERROR_TEMPORARY, nullptr}; 278 } 279 280 HDC mem_dc = CreateCompatibleDC(window_dc); 281 HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap()); 282 BOOL result = FALSE; 283 284 // When desktop composition (Aero) is enabled each window is rendered to a 285 // private buffer allowing BitBlt() to get the window content even if the 286 // window is occluded. PrintWindow() is slower but lets rendering the window 287 // contents to an off-screen device context when Aero is not available. 288 // PrintWindow() is not supported by some applications. 289 // 290 // If Aero is enabled, we prefer BitBlt() because it's faster and avoids 291 // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may 292 // render occluding windows on top of the desired window. 293 // 294 // When composition is enabled the DC returned by GetWindowDC() doesn't always 295 // have window frame rendered correctly. Windows renders it only once and then 296 // caches the result between captures. We hack it around by calling 297 // PrintWindow() whenever window size changes, including the first time of 298 // capturing - it somehow affects what we get from BitBlt() on the subsequent 299 // captures. 300 // 301 // For Windows 8.1 and later, we want to always use PrintWindow when the 302 // cropping screen capturer falls back to the window capturer. I.e. 303 // on Windows 8.1 and later, PrintWindow is only used when the window is 304 // occluded. When the window is not occluded, it is much faster to capture 305 // the screen and to crop it to the window position and size. 306 if (rtc_win::GetVersion() >= rtc_win::Version::VERSION_WIN8) { 307 // Special flag that makes PrintWindow to work on Windows 8.1 and later. 308 // Indeed certain apps (e.g. those using DirectComposition rendering) can't 309 // be captured using BitBlt or PrintWindow without this flag. Note that on 310 // Windows 8.0 this flag is not supported so the block below will fallback 311 // to the other call to PrintWindow. It seems to be very tricky to detect 312 // Windows 8.0 vs 8.1 so a try/fallback is more approriate here. 313 const UINT flags = PW_RENDERFULLCONTENT; 314 result = PrintWindow(window_, mem_dc, flags); 315 } 316 317 if (!result && (!window_capture_helper_.IsAeroEnabled() || 318 !previous_size_.equals(frame->size()))) { 319 result = PrintWindow(window_, mem_dc, 0); 320 } 321 322 // Aero is enabled or PrintWindow() failed, use BitBlt. 323 if (!result) { 324 result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(), 325 window_dc, 0, 0, SRCCOPY); 326 } 327 328 SelectObject(mem_dc, previous_object); 329 DeleteDC(mem_dc); 330 ReleaseDC(window_, window_dc); 331 332 previous_size_ = frame->size(); 333 window_size_map_[window_] = previous_size_; 334 335 frame->mutable_updated_region()->SetRect( 336 DesktopRect::MakeSize(frame->size())); 337 frame->set_top_left( 338 original_rect.top_left().subtract(GetFullscreenRect().top_left())); 339 340 if (!result) { 341 RTC_LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed."; 342 return {Result::ERROR_TEMPORARY, nullptr}; 343 } 344 345 // Rect for the data is relative to the first pixel of the frame. 346 cropped_rect.Translate(-original_rect.left(), -original_rect.top()); 347 std::unique_ptr<DesktopFrame> cropped_frame = 348 CreateCroppedDesktopFrame(std::move(frame), cropped_rect); 349 RTC_DCHECK(cropped_frame); 350 351 if (capture_owned_windows) { 352 // If any owned/pop-up windows overlap the selected window, capture them 353 // and copy/composite their contents into the frame. 354 owned_windows_.clear(); 355 OwnedWindowCollectorContext context(window_, unscaled_cropped_rect, 356 &window_capture_helper_, 357 &owned_windows_); 358 359 if (context.IsSelectedWindowValid()) { 360 EnumWindows(OwnedWindowCollector, reinterpret_cast<LPARAM>(&context)); 361 362 if (!owned_windows_.empty()) { 363 if (!owned_window_capturer_) { 364 owned_window_capturer_ = std::make_unique<WindowCapturerWinGdi>( 365 enumerate_current_process_windows_); 366 } 367 368 // Owned windows are stored in top-down z-order, so this iterates in 369 // reverse to capture / draw them in bottom-up z-order 370 for (auto it = owned_windows_.rbegin(); it != owned_windows_.rend(); 371 it++) { 372 HWND hwnd = *it; 373 LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); 374 if (style & WS_EX_LAYERED) { 375 continue; 376 } 377 if (owned_window_capturer_->SelectSource( 378 reinterpret_cast<SourceId>(hwnd))) { 379 CaptureResults results = owned_window_capturer_->CaptureFrame( 380 /*capture_owned_windows*/ false); 381 382 if (results.result != DesktopCapturer::Result::SUCCESS) { 383 // Simply log any error capturing an owned/pop-up window without 384 // bubbling it up to the caller (an expected error here is that 385 // the owned/pop-up window was closed; any unexpected errors won't 386 // fail the outer capture). 387 RTC_LOG(LS_INFO) << "Capturing owned window failed (previous " 388 "error/warning pertained to that)"; 389 } else { 390 // Copy / composite the captured frame into the outer frame. This 391 // may no-op if they no longer intersect (if the owned window was 392 // moved outside the owner bounds since scheduled for capture.) 393 cropped_frame->CopyIntersectingPixelsFrom( 394 *results.frame, horizontal_scale, vertical_scale); 395 } 396 } 397 } 398 } 399 } 400 } 401 402 return {Result::SUCCESS, std::move(cropped_frame)}; 403 } 404 405 // static 406 std::unique_ptr<DesktopCapturer> WindowCapturerWinGdi::CreateRawWindowCapturer( 407 const DesktopCaptureOptions& options) { 408 return std::unique_ptr<DesktopCapturer>( 409 new WindowCapturerWinGdi(options.enumerate_current_process_windows())); 410 } 411 412 } // namespace webrtc