Update IsMonitorValid to return false when no displays are found.

Versions of Windows before Win11 will crash when `CreateForMonitor` is
called, but the system has no attached displays. This can be avoided by
adding a check to ensure at least one display is found before we return
true in `IsMonitorValid`. Previously we would early return `true` if the
"monitor" we were checking was the `kFullDesktopScreenId`.

Bug: chromium:1316478
Change-Id: I2562fe3834db574cf3706ee1d604472ac03f9ff3
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/258920
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Austin Orion <auorion@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#36555}
This commit is contained in:
Austin Orion 2022-04-14 13:35:54 -07:00 committed by WebRTC LUCI CQ
parent d229326578
commit 740d704079
6 changed files with 67 additions and 5 deletions

View File

@ -24,6 +24,14 @@
namespace webrtc {
bool HasActiveDisplay() {
DesktopCapturer::SourceList screens;
if (!GetScreenList(&screens))
return false;
return screens.size() >= 1;
}
bool GetScreenList(DesktopCapturer::SourceList* screens,
std::vector<std::string>* device_names /* = nullptr */) {
RTC_DCHECK_EQ(screens->size(), 0U);
@ -91,6 +99,12 @@ bool IsMonitorValid(const HMONITOR monitor) {
// An HMONITOR of 0 refers to a virtual monitor that spans all physical
// monitors.
if (monitor == 0) {
// There is a bug in a Windows OS API that causes a crash when capturing if
// there are no active displays. We must ensure there is an active display
// before returning true.
if (!HasActiveDisplay())
return false;
return true;
}

View File

@ -29,6 +29,9 @@ WEBRTC_DECLARE_HANDLE(HMONITOR);
namespace webrtc {
// Returns true if the system has at least one active display.
bool HasActiveDisplay();
// 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

View File

@ -35,7 +35,8 @@ 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.";
RTC_LOG(LS_INFO)
<< "Skip ScreenCaptureUtilsTest on systems with no monitors.";
GTEST_SKIP();
}
@ -45,12 +46,33 @@ TEST(ScreenCaptureUtilsTest, DeviceIndexToHmonitor) {
}
TEST(ScreenCaptureUtilsTest, FullScreenDeviceIndexToHmonitor) {
if (!HasActiveDisplay()) {
RTC_LOG(LS_INFO)
<< "Skip ScreenCaptureUtilsTest on systems with no monitors.";
GTEST_SKIP();
}
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor));
ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0));
ASSERT_TRUE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, NoMonitors) {
if (HasActiveDisplay()) {
RTC_LOG(LS_INFO) << "Skip ScreenCaptureUtilsTest designed specifically for "
"systems with no monitors";
GTEST_SKIP();
}
HMONITOR hmonitor;
ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor));
ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0));
// The monitor should be invalid since the system has no attached displays.
ASSERT_FALSE(IsMonitorValid(hmonitor));
}
TEST(ScreenCaptureUtilsTest, InvalidDeviceIndexToHmonitor) {
HMONITOR hmonitor;
ASSERT_FALSE(GetHmonitorFromDeviceIndex(kInvalidScreenId, &hmonitor));

View File

@ -163,6 +163,12 @@ HRESULT WgcScreenSource::CreateCaptureItem(
if (FAILED(hr))
return hr;
// Ensure the monitor is still valid (hasn't disconnected) before trying to
// create the item. On versions of Windows before Win11, `CreateForMonitor`
// will crash if no displays are connected.
if (!IsMonitorValid(*hmonitor_))
return E_ABORT;
ComPtr<WGC::IGraphicsCaptureItem> item;
hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item));
if (FAILED(hr))

View File

@ -57,10 +57,15 @@ void RecordWgcCapturerResult(WgcCapturerResult error) {
} // namespace
bool IsWgcSupported(CaptureType capture_type) {
// A bug in the WGC API `CreateForMonitor` was fixed in 20H1.
if (capture_type == CaptureType::kScreen &&
rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_20H1) {
return false;
if (capture_type == CaptureType::kScreen) {
// A bug in the WGC API `CreateForMonitor` was fixed in 20H1.
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_20H1)
return false;
// There is another bug in `CreateForMonitor` that causes a crash if there
// are no active displays.
if (!HasActiveDisplay())
return false;
}
if (!ResolveCoreWinRTDelayload())

View File

@ -361,6 +361,18 @@ TEST_F(WgcCapturerWinTest, CaptureAllMonitors) {
EXPECT_GT(frame_->size().height(), 0);
}
TEST_F(WgcCapturerWinTest, NoMonitors) {
if (HasActiveDisplay()) {
RTC_LOG(LS_INFO) << "Skip WgcCapturerWinTest designed specifically for "
"systems with no monitors";
GTEST_SKIP();
}
// A bug in `CreateForMonitor` prevents screen capture when no displays are
// attached.
EXPECT_FALSE(IsWgcSupported(CaptureType::kScreen));
}
// Window specific tests.
TEST_F(WgcCapturerWinTest, FocusOnWindow) {
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(