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); } }