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}
diff --git a/modules/desktop_capture/win/screen_capture_utils.cc b/modules/desktop_capture/win/screen_capture_utils.cc
index 8411533..2fc2c1a 100644
--- a/modules/desktop_capture/win/screen_capture_utils.cc
+++ b/modules/desktop_capture/win/screen_capture_utils.cc
@@ -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 @@
// 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;
}
diff --git a/modules/desktop_capture/win/screen_capture_utils.h b/modules/desktop_capture/win/screen_capture_utils.h
index bcb183b..97bfe81 100644
--- a/modules/desktop_capture/win/screen_capture_utils.h
+++ b/modules/desktop_capture/win/screen_capture_utils.h
@@ -29,6 +29,9 @@
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
diff --git a/modules/desktop_capture/win/screen_capture_utils_unittest.cc b/modules/desktop_capture/win/screen_capture_utils_unittest.cc
index 80d1fb3..0855554 100644
--- a/modules/desktop_capture/win/screen_capture_utils_unittest.cc
+++ b/modules/desktop_capture/win/screen_capture_utils_unittest.cc
@@ -35,7 +35,8 @@
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, 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));
diff --git a/modules/desktop_capture/win/wgc_capture_source.cc b/modules/desktop_capture/win/wgc_capture_source.cc
index c81cfcb..c95847d 100644
--- a/modules/desktop_capture/win/wgc_capture_source.cc
+++ b/modules/desktop_capture/win/wgc_capture_source.cc
@@ -163,6 +163,12 @@
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))
diff --git a/modules/desktop_capture/win/wgc_capturer_win.cc b/modules/desktop_capture/win/wgc_capturer_win.cc
index dfe3577..36fefa2 100644
--- a/modules/desktop_capture/win/wgc_capturer_win.cc
+++ b/modules/desktop_capture/win/wgc_capturer_win.cc
@@ -57,10 +57,15 @@
} // 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())
diff --git a/modules/desktop_capture/win/wgc_capturer_win_unittest.cc b/modules/desktop_capture/win/wgc_capturer_win_unittest.cc
index 18edf45..ddef89f 100644
--- a/modules/desktop_capture/win/wgc_capturer_win_unittest.cc
+++ b/modules/desktop_capture/win/wgc_capturer_win_unittest.cc
@@ -361,6 +361,18 @@
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(