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 <jamiewalch@chromium.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Austin Orion <auorion@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#31748}
This commit is contained in:
Austin Orion 2020-07-13 14:52:49 -07:00 committed by Commit Bot
parent f678870777
commit 1ad89441ae
7 changed files with 161 additions and 126 deletions

View File

@ -13,9 +13,13 @@
// Just for the DWMWINDOWATTRIBUTE enums (DWMWA_CLOAKED).
#include <dwmapi.h>
#include <algorithm>
#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<DesktopCapturer::SourceList*>(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<WindowId>(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<LPARAM>(results);
if (!EnumWindows(&FilterUncapturableWindows, param))
return false;
for (auto it = results->begin(); it != results->end();) {
if (!IsWindowVisibleOnCurrentDesktop(reinterpret_cast<HWND>(it->id))) {
it = results->erase(it);
} else {
++it;
}
}
return true;
}
} // namespace webrtc

View File

@ -15,6 +15,7 @@
#include <windows.h>
#include <wrl/client.h>
#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;

View File

@ -10,8 +10,6 @@
#include "modules/desktop_capture/win/window_capturer_win_gdi.h"
#include <assert.h>
#include <map>
#include <memory>
#include <utility>
@ -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<DesktopCapturer::SourceList*>(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<WindowId>(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<LPARAM>(&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<HWND>(it->id))) {
it = result.erase(it);
} else {
++it;
}
}
sources->swap(result);
std::map<HWND, DesktopSize> new_map;
for (const auto& item : *sources) {
HWND hwnd = reinterpret_cast<HWND>(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<HWND>(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<HWND>(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<WindowCapturerGdi>();
owned_window_capturer_ = std::make_unique<WindowCapturerWinGdi>();
}
// 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<DesktopCapturer> WindowCapturerGdi::CreateRawWindowCapturer(
std::unique_ptr<DesktopCapturer> WindowCapturerWinGdi::CreateRawWindowCapturer(
const DesktopCaptureOptions& options) {
return std::unique_ptr<DesktopCapturer>(new WindowCapturerGdi());
return std::unique_ptr<DesktopCapturer>(new WindowCapturerWinGdi());
}
} // namespace webrtc

View File

@ -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<DesktopCapturer> CreateRawWindowCapturer(
const DesktopCaptureOptions& options);
@ -65,9 +68,7 @@ class WindowCapturerGdi : public DesktopCapturer {
WindowFinderWin window_finder_;
std::vector<HWND> owned_windows_;
std::unique_ptr<WindowCapturerGdi> owned_window_capturer_;
RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerGdi);
std::unique_ptr<WindowCapturerWinGdi> owned_window_capturer_;
};
} // namespace webrtc

View File

@ -10,41 +10,45 @@
#include "modules/desktop_capture/win/window_capturer_win_wgc.h"
#include <assert.h>
#include <memory>
#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<HWND>(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<DesktopCapturer> WindowCapturerWgc::CreateRawWindowCapturer(
std::unique_ptr<DesktopCapturer> WindowCapturerWinWgc::CreateRawWindowCapturer(
const DesktopCaptureOptions& options) {
return std::unique_ptr<DesktopCapturer>(new WindowCapturerWgc());
return std::unique_ptr<DesktopCapturer>(new WindowCapturerWinWgc());
}
} // namespace webrtc

View File

@ -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<DesktopCapturer> 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

View File

@ -22,9 +22,9 @@ std::unique_ptr<DesktopCapturer> 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);
}
}