Update WgcScreenSource* to use device indices instead of HMONITORs.
To maintain interoperability between different capturer implementations
this change updates WgcScreenSourceEnumerator to return a list of
device indices instead of a list of HMONITORs, and WgcScreenSource to
accept a device index as the input SourceId. WGC still requires an
HMONITOR to create the capture item, so this change also adds a utility
function GetHmonitorFromDeviceIndex to convert them, as well as new
tests to cover these changes.
Bug: webrtc:12663
Change-Id: Ic29faa0f023ebc26b4276cf29ef3d15d976e8615
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214600
Commit-Queue: Austin Orion <auorion@microsoft.com>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#33673}
diff --git a/modules/desktop_capture/win/screen_capture_utils.cc b/modules/desktop_capture/win/screen_capture_utils.cc
index c886023..b66e491 100644
--- a/modules/desktop_capture/win/screen_capture_utils.cc
+++ b/modules/desktop_capture/win/screen_capture_utils.cc
@@ -16,48 +16,13 @@
#include <vector>
#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/desktop_geometry.h"
#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/win32.h"
namespace webrtc {
-namespace {
-
-BOOL CALLBACK GetMonitorListHandler(HMONITOR monitor,
- HDC hdc,
- LPRECT rect,
- LPARAM data) {
- auto monitor_list = reinterpret_cast<DesktopCapturer::SourceList*>(data);
-
- // Get the name of the monitor.
- MONITORINFOEXA monitor_info;
- monitor_info.cbSize = sizeof(MONITORINFOEXA);
- if (!GetMonitorInfoA(monitor, &monitor_info)) {
- // Continue the enumeration, but don't add this monitor to |monitor_list|.
- return TRUE;
- }
-
- DesktopCapturer::Source monitor_source;
- monitor_source.id = reinterpret_cast<intptr_t>(monitor);
- monitor_source.title = monitor_info.szDevice;
- monitor_list->push_back(monitor_source);
- return TRUE;
-}
-
-} // namespace
-
-// |monitors| is populated with HMONITOR values for each display monitor found.
-// This is in contrast to |GetScreenList| which returns the display indices.
-bool GetMonitorList(DesktopCapturer::SourceList* monitors) {
- RTC_DCHECK_EQ(monitors->size(), 0U);
- // |EnumDisplayMonitors| accepts a display context and a rectangle, which
- // allows us to specify a certain region and return only the monitors that
- // intersect that region. We, however, want all the monitors, so we pass in
- // NULL parameters.
- return EnumDisplayMonitors(/*hdc=*/NULL, /*clip_rect=*/NULL,
- GetMonitorListHandler,
- reinterpret_cast<LPARAM>(monitors));
-}
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names /* = nullptr */) {
@@ -73,12 +38,14 @@
enum_result = EnumDisplayDevicesW(NULL, device_index, &device, 0);
// |enum_result| is 0 if we have enumerated all devices.
- if (!enum_result)
+ if (!enum_result) {
break;
+ }
// We only care about active displays.
- if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE))
+ if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
continue;
+ }
screens->push_back({device_index, std::string()});
if (device_names) {
@@ -88,13 +55,52 @@
return true;
}
-bool IsMonitorValid(DesktopCapturer::SourceId monitor) {
- MONITORINFO monitor_info;
- monitor_info.cbSize = sizeof(MONITORINFO);
- return GetMonitorInfoA(reinterpret_cast<HMONITOR>(monitor), &monitor_info);
+bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index,
+ HMONITOR* hmonitor) {
+ // A device index of |kFullDesktopScreenId| or -1 represents all screens, an
+ // HMONITOR of 0 indicates the same.
+ if (device_index == kFullDesktopScreenId) {
+ *hmonitor = 0;
+ return true;
+ }
+
+ std::wstring device_key;
+ if (!IsScreenValid(device_index, &device_key)) {
+ return false;
+ }
+
+ DesktopRect screen_rect = GetScreenRect(device_index, device_key);
+ if (screen_rect.is_empty()) {
+ return false;
+ }
+
+ RECT rect = {screen_rect.left(), screen_rect.top(), screen_rect.right(),
+ screen_rect.bottom()};
+
+ HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
+ if (monitor == NULL) {
+ RTC_LOG(LS_WARNING) << "No HMONITOR found for supplied device index.";
+ return false;
+ }
+
+ *hmonitor = monitor;
+ return true;
}
-bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key) {
+bool IsMonitorValid(const HMONITOR monitor) {
+ // An HMONITOR of 0 refers to a virtual monitor that spans all physical
+ // monitors.
+ if (monitor == 0) {
+ return true;
+ }
+
+ MONITORINFO monitor_info;
+ monitor_info.cbSize = sizeof(MONITORINFO);
+ return GetMonitorInfoA(monitor, &monitor_info);
+}
+
+bool IsScreenValid(const DesktopCapturer::SourceId screen,
+ std::wstring* device_key) {
if (screen == kFullDesktopScreenId) {
*device_key = L"";
return true;
@@ -103,8 +109,9 @@
DISPLAY_DEVICEW device;
device.cb = sizeof(device);
BOOL enum_result = EnumDisplayDevicesW(NULL, screen, &device, 0);
- if (enum_result)
+ if (enum_result) {
*device_key = device.DeviceKey;
+ }
return !!enum_result;
}
@@ -116,7 +123,7 @@
GetSystemMetrics(SM_CYVIRTUALSCREEN));
}
-DesktopRect GetScreenRect(DesktopCapturer::SourceId screen,
+DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen,
const std::wstring& device_key) {
if (screen == kFullDesktopScreenId) {
return GetFullscreenRect();
@@ -125,23 +132,26 @@
DISPLAY_DEVICEW device;
device.cb = sizeof(device);
BOOL result = EnumDisplayDevicesW(NULL, screen, &device, 0);
- if (!result)
+ if (!result) {
return DesktopRect();
+ }
// Verifies the device index still maps to the same display device, to make
// sure we are capturing the same device when devices are added or removed.
// DeviceKey is documented as reserved, but it actually contains the registry
// key for the device and is unique for each monitor, while DeviceID is not.
- if (device_key != device.DeviceKey)
+ if (device_key != device.DeviceKey) {
return DesktopRect();
+ }
DEVMODEW device_mode;
device_mode.dmSize = sizeof(device_mode);
device_mode.dmDriverExtra = 0;
result = EnumDisplaySettingsExW(device.DeviceName, ENUM_CURRENT_SETTINGS,
&device_mode, 0);
- if (!result)
+ if (!result) {
return DesktopRect();
+ }
return DesktopRect::MakeXYWH(
device_mode.dmPosition.x, device_mode.dmPosition.y,
diff --git a/modules/desktop_capture/win/screen_capture_utils.h b/modules/desktop_capture/win/screen_capture_utils.h
index f9c457d..86d92e1 100644
--- a/modules/desktop_capture/win/screen_capture_utils.h
+++ b/modules/desktop_capture/win/screen_capture_utils.h
@@ -19,10 +19,6 @@
namespace webrtc {
-// Outputs the HMONITOR values of all display monitors into |monitors|. Returns
-// true if succeeded, or false if it fails to enumerate the display monitors.
-bool GetMonitorList(DesktopCapturer::SourceList* monitors);
-
// Output the list of active screens into |screens|. Returns true if succeeded,
// or false if it fails to enumerate the display devices. If the |device_names|
// is provided, it will be filled with the DISPLAY_DEVICE.DeviceName in UTF-8
@@ -31,16 +27,22 @@
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names = nullptr);
-// Returns true if |monitor| is an HMONITOR that represents a valid display
-// monitor. Consumers should check that the results of |GetMonitorList| are
-// valid before use if a WM_DISPLAYCHANGE message has been received.
-bool IsMonitorValid(DesktopCapturer::SourceId monitor);
+// Converts a device index (which are returned by |GetScreenList|) into an
+// HMONITOR.
+bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index,
+ HMONITOR* hmonitor);
+
+// Returns true if |monitor| represents a valid display
+// monitor. Consumers should recheck the validity of HMONITORs before use if a
+// WM_DISPLAYCHANGE message has been received.
+bool IsMonitorValid(const HMONITOR monitor);
// Returns true if |screen| is a valid screen. The screen device key is
// returned through |device_key| if the screen is valid. The device key can be
// used in GetScreenRect to verify the screen matches the previously obtained
// id.
-bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key);
+bool IsScreenValid(const DesktopCapturer::SourceId screen,
+ std::wstring* device_key);
// Get the rect of the entire system in system coordinate system. I.e. the
// primary monitor always starts from (0, 0).
@@ -49,7 +51,7 @@
// Get the rect of the screen identified by |screen|, relative to the primary
// display's top-left. If the screen device key does not match |device_key|, or
// the screen does not exist, or any error happens, an empty rect is returned.
-RTC_EXPORT DesktopRect GetScreenRect(DesktopCapturer::SourceId screen,
+RTC_EXPORT DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen,
const std::wstring& device_key);
} // namespace webrtc
diff --git a/modules/desktop_capture/win/screen_capture_utils_unittest.cc b/modules/desktop_capture/win/screen_capture_utils_unittest.cc
index cd122b7..80d1fb3 100644
--- a/modules/desktop_capture/win/screen_capture_utils_unittest.cc
+++ b/modules/desktop_capture/win/screen_capture_utils_unittest.cc
@@ -13,6 +13,7 @@
#include <string>
#include <vector>
+#include "modules/desktop_capture/desktop_capture_types.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "rtc_base/logging.h"
#include "test/gtest.h"
@@ -30,26 +31,29 @@
ASSERT_EQ(screens.size(), device_names.size());
}
-TEST(ScreenCaptureUtilsTest, GetMonitorList) {
- DesktopCapturer::SourceList monitors;
-
- ASSERT_TRUE(GetMonitorList(&monitors));
-}
-
-TEST(ScreenCaptureUtilsTest, IsMonitorValid) {
- DesktopCapturer::SourceList monitors;
-
- ASSERT_TRUE(GetMonitorList(&monitors));
- if (monitors.size() == 0) {
+TEST(ScreenCaptureUtilsTest, DeviceIndexToHmonitor) {
+ DesktopCapturer::SourceList screens;
+ ASSERT_TRUE(GetScreenList(&screens));
+ if (screens.size() == 0) {
RTC_LOG(LS_INFO) << "Skip screen capture test on systems with no monitors.";
GTEST_SKIP();
}
- ASSERT_TRUE(IsMonitorValid(monitors[0].id));
+ HMONITOR hmonitor;
+ ASSERT_TRUE(GetHmonitorFromDeviceIndex(screens[0].id, &hmonitor));
+ ASSERT_TRUE(IsMonitorValid(hmonitor));
}
-TEST(ScreenCaptureUtilsTest, InvalidMonitor) {
- ASSERT_FALSE(IsMonitorValid(NULL));
+TEST(ScreenCaptureUtilsTest, FullScreenDeviceIndexToHmonitor) {
+ HMONITOR hmonitor;
+ ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor));
+ ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0));
+ ASSERT_TRUE(IsMonitorValid(hmonitor));
+}
+
+TEST(ScreenCaptureUtilsTest, InvalidDeviceIndexToHmonitor) {
+ HMONITOR hmonitor;
+ ASSERT_FALSE(GetHmonitorFromDeviceIndex(kInvalidScreenId, &hmonitor));
}
} // namespace webrtc
diff --git a/modules/desktop_capture/win/wgc_capture_source.cc b/modules/desktop_capture/win/wgc_capture_source.cc
index b7eb62f..f894a1e 100644
--- a/modules/desktop_capture/win/wgc_capture_source.cc
+++ b/modules/desktop_capture/win/wgc_capture_source.cc
@@ -36,6 +36,14 @@
return hr;
}
+bool WgcCaptureSource::IsCapturable() {
+ // If we can create a capture item, then we can capture it. Unfortunately,
+ // we can't cache this item because it may be created in a different COM
+ // apartment than where capture will eventually start from.
+ ComPtr<WGC::IGraphicsCaptureItem> item;
+ return SUCCEEDED(CreateCaptureItem(&item));
+}
+
WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default;
WgcWindowSourceFactory::WgcWindowSourceFactory() = default;
@@ -59,7 +67,10 @@
WgcWindowSource::~WgcWindowSource() = default;
bool WgcWindowSource::IsCapturable() {
- return IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()));
+ if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId())))
+ return false;
+
+ return WgcCaptureSource::IsCapturable();
}
HRESULT WgcWindowSource::CreateCaptureItem(
@@ -92,15 +103,25 @@
WgcScreenSource::~WgcScreenSource() = default;
bool WgcScreenSource::IsCapturable() {
- // 0 is the id used to capture all display monitors, so it is valid.
- if (GetSourceId() == 0)
- return true;
+ if (!hmonitor_) {
+ HMONITOR hmon;
+ if (!GetHmonitorFromDeviceIndex(GetSourceId(), &hmon))
+ return false;
- return IsMonitorValid(GetSourceId());
+ hmonitor_ = hmon;
+ }
+
+ if (!IsMonitorValid(*hmonitor_))
+ return false;
+
+ return WgcCaptureSource::IsCapturable();
}
HRESULT WgcScreenSource::CreateCaptureItem(
ComPtr<WGC::IGraphicsCaptureItem>* result) {
+ if (!hmonitor_)
+ return E_ABORT;
+
if (!ResolveCoreWinRTDelayload())
return E_FAIL;
@@ -112,8 +133,7 @@
return hr;
ComPtr<WGC::IGraphicsCaptureItem> item;
- hr = interop->CreateForMonitor(reinterpret_cast<HMONITOR>(GetSourceId()),
- IID_PPV_ARGS(&item));
+ hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item));
if (FAILED(hr))
return hr;
diff --git a/modules/desktop_capture/win/wgc_capture_source.h b/modules/desktop_capture/win/wgc_capture_source.h
index 20ccdfb..a5599c6 100644
--- a/modules/desktop_capture/win/wgc_capture_source.h
+++ b/modules/desktop_capture/win/wgc_capture_source.h
@@ -13,9 +13,12 @@
#include <windows.graphics.capture.h>
#include <wrl/client.h>
+
#include <memory>
+#include "absl/types/optional.h"
#include "modules/desktop_capture/desktop_capturer.h"
+
namespace webrtc {
// Abstract class to represent the source that WGC-based capturers capture
@@ -27,7 +30,7 @@
explicit WgcCaptureSource(DesktopCapturer::SourceId source_id);
virtual ~WgcCaptureSource();
- virtual bool IsCapturable() = 0;
+ virtual bool IsCapturable();
HRESULT GetCaptureItem(
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result);
@@ -41,7 +44,7 @@
private:
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
item_;
- DesktopCapturer::SourceId source_id_;
+ const DesktopCapturer::SourceId source_id_;
};
class WgcCaptureSourceFactory {
@@ -115,6 +118,12 @@
Microsoft::WRL::ComPtr<
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
override;
+
+ // To maintain compatibility with other capturers, this class accepts a
+ // device index as it's SourceId. However, WGC requires we use an HMONITOR to
+ // describe which screen to capture. So, we internally convert the supplied
+ // device index into an HMONITOR when |IsCapturable()| is called.
+ absl::optional<HMONITOR> hmonitor_;
};
} // namespace webrtc
diff --git a/modules/desktop_capture/win/wgc_capturer_win.h b/modules/desktop_capture/win/wgc_capturer_win.h
index f48d601..aae2304 100644
--- a/modules/desktop_capture/win/wgc_capturer_win.h
+++ b/modules/desktop_capture/win/wgc_capturer_win.h
@@ -13,6 +13,7 @@
#include <d3d11.h>
#include <wrl/client.h>
+
#include <map>
#include <memory>
@@ -62,7 +63,7 @@
~ScreenEnumerator() override = default;
bool FindAllSources(DesktopCapturer::SourceList* sources) override {
- return webrtc::GetMonitorList(sources);
+ return webrtc::GetScreenList(sources);
}
};