diff --git a/modules/desktop_capture/win/screen_capture_utils.cc b/modules/desktop_capture/win/screen_capture_utils.cc index 841153366c..2fc2c1a3f0 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* 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; } diff --git a/modules/desktop_capture/win/screen_capture_utils.h b/modules/desktop_capture/win/screen_capture_utils.h index bcb183b9d2..97bfe816d8 100644 --- a/modules/desktop_capture/win/screen_capture_utils.h +++ b/modules/desktop_capture/win/screen_capture_utils.h @@ -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 diff --git a/modules/desktop_capture/win/screen_capture_utils_unittest.cc b/modules/desktop_capture/win/screen_capture_utils_unittest.cc index 80d1fb3242..0855554f17 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 @@ 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(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(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 c81cfcbf7b..c95847d1c9 100644 --- a/modules/desktop_capture/win/wgc_capture_source.cc +++ b/modules/desktop_capture/win/wgc_capture_source.cc @@ -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 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 dfe3577d53..36fefa2a5a 100644 --- a/modules/desktop_capture/win/wgc_capturer_win.cc +++ b/modules/desktop_capture/win/wgc_capturer_win.cc @@ -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()) diff --git a/modules/desktop_capture/win/wgc_capturer_win_unittest.cc b/modules/desktop_capture/win/wgc_capturer_win_unittest.cc index 18edf451c5..ddef89ff6c 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 @@ 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(