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);

   }

 };