From 061d89877a577e1cf977d7d19948523db684ed57 Mon Sep 17 00:00:00 2001 From: Austin Orion Date: Fri, 9 Apr 2021 14:45:15 -0700 Subject: [PATCH] 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 Reviewed-by: Jamie Walch Cr-Commit-Position: refs/heads/master@{#33673} --- .../win/screen_capture_utils.cc | 108 ++++++++++-------- .../win/screen_capture_utils.h | 22 ++-- .../win/screen_capture_utils_unittest.cc | 32 +++--- .../desktop_capture/win/wgc_capture_source.cc | 34 ++++-- .../desktop_capture/win/wgc_capture_source.h | 13 ++- .../desktop_capture/win/wgc_capturer_win.h | 3 +- 6 files changed, 129 insertions(+), 83 deletions(-) diff --git a/modules/desktop_capture/win/screen_capture_utils.cc b/modules/desktop_capture/win/screen_capture_utils.cc index c88602342e..b66e4912d8 100644 --- a/modules/desktop_capture/win/screen_capture_utils.cc +++ b/modules/desktop_capture/win/screen_capture_utils.cc @@ -16,48 +16,13 @@ #include #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(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(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(monitors)); -} bool GetScreenList(DesktopCapturer::SourceList* screens, std::vector* device_names /* = nullptr */) { @@ -73,12 +38,14 @@ bool GetScreenList(DesktopCapturer::SourceList* screens, 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 @@ bool GetScreenList(DesktopCapturer::SourceList* screens, return true; } -bool IsMonitorValid(DesktopCapturer::SourceId monitor) { - MONITORINFO monitor_info; - monitor_info.cbSize = sizeof(MONITORINFO); - return GetMonitorInfoA(reinterpret_cast(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 @@ bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key) { 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 @@ DesktopRect GetFullscreenRect() { 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 @@ DesktopRect GetScreenRect(DesktopCapturer::SourceId screen, 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 f9c457da8d..86d92e1d71 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 GetMonitorList(DesktopCapturer::SourceList* monitors); bool GetScreenList(DesktopCapturer::SourceList* screens, std::vector* 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 @@ DesktopRect GetFullscreenRect(); // 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 cd122b7950..80d1fb3242 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 #include +#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 @@ TEST(ScreenCaptureUtilsTest, GetScreenList) { 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(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 b7eb62f201..f894a1ec3c 100644 --- a/modules/desktop_capture/win/wgc_capture_source.cc +++ b/modules/desktop_capture/win/wgc_capture_source.cc @@ -36,6 +36,14 @@ HRESULT WgcCaptureSource::GetCaptureItem( 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 item; + return SUCCEEDED(CreateCaptureItem(&item)); +} + WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default; WgcWindowSourceFactory::WgcWindowSourceFactory() = default; @@ -59,7 +67,10 @@ WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id) WgcWindowSource::~WgcWindowSource() = default; bool WgcWindowSource::IsCapturable() { - return IsWindowValidAndVisible(reinterpret_cast(GetSourceId())); + if (!IsWindowValidAndVisible(reinterpret_cast(GetSourceId()))) + return false; + + return WgcCaptureSource::IsCapturable(); } HRESULT WgcWindowSource::CreateCaptureItem( @@ -92,15 +103,25 @@ WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id) 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* result) { + if (!hmonitor_) + return E_ABORT; + if (!ResolveCoreWinRTDelayload()) return E_FAIL; @@ -112,8 +133,7 @@ HRESULT WgcScreenSource::CreateCaptureItem( return hr; ComPtr item; - hr = interop->CreateForMonitor(reinterpret_cast(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 20ccdfb853..a5599c620d 100644 --- a/modules/desktop_capture/win/wgc_capture_source.h +++ b/modules/desktop_capture/win/wgc_capture_source.h @@ -13,9 +13,12 @@ #include #include + #include +#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 @@ class WgcCaptureSource { 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 @@ class WgcCaptureSource { private: Microsoft::WRL::ComPtr item_; - DesktopCapturer::SourceId source_id_; + const DesktopCapturer::SourceId source_id_; }; class WgcCaptureSourceFactory { @@ -115,6 +118,12 @@ class WgcScreenSource final : public WgcCaptureSource { 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_; }; } // namespace webrtc diff --git a/modules/desktop_capture/win/wgc_capturer_win.h b/modules/desktop_capture/win/wgc_capturer_win.h index f48d6019be..aae2304263 100644 --- a/modules/desktop_capture/win/wgc_capturer_win.h +++ b/modules/desktop_capture/win/wgc_capturer_win.h @@ -13,6 +13,7 @@ #include #include + #include #include @@ -62,7 +63,7 @@ class ScreenEnumerator final : public SourceEnumerator { ~ScreenEnumerator() override = default; bool FindAllSources(DesktopCapturer::SourceList* sources) override { - return webrtc::GetMonitorList(sources); + return webrtc::GetScreenList(sources); } };