Repository: electron/electron (language: cpp)

Issue:
[Bug]: DesktopCapturer doesn't work in Ubuntu 22.04.2 LTS
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a bug report that matches the one I want to file, without success.

### Electron Version

23.1.1

### What operating system are you using?

Ubuntu

### Operating System Version

22.04.2

### What arch are you using?

x64

### Last Known Working Electron version

_No response_

### Expected Behavior

Get access to screens for capture recording using desktopCapturer in electron Api

### Actual Behavior

There are difficulties to report the problem because no specific error is received in the terminal, only the message below

```bash
tiago@tghpereira:~/Área de Trabalho/electron/test$ yarn start
yarn run v1.22.19
$ electron .
/home/tiago/Área de Trabalho/electron/test/node_modules/electron/dist/electron exited with signal SIGSEGV
error Command failed with exit code 1.
```

I followed the official Electronjs documentation step by step and tried several approaches mentioned in forums to find ways to solve the problem, but without success.
Please help us with this issue which is greatly affecting many packages.

### Testcase Gist URL

https://gist.github.com/68498fa7437a78b9daf435f3413c9a43

### Additional Information

_No response_


Discussion / hints:
Hello @tghpereira. Thanks for reporting this and helping to make Electron better!

Would it be possible for you to make a standalone testcase with only the code necessary to reproduce the issue? For example, [Electron Fiddle](https://www.electronjs.org/fiddle) is a great tool for making small test cases and makes it easy to publish your test case to a [gist](https://gist.github.com) that Electron maintainers can use.

Stand-alone test cases make fixing issues go more smoothly: it ensure everyone's looking at the same issue, it removes all unnecessary variables from the equation, and it can also provide the basis for automated regression tests.

Now adding the `blocked/need-repro` label for this reason. After you make a test case, please link to it in a followup comment. This issue will be closed in 10 days if the above is not addressed.
@tghpereira I have the same problem.  try installing a previous version of electron. 
hello @vlkalashnikov, thanks for the suggestion but I tried all the possibilities none worked, I left a test code created through Electron Fiddle, when running the project it just returned the message "Electron exited with signal SIGSEGV." and then the process shuts down.

@VerteDinde follow the gist link as requested

https://gist.github.com/68498fa7437a78b9daf435f3413c9a43
@tghpereira Thanks for the gist, very appreciated! 

I think I have a lead on the issue - if you switch to Xorg, does the crash still occur? I can reproduce this on Ubuntu 20.04 when using Wayland, 20.04 using Xorg works as expected, so I suspect the issue is Wayland-specific. Will try to find a fix.

Bisecting, it looks like this issue started in 23.0.0-nightly20221118 (23.0.0-nightly20221117 works as expected). I'm guessing this Chrome roll caused the regression: https://github.com/electron/electron/pull/36186/files
@VerteDinde  We in the community thank you for the attention of the Electron team.

I made 2 attempts as suggested, one in which I just deactivated Wayland and the other using Xorg, both still persist with the error with the same characteristics mentioned above
@tghpereira Would you mind re-testing this issue with Electron 23.1.3? I just started the release and it should be available via NPM in a few hours. If it's no longer happening for you, we can close this out - if it's still happening, would you mind giving me a crash dump to help me get more information? 

You should be able to collect a .dmp file by adding the following snippet to your main process code, before app.whenReady:

```
const { app, crashReporter } = require('electron')

console.log(app.getPath('crashDumps'))
crashReporter.start({ submitURL: '', uploadToServer: false })
```
Then reproduce the crash, zip up the crash dumps directory and attach it here.

Thanks again for reporting this and providing an easy-to-use gist, I hope E23.1.3 fixes the issue for you!
@VerteDinde I am available to help solve the problem at my fingertips.
I performed the test outside of Electron Fiddle because I had difficulty recovering the folder generated by it. Carrying out the test in a conventional way with Wayland enabled, I was able to obtain the "Crashpad" folder, I will attach the folder below.
If needed, I can retest with crashReporter by disabling Wayland. I also got the following error in the console.

```bash
tiago@tghpereira:~/Área de Trabalho/electron/test$ yarn start
yarn run v1.22.19
$ electron .
/home/tiago/.config/app/Crashpad

(electron:9769): Gtk-WARNING **: 11:41:59.668: cache version is different 1 != 2
[0308/114200.927591:ERROR:elf_dynamic_array_reader.h(64)] tag not found
/home/tiago/Área de Trabalho/electron/test/node_modules/electron/dist/electron exited with signal SIGSEGV
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
[Crashpad.zip](https://github.com/electron/electron/files/10921871/Crashpad.zip)

Thanks! Symbolicated crash here: [wayland_crash.txt](https://github.com/electron/electron/files/10922825/wayland_crash.txt)

Looks like this is crashing within `BaseCapturerPipeWire::CaptureFrame()`. I put in [an input check when creating the capturer](https://github.com/electron/electron/pull/37511/files#diff-73f755a575b1c517f40cd8358386c43c4ffb0eff9e8a7a9ab80c5f50a248dcf4R210) to make sure we wouldn't hard crash, but it looks like the capturer has been successfully created here. So the capturer is correctly finding PipeWire, but then failing further down the line 🤔 Will keep digging into this and see what's going on.

Same problem here on Fedora 37.
X11 works, Wayland just fails silently.

v22.3.6 works fine on Wayland as well, every release after that (including v25.0.0-alpha.3) fails.
Happens to me as well (Fedora 38, Wayland), I've tested versions from v22 to the latest v25 beta, but no avail. 
I tried bisecting the crash using the gist shared by tghpereira on Fedora 38 Workstation. I arrived at a different results depending on the CLI flags. I could not reproduce any crash while running on X11.

While running on Wayland with `--ozone-platform-hint=wayland` and `--enable-features=WebRTCPipeWireCapturer,WaylandWindowDecorations`, the bisect was [v22.0.0-nightly.20220908...v22.0.0-nightly.20220909](https://github.com/electron/electron/compare/v22.0.0-nightly.20220908...v22.0.0-nightly.20220909). The diff includes a Chromium version roll. There are lots of PipeWire-related changes in the [Chromium diff](https://chromium.googlesource.com/chromium/src/+log/107.0.5274.0..107.0.5286.0?pretty=fuller&n=10000), but the ones that seemed relevant to me are:

1. 3846595: Auto-select PipeWire target once selection is made | https://chromium-review.googlesource.com/c/chromium/src/+/3846595
2. 273541: Allow splitting PipeWire picker into Screen and Window options | https://webrtc-review.googlesource.com/c/src/+/273541
3. 3864873: Stop erasing Window picker option when using PipeWire | https://chromium-review.googlesource.com/c/chromium/src/+/3864873
4. 3846372: Show/Hide PipeWire picker based on Chrome Picker state | https://chromium-review.googlesource.com/c/chromium/src/+/3846372

Perhaps some change here caused the regression in electron. I can reproduce the same bisect using `--ozone-platform-hint=x11` and `--enable-features=WebRTCPipeWireCapturer` while running on Wayland. The cause of the crash in this scenario is always `electron!webrtc::BaseCapturerPipeWire::CaptureFrame()`.

When I tested with `--ozone-platform-hint=wayland` and `--enable-features=WaylandWindowDecorations`, the bisect was [v23.0.0-nightly.20221017...v23.0.0-nightly.20221018](https://github.com/electron/electron/compare/v23.0.0-nightly.20221017...v23.0.0-nightly.20221018). However in the good version, the windows that were listed were XWayland windows of the electron process or its parent. This indicates that the X11 capturer was being used here. When I tried to reproduce this on 26.0.0-alpha.1, the cause of the crash changed to `BaseCapturerPipeWire::CaptureFrame()`. This makes sense, as [PipeWire is now enabled by default](https://chromium-review.googlesource.com/c/chromium/src/+/4072168) in Chromium.

When I tested with `--ozone-platform-hint=x11` or no flags at all, I arrived at the same bisect as @VerteDinde, that is [v23.0.0-nightly.20221117...v23.0.0-nightly.20221118](https://github.com/electron/electron/compare/v23.0.0-nightly.20221117...v23.0.0-nightly.20221118). Similar to the previous scenario, the windows listed by the good version were XWayland windows of the electron process or its parent, indicating the usage of X11 capturer. The cause of the crashes changes to PipeWire with 26.0.0-alpha.1 in this scenario as well, due to the same reason as before.

Summarizing, the PipeWire capturer broke between v22.0.0-nightly.20220908 and v22.0.0-nightly.20220909, likely due to a regression from one of the four mentioned CLs. The fallback to X11 capturer broke in different ways in subsequent nightlies, but these should not matter for new electron versions as PipeWire is now enabled by default. The X11 capturer also has significant limitations on what it can do in Wayland.
@VerteDinde consider picking https://webrtc-review.googlesource.com/c/src/+/305120
it was requested for chromium to pick to 114 but they have not done so as of yet. Solves this bug https://bugs.chromium.org/p/webrtc/issues/detail?id=15174
I looked into the PipeWire changes and it appears that regression is a result of major changes in how PipeWire capturer works.

Earlier, when an instance of `NativeDesktopMediaList` was created, it used to start all capturers. This would cause an XDG Desktop Portal permission dialog to pop up for PipeWire capturer. If permission is granted, the application could capture screen/window to show thumbnails. Once a selection was made and the desktop capture was to begin, another XDG Desktop Portal permission dialog would show up when the capture is restarted. This was the cause of #30652.

To address the double popup issue, Chromium now does not start the capturer during the instantiation of `NativeDesktopMediaList` if it's "delegated". Right now, PipeWire is the only delegated capturer. In addition to this, if you try to fetch the source list from PipeWire capturer before a selection is made on the XDG Desktop Portal permission dialog, you'll get a placeholder source ID. These are the two reasons for the crash—the capturer is not started when `NativeDesktopMediaList::Update` is called and the capturer tries to capture thumbnails for a non-existent source ID.

The expected flow for delegated capturers is to call `NativeDesktopMediaList::StartUpdating` with an implementation of DesktopMediaListObserver. The `desktopCapturer.getSources` implementation should block until it receives `OnDelegatedSourceListSelection` or `OnDelegatedSourceListDismissed`. If `desktopCapturer.getSources` is called for both screens and windows, this should happen serially so that the two permission dialogs don't show up on top of one another. The `desktopCapturer.getSources` implementation should return the source IDs received from OnSourceAdded/OnSourceRemoved/OnSourceMoved.

A quick and dirty way to fix the crash might be to patch `DesktopMediaListBase::Update` in Chromium to start the capturer and set thumbnail size to 0 if the capturer is delegated. This will take `desktopCapturer.getSources` back to the behavior before version 22. However, it will continue to return placeholder IDs.

I'll try to take a stab at the proper fix next weekend. My C++ is very rusty, so if someone else wants to take over, feel free to do so. 

Fix patch:
diff --git a/patches/chromium/desktop_media_list.patch b/patches/chromium/desktop_media_list.patch
index ba0b2842574a0..e3aefc69cd991 100644
--- a/patches/chromium/desktop_media_list.patch
+++ b/patches/chromium/desktop_media_list.patch
@@ -82,7 +82,7 @@ index 33ca7a53dfb6d2c9e3a33f0065a3acd806e82e01..9fdf2e8ff0056ff407015b914c6b03eb
    const Source& GetSource(int index) const override;
    DesktopMediaList::Type GetMediaListType() const override;
 diff --git a/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chrome/browser/media/webrtc/native_desktop_media_list.cc
-index b548c9fbd3c0bf425447b29dcd866cd27e96b14c..f994ac6086c7b4cd3e8534f34691189d78a21601 100644
+index b548c9fbd3c0bf425447b29dcd866cd27e96b14c..4de719510eaeaaf77cdbd46560f3b4ab1869877a 100644
 --- a/chrome/browser/media/webrtc/native_desktop_media_list.cc
 +++ b/chrome/browser/media/webrtc/native_desktop_media_list.cc
 @@ -147,7 +147,7 @@ BOOL CALLBACK AllHwndCollector(HWND hwnd, LPARAM param) {
@@ -94,17 +94,20 @@ index b548c9fbd3c0bf425447b29dcd866cd27e96b14c..f994ac6086c7b4cd3e8534f34691189d
  #endif
  
  }  // namespace
-@@ -457,6 +457,9 @@ void NativeDesktopMediaList::Worker::RefreshNextThumbnail() {
+@@ -457,6 +457,12 @@ void NativeDesktopMediaList::Worker::RefreshNextThumbnail() {
        FROM_HERE,
        base::BindOnce(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished,
                       media_list_));
 +
 +  // This call is necessary to release underlying OS screen capture mechanisms.
-+  capturer_.reset();
++  // Skip if the source list is delegated, as the source list window will be active.
++  if (!capturer_->GetDelegatedSourceListController()) {
++    capturer_.reset();
++  }
  }
  
  void NativeDesktopMediaList::Worker::OnCaptureResult(
-@@ -829,6 +832,11 @@ void NativeDesktopMediaList::RefreshForVizFrameSinkWindows(
+@@ -829,6 +835,11 @@ void NativeDesktopMediaList::RefreshForVizFrameSinkWindows(
          FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails,
                                    base::Unretained(worker_.get()),
                                    std::move(native_ids), thumbnail_size_));
diff --git a/shell/browser/api/electron_api_desktop_capturer.cc b/shell/browser/api/electron_api_desktop_capturer.cc
index 650e4817c2ae6..1e908db7c87c8 100644
--- a/shell/browser/api/electron_api_desktop_capturer.cc
+++ b/shell/browser/api/electron_api_desktop_capturer.cc
@@ -181,6 +181,41 @@ DesktopCapturer::DesktopCapturer(v8::Isolate* isolate) {}
 
 DesktopCapturer::~DesktopCapturer() = default;
 
+DesktopCapturer::DesktopListListener::DesktopListListener(
+    OnceCallback update_callback,
+    OnceCallback failure_callback,
+    bool skip_thumbnails)
+    : update_callback_(std::move(update_callback)),
+      failure_callback_(std::move(failure_callback)),
+      have_thumbnail_(skip_thumbnails) {}
+
+DesktopCapturer::DesktopListListener::~DesktopListListener() = default;
+
+void DesktopCapturer::DesktopListListener::OnDelegatedSourceListSelection() {
+  if (have_thumbnail_) {
+    std::move(update_callback_).Run();
+  } else {
+    have_selection_ = true;
+  }
+}
+
+void DesktopCapturer::DesktopListListener::OnSourceThumbnailChanged(int index) {
+  if (have_selection_) {
+    // This is called every time a thumbnail is refreshed. Reset variable to
+    // ensure that the callback is not run again.
+    have_selection_ = false;
+
+    // PipeWire returns a single source, so index is not relevant.
+    std::move(update_callback_).Run();
+  } else {
+    have_thumbnail_ = true;
+  }
+}
+
+void DesktopCapturer::DesktopListListener::OnDelegatedSourceListDismissed() {
+  std::move(failure_callback_).Run();
+}
+
 void DesktopCapturer::StartHandling(bool capture_window,
                                     bool capture_screen,
                                     const gfx::Size& thumbnail_size,
@@ -212,11 +247,22 @@ void DesktopCapturer::StartHandling(bool capture_window,
         window_capturer_ = std::make_unique<NativeDesktopMediaList>(
             DesktopMediaList::Type::kWindow, std::move(capturer));
         window_capturer_->SetThumbnailSize(thumbnail_size);
-        window_capturer_->Update(
-            base::BindOnce(&DesktopCapturer::UpdateSourcesList,
-                           weak_ptr_factory_.GetWeakPtr(),
-                           window_capturer_.get()),
-            /* refresh_thumbnails = */ true);
+
+        OnceCallback update_callback = base::BindOnce(
+            &DesktopCapturer::UpdateSourcesList, weak_ptr_factory_.GetWeakPtr(),
+            window_capturer_.get());
+
+        if (window_capturer_->IsSourceListDelegated()) {
+          OnceCallback failure_callback = base::BindOnce(
+              &DesktopCapturer::HandleFailure, weak_ptr_factory_.GetWeakPtr());
+          window_listener_ = std::make_unique<DesktopListListener>(
+              std::move(update_callback), std::move(failure_callback),
+              thumbnail_size.IsEmpty());
+          window_capturer_->StartUpdating(window_listener_.get());
+        } else {
+          window_capturer_->Update(std::move(update_callback),
+                                   /* refresh_thumbnails = */ true);
+        }
       }
     }
 
@@ -226,11 +272,22 @@ void DesktopCapturer::StartHandling(bool capture_window,
         screen_capturer_ = std::make_unique<NativeDesktopMediaList>(
             DesktopMediaList::Type::kScreen, std::move(capturer));
         screen_capturer_->SetThumbnailSize(thumbnail_size);
-        screen_capturer_->Update(
-            base::BindOnce(&DesktopCapturer::UpdateSourcesList,
-                           weak_ptr_factory_.GetWeakPtr(),
-                           screen_capturer_.get()),
-            /* refresh_thumbnails = */ true);
+
+        OnceCallback update_callback = base::BindOnce(
+            &DesktopCapturer::UpdateSourcesList, weak_ptr_factory_.GetWeakPtr(),
+            screen_capturer_.get());
+
+        if (screen_capturer_->IsSourceListDelegated()) {
+          OnceCallback failure_callback = base::BindOnce(
+              &DesktopCapturer::HandleFailure, weak_ptr_factory_.GetWeakPtr());
+          screen_listener_ = std::make_unique<DesktopListListener>(
+              std::move(update_callback), std::move(failure_callback),
+              thumbnail_size.IsEmpty());
+          screen_capturer_->StartUpdating(screen_listener_.get());
+        } else {
+          screen_capturer_->Update(std::move(update_callback),
+                                   /* refresh_thumbnails = */ true);
+        }
       }
     }
   }
@@ -269,12 +326,7 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
       // |media_list_sources|.
       if (!webrtc::DxgiDuplicatorController::Instance()->GetDeviceNames(
               &device_names)) {
-        v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
-        v8::HandleScope scope(isolate);
-        gin_helper::CallMethod(this, "_onerror", "Failed to get sources.");
-
-        Unpin();
-
+        HandleFailure();
         return;
       }
 
@@ -324,6 +376,14 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
   }
 }
 
+void DesktopCapturer::HandleFailure() {
+  v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
+  v8::HandleScope scope(isolate);
+  gin_helper::CallMethod(this, "_onerror", "Failed to get sources.");
+
+  Unpin();
+}
+
 // static
 gin::Handle<DesktopCapturer> DesktopCapturer::Create(v8::Isolate* isolate) {
   auto handle = gin::CreateHandle(isolate, new DesktopCapturer(isolate));
diff --git a/shell/browser/api/electron_api_desktop_capturer.h b/shell/browser/api/electron_api_desktop_capturer.h
index f06ce5faee286..65285086a158c 100644
--- a/shell/browser/api/electron_api_desktop_capturer.h
+++ b/shell/browser/api/electron_api_desktop_capturer.h
@@ -62,8 +62,37 @@ class DesktopCapturer : public gin::Wrappable<DesktopCapturer>,
   void OnDelegatedSourceListDismissed() override {}
 
  private:
+  using OnceCallback = base::OnceClosure;
+
+  class DesktopListListener : public DesktopMediaListObserver {
+   public:
+    DesktopListListener(OnceCallback update_callback,
+                        OnceCallback failure_callback,
+                        bool skip_thumbnails);
+    ~DesktopListListener() override;
+
+   protected:
+    void OnSourceAdded(int index) override {}
+    void OnSourceRemoved(int index) override {}
+    void OnSourceMoved(int old_index, int new_index) override {}
+    void OnSourceNameChanged(int index) override {}
+    void OnSourceThumbnailChanged(int index) override;
+    void OnSourcePreviewChanged(size_t index) override {}
+    void OnDelegatedSourceListSelection() override;
+    void OnDelegatedSourceListDismissed() override;
+
+   private:
+    OnceCallback update_callback_;
+    OnceCallback failure_callback_;
+    bool have_selection_ = false;
+    bool have_thumbnail_ = false;
+  };
+
   void UpdateSourcesList(DesktopMediaList* list);
+  void HandleFailure();
 
+  std::unique_ptr<DesktopListListener> window_listener_;
+  std::unique_ptr<DesktopListListener> screen_listener_;
   std::unique_ptr<DesktopMediaList> window_capturer_;
   std::unique_ptr<DesktopMediaList> screen_capturer_;
   std::vector<DesktopCapturer::Source> captured_sources_;


Test patch: