From 1ad89441aee9146f0a8d65c926f93d2e0a03f566 Mon Sep 17 00:00:00 2001 From: Austin Orion Date: Mon, 13 Jul 2020 14:52:49 -0700 Subject: [PATCH] Implement Source enumeration and selection for WGC capturer This change implements the GetSourceList and SelectSource APIs from the DesktopCapturer interface for WindowCapturerWinWgc. No functional changes were made as the WGC capturer is not in use yet. I refactored the source enumeration functionality out of the GDI capturer and into the utils file, so both of the capturers can share the implementation. This change also renames the window capturers to include Win in the name, and updates some of the out dated code style. I've tested these changes by running the related unit tests and applying them to a Chromium enlistment and testing on https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/ Bug: webrtc:9273 Change-Id: If0ca023cb13900ab2b897aec0f38333f75a1b748 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/178960 Reviewed-by: Jamie Walch Reviewed-by: Tommi Reviewed-by: Mirko Bonadei Commit-Queue: Austin Orion Cr-Commit-Position: refs/heads/master@{#31748} --- .../win/window_capture_utils.cc | 91 +++++++++++++- .../win/window_capture_utils.h | 13 +- .../win/window_capturer_win_gdi.cc | 114 ++++-------------- .../win/window_capturer_win_gdi.h | 17 +-- .../win/window_capturer_win_wgc.cc | 32 ++--- .../win/window_capturer_win_wgc.h | 16 ++- .../desktop_capture/window_capturer_win.cc | 4 +- 7 files changed, 161 insertions(+), 126 deletions(-) diff --git a/modules/desktop_capture/win/window_capture_utils.cc b/modules/desktop_capture/win/window_capture_utils.cc index 226b564b64..006870f3c5 100644 --- a/modules/desktop_capture/win/window_capture_utils.cc +++ b/modules/desktop_capture/win/window_capture_utils.cc @@ -13,9 +13,13 @@ // Just for the DWMWINDOWATTRIBUTE enums (DWMWA_CLOAKED). #include +#include + #include "modules/desktop_capture/win/scoped_gdi_object.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/win32.h" namespace webrtc { @@ -157,6 +161,63 @@ bool IsWindowMaximized(HWND window, bool* result) { return true; } +bool IsWindowValidAndVisible(HWND window) { + return IsWindow(window) && IsWindowVisible(window) && !IsIconic(window); +} + +BOOL CALLBACK FilterUncapturableWindows(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 || !IsWindowValidAndVisible(hwnd) || + (owner && !(exstyle & WS_EX_APPWINDOW))) { + return TRUE; + } + + // Skip unresponsive windows. Set timout with 50ms, in case system is under + // heavy load. We could wait longer and have a lower false negative, but that + // would delay the the enumeration. + const UINT timeout = 50; // ms + if (!SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, timeout, + nullptr)) { + return TRUE; + } + + // Skip the Program Manager window and the Start button. + WCHAR class_name[256]; + const int class_name_length = + GetClassNameW(hwnd, class_name, arraysize(class_name)); + 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); + + // Truncate the title if it's longer than 500 characters. + WCHAR window_title[500]; + GetWindowTextW(hwnd, window_title, arraysize(window_title)); + 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; +} + // WindowCaptureHelperWin implementation. WindowCaptureHelperWin::WindowCaptureHelperWin() { // Try to load dwmapi.dll dynamically since it is not available on XP. @@ -223,12 +284,13 @@ bool WindowCaptureHelperWin::IsWindowChromeNotification(HWND hwnd) { } // |content_rect| is preferred because, -// 1. WindowCapturerWin is using GDI capturer, which cannot capture DX output. +// 1. WindowCapturerWinGdi is using GDI capturer, which cannot capture DX +// output. // So ScreenCapturer should be used as much as possible to avoid // uncapturable cases. Note: lots of new applications are using DX output // (hardware acceleration) to improve the performance which cannot be -// captured by WindowCapturerWin. See bug http://crbug.com/741770. -// 2. WindowCapturerWin is still useful because we do not want to expose the +// captured by WindowCapturerWinGdi. See bug http://crbug.com/741770. +// 2. WindowCapturerWinGdi is still useful because we do not want to expose the // content on other windows if the target window is covered by them. // 3. Shadow and borders should not be considered as "content" on other // windows because they do not expose any useful information. @@ -288,8 +350,8 @@ bool WindowCaptureHelperWin::IsWindowOnCurrentDesktop(HWND hwnd) { } bool WindowCaptureHelperWin::IsWindowVisibleOnCurrentDesktop(HWND hwnd) { - return !::IsIconic(hwnd) && ::IsWindowVisible(hwnd) && - IsWindowOnCurrentDesktop(hwnd) && !IsWindowCloaked(hwnd); + return IsWindowValidAndVisible(hwnd) && IsWindowOnCurrentDesktop(hwnd) && + !IsWindowCloaked(hwnd); } // A cloaked window is composited but not visible to the user. @@ -303,11 +365,28 @@ bool WindowCaptureHelperWin::IsWindowCloaked(HWND hwnd) { int res = 0; if (dwm_get_window_attribute_func_(hwnd, DWMWA_CLOAKED, &res, sizeof(res)) != S_OK) { - // Cannot tell so assume not cloacked for backward compatibility. + // Cannot tell so assume not cloaked for backward compatibility. return false; } return res != 0; } +bool WindowCaptureHelperWin::EnumerateCapturableWindows( + DesktopCapturer::SourceList* results) { + LPARAM param = reinterpret_cast(results); + if (!EnumWindows(&FilterUncapturableWindows, param)) + return false; + + for (auto it = results->begin(); it != results->end();) { + if (!IsWindowVisibleOnCurrentDesktop(reinterpret_cast(it->id))) { + it = results->erase(it); + } else { + ++it; + } + } + + return true; +} + } // namespace webrtc diff --git a/modules/desktop_capture/win/window_capture_utils.h b/modules/desktop_capture/win/window_capture_utils.h index 20a475510b..af55ceb534 100644 --- a/modules/desktop_capture/win/window_capture_utils.h +++ b/modules/desktop_capture/win/window_capture_utils.h @@ -15,6 +15,7 @@ #include #include +#include "modules/desktop_capture/desktop_capturer.h" #include "modules/desktop_capture/desktop_geometry.h" #include "rtc_base/constructor_magic.h" @@ -40,7 +41,7 @@ bool GetWindowRect(HWND window, DesktopRect* result); // This function should only be used by CroppingWindowCapturerWin. Instead a // DesktopRect CropWindowRect(const DesktopRect& rect) // should be added as a utility function to help CroppingWindowCapturerWin and -// WindowCapturerWin to crop out the borders or shadow according to their +// WindowCapturerWinGdi to crop out the borders or shadow according to their // scenarios. But this function is too generic and easy to be misused. bool GetCroppedWindowRect(HWND window, bool avoid_cropping_border, @@ -66,6 +67,15 @@ bool GetDcSize(HDC hdc, DesktopSize* size); // function returns false if native APIs fail. bool IsWindowMaximized(HWND window, bool* result); +// Checks that the HWND is for a valid window, that window's visibility state is +// visible, and that it is not minimized. +bool IsWindowValidAndVisible(HWND window); + +// This function is passed into the EnumWindows API and filters out windows that +// we don't want to capture, e.g. minimized or unresponsive windows and the +// Start menu. +BOOL CALLBACK FilterUncapturableWindows(HWND hwnd, LPARAM param); + typedef HRESULT(WINAPI* DwmIsCompositionEnabledFunc)(BOOL* enabled); typedef HRESULT(WINAPI* DwmGetWindowAttributeFunc)(HWND hwnd, DWORD flag, @@ -84,6 +94,7 @@ class WindowCaptureHelperWin { bool IsWindowOnCurrentDesktop(HWND hwnd); bool IsWindowVisibleOnCurrentDesktop(HWND hwnd); bool IsWindowCloaked(HWND hwnd); + bool EnumerateCapturableWindows(DesktopCapturer::SourceList* results); private: HMODULE dwmapi_library_ = nullptr; diff --git a/modules/desktop_capture/win/window_capturer_win_gdi.cc b/modules/desktop_capture/win/window_capturer_win_gdi.cc index ca6305305c..82a8551831 100644 --- a/modules/desktop_capture/win/window_capturer_win_gdi.cc +++ b/modules/desktop_capture/win/window_capturer_win_gdi.cc @@ -10,8 +10,6 @@ #include "modules/desktop_capture/win/window_capturer_win_gdi.h" -#include - #include #include #include @@ -27,62 +25,10 @@ #include "rtc_base/logging.h" #include "rtc_base/string_utils.h" #include "rtc_base/trace_event.h" +#include "rtc_base/win32.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 { @@ -144,26 +90,13 @@ BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) { return TRUE; } -WindowCapturerGdi::WindowCapturerGdi() {} -WindowCapturerGdi::~WindowCapturerGdi() {} +WindowCapturerWinGdi::WindowCapturerWinGdi() {} +WindowCapturerWinGdi::~WindowCapturerWinGdi() {} -bool WindowCapturerGdi::GetSourceList(SourceList* sources) { - SourceList result; - LPARAM param = reinterpret_cast(&result); - // EnumWindows only enumerates root windows. - if (!EnumWindows(&WindowsEnumerationHandler, param)) +bool WindowCapturerWinGdi::GetSourceList(SourceList* sources) { + if (!window_capture_helper_.EnumerateCapturableWindows(sources)) 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); @@ -174,10 +107,11 @@ bool WindowCapturerGdi::GetSourceList(SourceList* sources) { return true; } -bool WindowCapturerGdi::SelectSource(SourceId id) { +bool WindowCapturerWinGdi::SelectSource(SourceId id) { HWND window = reinterpret_cast(id); - if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window)) + if (!IsWindowValidAndVisible(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). @@ -185,18 +119,17 @@ bool WindowCapturerGdi::SelectSource(SourceId id) { return true; } -bool WindowCapturerGdi::FocusOnSelectedSource() { +bool WindowCapturerWinGdi::FocusOnSelectedSource() { if (!window_) return false; - if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_)) + if (!IsWindowValidAndVisible(window_)) return false; - return BringWindowToTop(window_) != FALSE && - SetForegroundWindow(window_) != FALSE; + return BringWindowToTop(window_) && SetForegroundWindow(window_); } -bool WindowCapturerGdi::IsOccluded(const DesktopVector& pos) { +bool WindowCapturerWinGdi::IsOccluded(const DesktopVector& pos) { DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left()); HWND hwnd = reinterpret_cast(window_finder_.GetWindowUnderPoint(sys_pos)); @@ -206,22 +139,23 @@ bool WindowCapturerGdi::IsOccluded(const DesktopVector& pos) { owned_windows_.end(); } -void WindowCapturerGdi::Start(Callback* callback) { - assert(!callback_); - assert(callback); +void WindowCapturerWinGdi::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); callback_ = callback; } -void WindowCapturerGdi::CaptureFrame() { - CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true); +void WindowCapturerWinGdi::CaptureFrame() { + RTC_DCHECK(callback_); + CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true); callback_->OnCaptureResult(results.result, std::move(results.frame)); } -WindowCapturerGdi::CaptureResults WindowCapturerGdi::CaptureFrame( +WindowCapturerWinGdi::CaptureResults WindowCapturerWinGdi::CaptureFrame( bool capture_owned_windows) { - TRACE_EVENT0("webrtc", "WindowCapturerGdi::CaptureFrame"); + TRACE_EVENT0("webrtc", "WindowCapturerWinGdi::CaptureFrame"); if (!window_) { RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError(); @@ -230,7 +164,7 @@ WindowCapturerGdi::CaptureResults WindowCapturerGdi::CaptureFrame( // Stop capturing if the window has been closed. if (!IsWindow(window_)) { - RTC_LOG(LS_ERROR) << "target window has been closed"; + RTC_LOG(LS_ERROR) << "Target window has been closed."; return {Result::ERROR_PERMANENT, nullptr}; } @@ -388,7 +322,7 @@ WindowCapturerGdi::CaptureResults WindowCapturerGdi::CaptureFrame( if (!owned_windows_.empty()) { if (!owned_window_capturer_) { - owned_window_capturer_ = std::make_unique(); + owned_window_capturer_ = std::make_unique(); } // Owned windows are stored in top-down z-order, so this iterates in @@ -425,9 +359,9 @@ WindowCapturerGdi::CaptureResults WindowCapturerGdi::CaptureFrame( } // static -std::unique_ptr WindowCapturerGdi::CreateRawWindowCapturer( +std::unique_ptr WindowCapturerWinGdi::CreateRawWindowCapturer( const DesktopCaptureOptions& options) { - return std::unique_ptr(new WindowCapturerGdi()); + return std::unique_ptr(new WindowCapturerWinGdi()); } } // namespace webrtc diff --git a/modules/desktop_capture/win/window_capturer_win_gdi.h b/modules/desktop_capture/win/window_capturer_win_gdi.h index aec361db9b..c954c230c9 100644 --- a/modules/desktop_capture/win/window_capturer_win_gdi.h +++ b/modules/desktop_capture/win/window_capturer_win_gdi.h @@ -19,15 +19,18 @@ #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 { +class WindowCapturerWinGdi : public DesktopCapturer { public: - WindowCapturerGdi(); - ~WindowCapturerGdi() override; + WindowCapturerWinGdi(); + + // Disallow copy and assign + WindowCapturerWinGdi(const WindowCapturerWinGdi&) = delete; + WindowCapturerWinGdi& operator=(const WindowCapturerWinGdi&) = delete; + + ~WindowCapturerWinGdi() override; static std::unique_ptr CreateRawWindowCapturer( const DesktopCaptureOptions& options); @@ -65,9 +68,7 @@ class WindowCapturerGdi : public DesktopCapturer { WindowFinderWin window_finder_; std::vector owned_windows_; - std::unique_ptr owned_window_capturer_; - - RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerGdi); + std::unique_ptr owned_window_capturer_; }; } // namespace webrtc diff --git a/modules/desktop_capture/win/window_capturer_win_wgc.cc b/modules/desktop_capture/win/window_capturer_win_wgc.cc index 5cf3858e8d..3f64983e0d 100644 --- a/modules/desktop_capture/win/window_capturer_win_wgc.cc +++ b/modules/desktop_capture/win/window_capturer_win_wgc.cc @@ -10,41 +10,45 @@ #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; +WindowCapturerWinWgc::WindowCapturerWinWgc() = default; +WindowCapturerWinWgc::~WindowCapturerWinWgc() = default; -bool WindowCapturerWgc::GetSourceList(SourceList* sources) { - return false; +bool WindowCapturerWinWgc::GetSourceList(SourceList* sources) { + return window_capture_helper_.EnumerateCapturableWindows(sources); } -bool WindowCapturerWgc::SelectSource(SourceId id) { - return false; +bool WindowCapturerWinWgc::SelectSource(SourceId id) { + HWND window = reinterpret_cast(id); + if (!IsWindowValidAndVisible(window)) + return false; + + window_ = window; + return true; } -void WindowCapturerWgc::Start(Callback* callback) { - assert(!callback_); - assert(callback); +void WindowCapturerWinWgc::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); callback_ = callback; } -void WindowCapturerWgc::CaptureFrame() { - assert(callback_); +void WindowCapturerWinWgc::CaptureFrame() { + RTC_DCHECK(callback_); callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); } // static -std::unique_ptr WindowCapturerWgc::CreateRawWindowCapturer( +std::unique_ptr WindowCapturerWinWgc::CreateRawWindowCapturer( const DesktopCaptureOptions& options) { - return std::unique_ptr(new WindowCapturerWgc()); + return std::unique_ptr(new WindowCapturerWinWgc()); } } // namespace webrtc diff --git a/modules/desktop_capture/win/window_capturer_win_wgc.h b/modules/desktop_capture/win/window_capturer_win_wgc.h index ed09562587..6617a2d4d9 100644 --- a/modules/desktop_capture/win/window_capturer_win_wgc.h +++ b/modules/desktop_capture/win/window_capturer_win_wgc.h @@ -15,18 +15,19 @@ #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/window_capture_utils.h" namespace webrtc { -class WindowCapturerWgc : public DesktopCapturer { +class WindowCapturerWinWgc : public DesktopCapturer { public: - WindowCapturerWgc(); + WindowCapturerWinWgc(); // Disallow copy and assign - WindowCapturerWgc(const WindowCapturerWgc&) = delete; - WindowCapturerWgc& operator=(const WindowCapturerWgc&) = delete; + WindowCapturerWinWgc(const WindowCapturerWinWgc&) = delete; + WindowCapturerWinWgc& operator=(const WindowCapturerWinWgc&) = delete; - ~WindowCapturerWgc() override; + ~WindowCapturerWinWgc() override; static std::unique_ptr CreateRawWindowCapturer( const DesktopCaptureOptions& options); @@ -39,6 +40,11 @@ class WindowCapturerWgc : public DesktopCapturer { private: Callback* callback_ = nullptr; + + // HWND for the currently selected window or nullptr if window is not + // selected. + HWND window_ = nullptr; + WindowCaptureHelperWin window_capture_helper_; }; } // namespace webrtc diff --git a/modules/desktop_capture/window_capturer_win.cc b/modules/desktop_capture/window_capturer_win.cc index 2cf8835caa..a63a24df58 100644 --- a/modules/desktop_capture/window_capturer_win.cc +++ b/modules/desktop_capture/window_capturer_win.cc @@ -22,9 +22,9 @@ std::unique_ptr DesktopCapturer::CreateRawWindowCapturer( // 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); + return WindowCapturerWinGdi::CreateRawWindowCapturer(options); } else { - return WindowCapturerWgc::CreateRawWindowCapturer(options); + return WindowCapturerWinWgc::CreateRawWindowCapturer(options); } }