Repository: electron/electron
Pull request: fix: `desktopCapturer` and `screen` source ids should match screen ids

#### Description of Change

Closes https://github.com/electron/electron/issues/42747.

Fixes an issue where sources returned by the `desktopCapturer` API and by `screen.getAllDisplays()` no longer match on Windows. This happened initially as a result of https://chromium-review.googlesource.com/c/chromium/src/+/5362362, which removed the static helper we were previously using and required that the id be derived from `HMONITOREX`. The ids are specifically intended and expected to match, so this created a problem for developers needing this information to be kept consistent. To fix this, we need to do a bit more work unfortunately and get that `HMONITOREX` information ourselves in order to derive the correct id.

Confirmed to produce expected results via https://gist.github.com/bukharin/7e4c4d61c9569e57de2183076d2808f9

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed an issue where sources returned by the `desktopCapturer` API and `screen.getAllDisplays()` no longer matched on Windows.

Changed files:
filename: shell/browser/api/electron_api_desktop_capturer.cc
patch: @@ -167,6 +167,16 @@ std::unique_ptr<ThumbnailCapturer> MakeScreenCapturer() {
                          : nullptr;
 }
 
+#if BUILDFLAG(IS_WIN)
+BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR monitor,
+                                          HDC hdc,
+                                          LPRECT rect,
+                                          LPARAM data) {
+  reinterpret_cast<std::vector<HMONITOR>*>(data)->push_back(monitor);
+  return TRUE;
+}
+#endif
+
 }  // namespace
 
 namespace gin {
@@ -396,11 +406,22 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
         return;
       }
 
-      int device_name_index = 0;
-      for (auto& source : screen_sources) {
-        const auto& device_name = device_names[device_name_index++];
-        const int64_t device_id = base::PersistentHash(device_name);
-        source.display_id = base::NumberToString(device_id);
+      std::vector<HMONITOR> monitors;
+      EnumDisplayMonitors(nullptr, nullptr, EnumDisplayMonitorsCallback,
+                          reinterpret_cast<LPARAM>(&monitors));
+
+      std::vector<std::pair<std::string, MONITORINFOEX>> pairs;
+      for (const auto& device_name : device_names) {
+        std::wstring wide_device_name;
+        base::UTF8ToWide(device_name.c_str(), device_name.size(),
+                         &wide_device_name);
+        for (const auto monitor : monitors) {
+          MONITORINFOEX monitorInfo{{sizeof(MONITORINFOEX)}};
+          if (GetMonitorInfo(monitor, &monitorInfo)) {
+            if (wide_device_name == monitorInfo.szDevice)
+              pairs.push_back(std::make_pair(device_name, monitorInfo));
+          }
+        }
       }
     }
 #elif BUILDFLAG(IS_MAC)

filename: spec/api-screen-spec.ts
patch: @@ -1,5 +1,6 @@
 import { expect } from 'chai';
-import { Display, screen } from 'electron/main';
+import { Display, screen, desktopCapturer } from 'electron/main';
+import { ifit } from './lib/spec-helpers';
 
 describe('screen module', () => {
   describe('methods reassignment', () => {
@@ -14,6 +15,26 @@ describe('screen module', () => {
     });
   });
 
+  describe('screen.getAllDisplays', () => {
+    it('returns an array of displays', () => {
+      const displays = screen.getAllDisplays();
+      expect(displays).to.be.an('array').with.lengthOf.at.least(1);
+      for (const display of displays) {
+        expect(display).to.be.an('object');
+      }
+    });
+
+    // desktopCapturer.getSources does not work as expected in Windows CI.
+    ifit(process.platform !== 'win32')('returns displays with IDs matching desktopCapturer source display IDs', async () => {
+      const displayIds = screen.getAllDisplays().map(d => `${d.id}`);
+
+      const sources = await desktopCapturer.getSources({ types: ['screen'] });
+      const sourceIds = sources.map(s => s.display_id);
+
+      expect(displayIds).to.have.members(sourceIds);
+    });
+  });
+
   describe('screen.getCursorScreenPoint()', () => {
     it('returns a point object', () => {
       const point = screen.getCursorScreenPoint();