diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index 0d8bcbc76a..eb26f5c7bb 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -459,6 +459,10 @@ rtc_library("desktop_capture_generic") { "win/selected_window_context.h", "win/window_capture_utils.cc", "win/window_capture_utils.h", + "win/window_capturer_win_gdi.cc", + "win/window_capturer_win_gdi.h", + "win/window_capturer_win_wgc.cc", + "win/window_capturer_win_wgc.h", "window_capturer_win.cc", "window_finder_win.cc", "window_finder_win.h", diff --git a/modules/desktop_capture/win/window_capturer_win_gdi.cc b/modules/desktop_capture/win/window_capturer_win_gdi.cc new file mode 100644 index 0000000000..ca6305305c --- /dev/null +++ b/modules/desktop_capture/win/window_capturer_win_gdi.cc @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/window_capturer_win_gdi.h" + +#include + +#include +#include +#include +#include + +#include "modules/desktop_capture/cropped_desktop_frame.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame_win.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/selected_window_context.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) { + DesktopCapturer::SourceList* list = + reinterpret_cast(param); + + // Skip windows that are invisible, minimized, have no title, or are owned, + // unless they have the app window style set. + int len = GetWindowTextLength(hwnd); + HWND owner = GetWindow(hwnd, GW_OWNER); + LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) || + (owner && !(exstyle & WS_EX_APPWINDOW))) { + return TRUE; + } + // Skip unresponsive windows. Set timout with 50ms, in case system is under + // heavy load, the check can wait longer but wont' be too long to delay the + // the enumeration. + const UINT uTimeout = 50; // ms + if (!SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, uTimeout, + nullptr)) { + return TRUE; + } + + // Skip the Program Manager window and the Start button. + const size_t kClassLength = 256; + WCHAR class_name[kClassLength]; + const int class_name_length = GetClassNameW(hwnd, class_name, kClassLength); + if (class_name_length < 1) + return TRUE; + + // Skip Program Manager window and the Start button. This is the same logic + // that's used in Win32WindowPicker in libjingle. Consider filtering other + // windows as well (e.g. toolbars). + if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0) + return TRUE; + + DesktopCapturer::Source window; + window.id = reinterpret_cast(hwnd); + + const size_t kTitleLength = 500; + WCHAR window_title[kTitleLength]; + // Truncate the title if it's longer than kTitleLength. + GetWindowTextW(hwnd, window_title, kTitleLength); + window.title = rtc::ToUtf8(window_title); + + // Skip windows when we failed to convert the title or it is empty. + if (window.title.empty()) + return TRUE; + + list->push_back(window); + + return TRUE; +} + +// Used to pass input/output data during the EnumWindows call to collect +// owned/pop-up windows that should be captured. +struct OwnedWindowCollectorContext : public SelectedWindowContext { + OwnedWindowCollectorContext(HWND selected_window, + DesktopRect selected_window_rect, + WindowCaptureHelperWin* window_capture_helper, + std::vector* owned_windows) + : SelectedWindowContext(selected_window, + selected_window_rect, + window_capture_helper), + owned_windows(owned_windows) {} + + std::vector* owned_windows; +}; + +// Called via EnumWindows for each root window; adds owned/pop-up windows that +// should be captured to a vector it's passed. +BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) { + OwnedWindowCollectorContext* context = + reinterpret_cast(param); + if (hwnd == context->selected_window()) { + // Windows are enumerated in top-down z-order, so we can stop enumerating + // upon reaching the selected window. + return FALSE; + } + + // Skip windows that aren't visible pop-up windows. + if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) || + !context->window_capture_helper()->IsWindowVisibleOnCurrentDesktop( + hwnd)) { + return TRUE; + } + + // Owned windows that intersect the selected window should be captured. + if (context->IsWindowOwnedBySelectedWindow(hwnd) && + context->IsWindowOverlappingSelectedWindow(hwnd)) { + // Skip windows that draw shadows around menus. These "SysShadow" windows + // would otherwise be captured as solid black bars with no transparency + // gradient (since this capturer doesn't detect / respect variations in the + // window alpha channel). Any other semi-transparent owned windows will be + // captured fully-opaque. This seems preferable to excluding them (at least + // when they have content aside from a solid fill color / visual adornment; + // e.g. some tooltips have the transparent style set). + if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) { + const WCHAR kSysShadow[] = L"SysShadow"; + const size_t kClassLength = arraysize(kSysShadow); + WCHAR class_name[kClassLength]; + const int class_name_length = + GetClassNameW(hwnd, class_name, kClassLength); + if (class_name_length == kClassLength - 1 && + wcscmp(class_name, kSysShadow) == 0) { + return TRUE; + } + } + + context->owned_windows->push_back(hwnd); + } + + return TRUE; +} + +WindowCapturerGdi::WindowCapturerGdi() {} +WindowCapturerGdi::~WindowCapturerGdi() {} + +bool WindowCapturerGdi::GetSourceList(SourceList* sources) { + SourceList result; + LPARAM param = reinterpret_cast(&result); + // EnumWindows only enumerates root windows. + if (!EnumWindows(&WindowsEnumerationHandler, param)) + return false; + + for (auto it = result.begin(); it != result.end();) { + if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop( + reinterpret_cast(it->id))) { + it = result.erase(it); + } else { + ++it; + } + } + sources->swap(result); + + std::map new_map; + for (const auto& item : *sources) { + HWND hwnd = reinterpret_cast(item.id); + new_map[hwnd] = window_size_map_[hwnd]; + } + window_size_map_.swap(new_map); + + return true; +} + +bool WindowCapturerGdi::SelectSource(SourceId id) { + HWND window = reinterpret_cast(id); + if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window)) + return false; + window_ = window; + // When a window is not in the map, window_size_map_[window] will create an + // item with DesktopSize (0, 0). + previous_size_ = window_size_map_[window]; + return true; +} + +bool WindowCapturerGdi::FocusOnSelectedSource() { + if (!window_) + return false; + + if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_)) + return false; + + return BringWindowToTop(window_) != FALSE && + SetForegroundWindow(window_) != FALSE; +} + +bool WindowCapturerGdi::IsOccluded(const DesktopVector& pos) { + DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left()); + HWND hwnd = + reinterpret_cast(window_finder_.GetWindowUnderPoint(sys_pos)); + + return hwnd != window_ && + std::find(owned_windows_.begin(), owned_windows_.end(), hwnd) == + owned_windows_.end(); +} + +void WindowCapturerGdi::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; +} + +void WindowCapturerGdi::CaptureFrame() { + CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true); + + callback_->OnCaptureResult(results.result, std::move(results.frame)); +} + +WindowCapturerGdi::CaptureResults WindowCapturerGdi::CaptureFrame( + bool capture_owned_windows) { + TRACE_EVENT0("webrtc", "WindowCapturerGdi::CaptureFrame"); + + if (!window_) { + RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError(); + return {Result::ERROR_PERMANENT, nullptr}; + } + + // Stop capturing if the window has been closed. + if (!IsWindow(window_)) { + RTC_LOG(LS_ERROR) << "target window has been closed"; + return {Result::ERROR_PERMANENT, nullptr}; + } + + // Determine the window region excluding any resize border, and including + // any visible border if capturing an owned window / dialog. (Don't include + // any visible border for the selected window for consistency with + // CroppingWindowCapturerWin, which would expose a bit of the background + // through the partially-transparent border.) + const bool avoid_cropping_border = !capture_owned_windows; + DesktopRect cropped_rect; + DesktopRect original_rect; + + if (!GetCroppedWindowRect(window_, avoid_cropping_border, &cropped_rect, + &original_rect)) { + RTC_LOG(LS_WARNING) << "Failed to get drawable window area: " + << GetLastError(); + return {Result::ERROR_TEMPORARY, nullptr}; + } + + // Return a 1x1 black frame if the window is minimized or invisible on current + // desktop, to match behavior on mace. Window can be temporarily invisible + // during the transition of full screen mode on/off. + if (original_rect.is_empty() || + !window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) { + std::unique_ptr frame( + new BasicDesktopFrame(DesktopSize(1, 1))); + + previous_size_ = frame->size(); + window_size_map_[window_] = previous_size_; + return {Result::SUCCESS, std::move(frame)}; + } + + HDC window_dc = GetWindowDC(window_); + if (!window_dc) { + RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError(); + return {Result::ERROR_TEMPORARY, nullptr}; + } + + DesktopRect unscaled_cropped_rect = cropped_rect; + double horizontal_scale = 1.0; + double vertical_scale = 1.0; + + DesktopSize window_dc_size; + if (GetDcSize(window_dc, &window_dc_size)) { + // The |window_dc_size| is used to detect the scaling of the original + // window. If the application does not support high-DPI settings, it will + // be scaled by Windows according to the scaling setting. + // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8 + // So the size of the |window_dc|, i.e. the bitmap we can retrieve from + // PrintWindow() or BitBlt() function, will be smaller than + // |original_rect| and |cropped_rect|. Part of the captured desktop frame + // will be black. See + // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for + // details. + + // If |window_dc_size| is smaller than |window_rect|, let's resize both + // |original_rect| and |cropped_rect| according to the scaling factor. + horizontal_scale = + static_cast(window_dc_size.width()) / original_rect.width(); + vertical_scale = + static_cast(window_dc_size.height()) / original_rect.height(); + original_rect.Scale(horizontal_scale, vertical_scale); + cropped_rect.Scale(horizontal_scale, vertical_scale); + } + + std::unique_ptr frame( + DesktopFrameWin::Create(original_rect.size(), nullptr, window_dc)); + if (!frame.get()) { + RTC_LOG(LS_WARNING) << "Failed to create frame."; + ReleaseDC(window_, window_dc); + return {Result::ERROR_TEMPORARY, nullptr}; + } + + HDC mem_dc = CreateCompatibleDC(window_dc); + HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap()); + BOOL result = FALSE; + + // When desktop composition (Aero) is enabled each window is rendered to a + // private buffer allowing BitBlt() to get the window content even if the + // window is occluded. PrintWindow() is slower but lets rendering the window + // contents to an off-screen device context when Aero is not available. + // PrintWindow() is not supported by some applications. + // + // If Aero is enabled, we prefer BitBlt() because it's faster and avoids + // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may + // render occluding windows on top of the desired window. + // + // When composition is enabled the DC returned by GetWindowDC() doesn't always + // have window frame rendered correctly. Windows renders it only once and then + // caches the result between captures. We hack it around by calling + // PrintWindow() whenever window size changes, including the first time of + // capturing - it somehow affects what we get from BitBlt() on the subsequent + // captures. + // + // For Windows 8.1 and later, we want to always use PrintWindow when the + // cropping screen capturer falls back to the window capturer. I.e. + // on Windows 8.1 and later, PrintWindow is only used when the window is + // occluded. When the window is not occluded, it is much faster to capture + // the screen and to crop it to the window position and size. + if (rtc::IsWindows8OrLater()) { + // Special flag that makes PrintWindow to work on Windows 8.1 and later. + // Indeed certain apps (e.g. those using DirectComposition rendering) can't + // be captured using BitBlt or PrintWindow without this flag. Note that on + // Windows 8.0 this flag is not supported so the block below will fallback + // to the other call to PrintWindow. It seems to be very tricky to detect + // Windows 8.0 vs 8.1 so a try/fallback is more approriate here. + const UINT flags = PW_RENDERFULLCONTENT; + result = PrintWindow(window_, mem_dc, flags); + } + + if (!result && (!window_capture_helper_.IsAeroEnabled() || + !previous_size_.equals(frame->size()))) { + result = PrintWindow(window_, mem_dc, 0); + } + + // Aero is enabled or PrintWindow() failed, use BitBlt. + if (!result) { + result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(), + window_dc, 0, 0, SRCCOPY); + } + + SelectObject(mem_dc, previous_object); + DeleteDC(mem_dc); + ReleaseDC(window_, window_dc); + + previous_size_ = frame->size(); + window_size_map_[window_] = previous_size_; + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + frame->set_top_left( + original_rect.top_left().subtract(GetFullscreenRect().top_left())); + + if (!result) { + RTC_LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed."; + return {Result::ERROR_TEMPORARY, nullptr}; + } + + // Rect for the data is relative to the first pixel of the frame. + cropped_rect.Translate(-original_rect.left(), -original_rect.top()); + std::unique_ptr cropped_frame = + CreateCroppedDesktopFrame(std::move(frame), cropped_rect); + RTC_DCHECK(cropped_frame); + + if (capture_owned_windows) { + // If any owned/pop-up windows overlap the selected window, capture them + // and copy/composite their contents into the frame. + owned_windows_.clear(); + OwnedWindowCollectorContext context(window_, unscaled_cropped_rect, + &window_capture_helper_, + &owned_windows_); + + if (context.IsSelectedWindowValid()) { + EnumWindows(OwnedWindowCollector, reinterpret_cast(&context)); + + if (!owned_windows_.empty()) { + if (!owned_window_capturer_) { + owned_window_capturer_ = std::make_unique(); + } + + // Owned windows are stored in top-down z-order, so this iterates in + // reverse to capture / draw them in bottom-up z-order + for (auto it = owned_windows_.rbegin(); it != owned_windows_.rend(); + it++) { + HWND hwnd = *it; + if (owned_window_capturer_->SelectSource( + reinterpret_cast(hwnd))) { + CaptureResults results = owned_window_capturer_->CaptureFrame( + /*capture_owned_windows*/ false); + + if (results.result != DesktopCapturer::Result::SUCCESS) { + // Simply log any error capturing an owned/pop-up window without + // bubbling it up to the caller (an expected error here is that + // the owned/pop-up window was closed; any unexpected errors won't + // fail the outer capture). + RTC_LOG(LS_INFO) << "Capturing owned window failed (previous " + "error/warning pertained to that)"; + } else { + // Copy / composite the captured frame into the outer frame. This + // may no-op if they no longer intersect (if the owned window was + // moved outside the owner bounds since scheduled for capture.) + cropped_frame->CopyIntersectingPixelsFrom( + *results.frame, horizontal_scale, vertical_scale); + } + } + } + } + } + } + + return {Result::SUCCESS, std::move(cropped_frame)}; +} + +// static +std::unique_ptr WindowCapturerGdi::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + return std::unique_ptr(new WindowCapturerGdi()); +} + +} // namespace webrtc diff --git a/modules/desktop_capture/win/window_capturer_win_gdi.h b/modules/desktop_capture/win/window_capturer_win_gdi.h new file mode 100644 index 0000000000..aec361db9b --- /dev/null +++ b/modules/desktop_capture/win/window_capturer_win_gdi.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_ + +#include +#include +#include + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "modules/desktop_capture/window_finder_win.h" +#include "rtc_base/constructor_magic.h" +#include "rtc_base/win32.h" + +namespace webrtc { + +class WindowCapturerGdi : public DesktopCapturer { + public: + WindowCapturerGdi(); + ~WindowCapturerGdi() override; + + static std::unique_ptr CreateRawWindowCapturer( + const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + private: + struct CaptureResults { + Result result; + std::unique_ptr frame; + }; + + CaptureResults CaptureFrame(bool capture_owned_windows); + + Callback* callback_ = nullptr; + + // HWND and HDC for the currently selected window or nullptr if window is not + // selected. + HWND window_ = nullptr; + + DesktopSize previous_size_; + + WindowCaptureHelperWin window_capture_helper_; + + // This map is used to avoid flickering for the case when SelectWindow() calls + // are interleaved with Capture() calls. + std::map window_size_map_; + + WindowFinderWin window_finder_; + + std::vector owned_windows_; + std::unique_ptr owned_window_capturer_; + + RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerGdi); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_ diff --git a/modules/desktop_capture/win/window_capturer_win_wgc.cc b/modules/desktop_capture/win/window_capturer_win_wgc.cc new file mode 100644 index 0000000000..5cf3858e8d --- /dev/null +++ b/modules/desktop_capture/win/window_capturer_win_wgc.cc @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/window_capturer_win_wgc.h" + +#include +#include + +#include "modules/desktop_capture/desktop_capturer.h" + +namespace webrtc { + +WindowCapturerWgc::WindowCapturerWgc() = default; +WindowCapturerWgc::~WindowCapturerWgc() = default; + +bool WindowCapturerWgc::GetSourceList(SourceList* sources) { + return false; +} + +bool WindowCapturerWgc::SelectSource(SourceId id) { + return false; +} + +void WindowCapturerWgc::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; +} + +void WindowCapturerWgc::CaptureFrame() { + assert(callback_); + + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); +} + +// static +std::unique_ptr WindowCapturerWgc::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + return std::unique_ptr(new WindowCapturerWgc()); +} + +} // namespace webrtc diff --git a/modules/desktop_capture/win/window_capturer_win_wgc.h b/modules/desktop_capture/win/window_capturer_win_wgc.h new file mode 100644 index 0000000000..ed09562587 --- /dev/null +++ b/modules/desktop_capture/win/window_capturer_win_wgc.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_WGC_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_WGC_H_ + +#include + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" + +namespace webrtc { + +class WindowCapturerWgc : public DesktopCapturer { + public: + WindowCapturerWgc(); + + // Disallow copy and assign + WindowCapturerWgc(const WindowCapturerWgc&) = delete; + WindowCapturerWgc& operator=(const WindowCapturerWgc&) = delete; + + ~WindowCapturerWgc() override; + + static std::unique_ptr CreateRawWindowCapturer( + const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + private: + Callback* callback_ = nullptr; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_WGC_H_ diff --git a/modules/desktop_capture/window_capturer_win.cc b/modules/desktop_capture/window_capturer_win.cc index 4e16c44ced..2cf8835caa 100644 --- a/modules/desktop_capture/window_capturer_win.cc +++ b/modules/desktop_capture/window_capturer_win.cc @@ -8,472 +8,24 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include - -#include - -#include "modules/desktop_capture/cropped_desktop_frame.h" +#include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer.h" -#include "modules/desktop_capture/desktop_frame_win.h" -#include "modules/desktop_capture/win/screen_capture_utils.h" -#include "modules/desktop_capture/win/selected_window_context.h" -#include "modules/desktop_capture/win/window_capture_utils.h" -#include "modules/desktop_capture/window_finder_win.h" -#include "rtc_base/arraysize.h" -#include "rtc_base/checks.h" -#include "rtc_base/constructor_magic.h" -#include "rtc_base/logging.h" -#include "rtc_base/string_utils.h" -#include "rtc_base/trace_event.h" -#include "rtc_base/win32.h" +#include "modules/desktop_capture/win/window_capturer_win_gdi.h" +#include "modules/desktop_capture/win/window_capturer_win_wgc.h" namespace webrtc { -namespace { - -BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) { - DesktopCapturer::SourceList* list = - reinterpret_cast(param); - - // Skip windows that are invisible, minimized, have no title, or are owned, - // unless they have the app window style set. - int len = GetWindowTextLength(hwnd); - HWND owner = GetWindow(hwnd, GW_OWNER); - LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); - if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) || - (owner && !(exstyle & WS_EX_APPWINDOW))) { - return TRUE; - } - // Skip unresponsive windows. Set timout with 50ms, in case system is under - // heavy load, the check can wait longer but wont' be too long to delay the - // the enumeration. - const UINT uTimeout = 50; // ms - if (!SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, uTimeout, - nullptr)) { - return TRUE; - } - - // Skip the Program Manager window and the Start button. - const size_t kClassLength = 256; - WCHAR class_name[kClassLength]; - const int class_name_length = GetClassNameW(hwnd, class_name, kClassLength); - if (class_name_length < 1) - return TRUE; - - // Skip Program Manager window and the Start button. This is the same logic - // that's used in Win32WindowPicker in libjingle. Consider filtering other - // windows as well (e.g. toolbars). - if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0) - return TRUE; - - DesktopCapturer::Source window; - window.id = reinterpret_cast(hwnd); - - const size_t kTitleLength = 500; - WCHAR window_title[kTitleLength]; - // Truncate the title if it's longer than kTitleLength. - GetWindowTextW(hwnd, window_title, kTitleLength); - window.title = rtc::ToUtf8(window_title); - - // Skip windows when we failed to convert the title or it is empty. - if (window.title.empty()) - return TRUE; - - list->push_back(window); - - return TRUE; -} - -// Used to pass input/output data during the EnumWindows call to collect -// owned/pop-up windows that should be captured. -struct OwnedWindowCollectorContext : public SelectedWindowContext { - OwnedWindowCollectorContext(HWND selected_window, - DesktopRect selected_window_rect, - WindowCaptureHelperWin* window_capture_helper, - std::vector* owned_windows) - : SelectedWindowContext(selected_window, - selected_window_rect, - window_capture_helper), - owned_windows(owned_windows) {} - - std::vector* owned_windows; -}; - -// Called via EnumWindows for each root window; adds owned/pop-up windows that -// should be captured to a vector it's passed. -BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) { - OwnedWindowCollectorContext* context = - reinterpret_cast(param); - if (hwnd == context->selected_window()) { - // Windows are enumerated in top-down z-order, so we can stop enumerating - // upon reaching the selected window. - return FALSE; - } - - // Skip windows that aren't visible pop-up windows. - if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) || - !context->window_capture_helper()->IsWindowVisibleOnCurrentDesktop( - hwnd)) { - return TRUE; - } - - // Owned windows that intersect the selected window should be captured. - if (context->IsWindowOwnedBySelectedWindow(hwnd) && - context->IsWindowOverlappingSelectedWindow(hwnd)) { - // Skip windows that draw shadows around menus. These "SysShadow" windows - // would otherwise be captured as solid black bars with no transparency - // gradient (since this capturer doesn't detect / respect variations in the - // window alpha channel). Any other semi-transparent owned windows will be - // captured fully-opaque. This seems preferable to excluding them (at least - // when they have content aside from a solid fill color / visual adornment; - // e.g. some tooltips have the transparent style set). - if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) { - const WCHAR kSysShadow[] = L"SysShadow"; - const size_t kClassLength = arraysize(kSysShadow); - WCHAR class_name[kClassLength]; - const int class_name_length = - GetClassNameW(hwnd, class_name, kClassLength); - if (class_name_length == kClassLength - 1 && - wcscmp(class_name, kSysShadow) == 0) { - return TRUE; - } - } - - context->owned_windows->push_back(hwnd); - } - - return TRUE; -} - -class WindowCapturerWin : public DesktopCapturer { - public: - WindowCapturerWin(); - ~WindowCapturerWin() override; - - // DesktopCapturer interface. - void Start(Callback* callback) override; - void CaptureFrame() override; - bool GetSourceList(SourceList* sources) override; - bool SelectSource(SourceId id) override; - bool FocusOnSelectedSource() override; - bool IsOccluded(const DesktopVector& pos) override; - - private: - struct CaptureResults { - Result result; - std::unique_ptr frame; - }; - - CaptureResults CaptureFrame(bool capture_owned_windows); - - Callback* callback_ = nullptr; - - // HWND and HDC for the currently selected window or nullptr if window is not - // selected. - HWND window_ = nullptr; - - DesktopSize previous_size_; - - WindowCaptureHelperWin window_capture_helper_; - - // This map is used to avoid flickering for the case when SelectWindow() calls - // are interleaved with Capture() calls. - std::map window_size_map_; - - WindowFinderWin window_finder_; - - std::vector owned_windows_; - std::unique_ptr owned_window_capturer_; - - RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin); -}; - -WindowCapturerWin::WindowCapturerWin() {} -WindowCapturerWin::~WindowCapturerWin() {} - -bool WindowCapturerWin::GetSourceList(SourceList* sources) { - SourceList result; - LPARAM param = reinterpret_cast(&result); - // EnumWindows only enumerates root windows. - if (!EnumWindows(&WindowsEnumerationHandler, param)) - return false; - - for (auto it = result.begin(); it != result.end();) { - if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop( - reinterpret_cast(it->id))) { - it = result.erase(it); - } else { - ++it; - } - } - sources->swap(result); - - std::map new_map; - for (const auto& item : *sources) { - HWND hwnd = reinterpret_cast(item.id); - new_map[hwnd] = window_size_map_[hwnd]; - } - window_size_map_.swap(new_map); - - return true; -} - -bool WindowCapturerWin::SelectSource(SourceId id) { - HWND window = reinterpret_cast(id); - if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window)) - return false; - window_ = window; - // When a window is not in the map, window_size_map_[window] will create an - // item with DesktopSize (0, 0). - previous_size_ = window_size_map_[window]; - return true; -} - -bool WindowCapturerWin::FocusOnSelectedSource() { - if (!window_) - return false; - - if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_)) - return false; - - return BringWindowToTop(window_) != FALSE && - SetForegroundWindow(window_) != FALSE; -} - -bool WindowCapturerWin::IsOccluded(const DesktopVector& pos) { - DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left()); - HWND hwnd = - reinterpret_cast(window_finder_.GetWindowUnderPoint(sys_pos)); - - return hwnd != window_ && - std::find(owned_windows_.begin(), owned_windows_.end(), hwnd) == - owned_windows_.end(); -} - -void WindowCapturerWin::Start(Callback* callback) { - assert(!callback_); - assert(callback); - - callback_ = callback; -} - -void WindowCapturerWin::CaptureFrame() { - CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true); - - callback_->OnCaptureResult(results.result, std::move(results.frame)); -} - -WindowCapturerWin::CaptureResults WindowCapturerWin::CaptureFrame( - bool capture_owned_windows) { - TRACE_EVENT0("webrtc", "WindowCapturerWin::CaptureFrame"); - - if (!window_) { - RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError(); - return {Result::ERROR_PERMANENT, nullptr}; - } - - // Stop capturing if the window has been closed. - if (!IsWindow(window_)) { - RTC_LOG(LS_ERROR) << "target window has been closed"; - return {Result::ERROR_PERMANENT, nullptr}; - } - - // Determine the window region excluding any resize border, and including - // any visible border if capturing an owned window / dialog. (Don't include - // any visible border for the selected window for consistency with - // CroppingWindowCapturerWin, which would expose a bit of the background - // through the partially-transparent border.) - const bool avoid_cropping_border = !capture_owned_windows; - DesktopRect cropped_rect; - DesktopRect original_rect; - - if (!GetCroppedWindowRect(window_, avoid_cropping_border, &cropped_rect, - &original_rect)) { - RTC_LOG(LS_WARNING) << "Failed to get drawable window area: " - << GetLastError(); - return {Result::ERROR_TEMPORARY, nullptr}; - } - - // Return a 1x1 black frame if the window is minimized or invisible on current - // desktop, to match behavior on mace. Window can be temporarily invisible - // during the transition of full screen mode on/off. - if (original_rect.is_empty() || - !window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) { - std::unique_ptr frame( - new BasicDesktopFrame(DesktopSize(1, 1))); - - previous_size_ = frame->size(); - window_size_map_[window_] = previous_size_; - return {Result::SUCCESS, std::move(frame)}; - } - - HDC window_dc = GetWindowDC(window_); - if (!window_dc) { - RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError(); - return {Result::ERROR_TEMPORARY, nullptr}; - } - - DesktopRect unscaled_cropped_rect = cropped_rect; - double horizontal_scale = 1.0; - double vertical_scale = 1.0; - - DesktopSize window_dc_size; - if (GetDcSize(window_dc, &window_dc_size)) { - // The |window_dc_size| is used to detect the scaling of the original - // window. If the application does not support high-DPI settings, it will - // be scaled by Windows according to the scaling setting. - // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8 - // So the size of the |window_dc|, i.e. the bitmap we can retrieve from - // PrintWindow() or BitBlt() function, will be smaller than - // |original_rect| and |cropped_rect|. Part of the captured desktop frame - // will be black. See - // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for - // details. - - // If |window_dc_size| is smaller than |window_rect|, let's resize both - // |original_rect| and |cropped_rect| according to the scaling factor. - horizontal_scale = - static_cast(window_dc_size.width()) / original_rect.width(); - vertical_scale = - static_cast(window_dc_size.height()) / original_rect.height(); - original_rect.Scale(horizontal_scale, vertical_scale); - cropped_rect.Scale(horizontal_scale, vertical_scale); - } - - std::unique_ptr frame( - DesktopFrameWin::Create(original_rect.size(), nullptr, window_dc)); - if (!frame.get()) { - RTC_LOG(LS_WARNING) << "Failed to create frame."; - ReleaseDC(window_, window_dc); - return {Result::ERROR_TEMPORARY, nullptr}; - } - - HDC mem_dc = CreateCompatibleDC(window_dc); - HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap()); - BOOL result = FALSE; - - // When desktop composition (Aero) is enabled each window is rendered to a - // private buffer allowing BitBlt() to get the window content even if the - // window is occluded. PrintWindow() is slower but lets rendering the window - // contents to an off-screen device context when Aero is not available. - // PrintWindow() is not supported by some applications. - // - // If Aero is enabled, we prefer BitBlt() because it's faster and avoids - // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may - // render occluding windows on top of the desired window. - // - // When composition is enabled the DC returned by GetWindowDC() doesn't always - // have window frame rendered correctly. Windows renders it only once and then - // caches the result between captures. We hack it around by calling - // PrintWindow() whenever window size changes, including the first time of - // capturing - it somehow affects what we get from BitBlt() on the subsequent - // captures. - // - // For Windows 8.1 and later, we want to always use PrintWindow when the - // cropping screen capturer falls back to the window capturer. I.e. - // on Windows 8.1 and later, PrintWindow is only used when the window is - // occluded. When the window is not occluded, it is much faster to capture - // the screen and to crop it to the window position and size. - if (rtc::IsWindows8OrLater()) { - // Special flag that makes PrintWindow to work on Windows 8.1 and later. - // Indeed certain apps (e.g. those using DirectComposition rendering) can't - // be captured using BitBlt or PrintWindow without this flag. Note that on - // Windows 8.0 this flag is not supported so the block below will fallback - // to the other call to PrintWindow. It seems to be very tricky to detect - // Windows 8.0 vs 8.1 so a try/fallback is more approriate here. - const UINT flags = PW_RENDERFULLCONTENT; - result = PrintWindow(window_, mem_dc, flags); - } - - if (!result && (!window_capture_helper_.IsAeroEnabled() || - !previous_size_.equals(frame->size()))) { - result = PrintWindow(window_, mem_dc, 0); - } - - // Aero is enabled or PrintWindow() failed, use BitBlt. - if (!result) { - result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(), - window_dc, 0, 0, SRCCOPY); - } - - SelectObject(mem_dc, previous_object); - DeleteDC(mem_dc); - ReleaseDC(window_, window_dc); - - previous_size_ = frame->size(); - window_size_map_[window_] = previous_size_; - - frame->mutable_updated_region()->SetRect( - DesktopRect::MakeSize(frame->size())); - frame->set_top_left( - original_rect.top_left().subtract(GetFullscreenRect().top_left())); - - if (!result) { - RTC_LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed."; - return {Result::ERROR_TEMPORARY, nullptr}; - } - - // Rect for the data is relative to the first pixel of the frame. - cropped_rect.Translate(-original_rect.left(), -original_rect.top()); - std::unique_ptr cropped_frame = - CreateCroppedDesktopFrame(std::move(frame), cropped_rect); - RTC_DCHECK(cropped_frame); - - if (capture_owned_windows) { - // If any owned/pop-up windows overlap the selected window, capture them - // and copy/composite their contents into the frame. - owned_windows_.clear(); - OwnedWindowCollectorContext context(window_, unscaled_cropped_rect, - &window_capture_helper_, - &owned_windows_); - - if (context.IsSelectedWindowValid()) { - EnumWindows(OwnedWindowCollector, reinterpret_cast(&context)); - - if (!owned_windows_.empty()) { - if (!owned_window_capturer_) { - owned_window_capturer_ = std::make_unique(); - } - - // Owned windows are stored in top-down z-order, so this iterates in - // reverse to capture / draw them in bottom-up z-order - for (auto it = owned_windows_.rbegin(); it != owned_windows_.rend(); - it++) { - HWND hwnd = *it; - if (owned_window_capturer_->SelectSource( - reinterpret_cast(hwnd))) { - CaptureResults results = owned_window_capturer_->CaptureFrame( - /*capture_owned_windows*/ false); - - if (results.result != DesktopCapturer::Result::SUCCESS) { - // Simply log any error capturing an owned/pop-up window without - // bubbling it up to the caller (an expected error here is that - // the owned/pop-up window was closed; any unexpected errors won't - // fail the outer capture). - RTC_LOG(LS_INFO) << "Capturing owned window failed (previous " - "error/warning pertained to that)"; - } else { - // Copy / composite the captured frame into the outer frame. This - // may no-op if they no longer intersect (if the owned window was - // moved outside the owner bounds since scheduled for capture.) - cropped_frame->CopyIntersectingPixelsFrom( - *results.frame, horizontal_scale, vertical_scale); - } - } - } - } - } - } - - return {Result::SUCCESS, std::move(cropped_frame)}; -} - -} // namespace - // static std::unique_ptr DesktopCapturer::CreateRawWindowCapturer( const DesktopCaptureOptions& options) { - return std::unique_ptr(new WindowCapturerWin()); + // TODO(bugs.webrtc.org/11760): Add a WebRTC field trial (or similar + // mechanism) and Windows version check here that leads to use of the WGC + // capturer once it is fully implemented. + if (true) { + return WindowCapturerGdi::CreateRawWindowCapturer(options); + } else { + return WindowCapturerWgc::CreateRawWindowCapturer(options); + } } } // namespace webrtc