← Back to Wiki
Electron / WebRTC

Electron Screen Share Fails Silently, and It Looks Like a Distro Problem

The bug arrived as "screen sharing does not work on Bazzite, but it worked on Arch". That reads like Wayland versus X11, and it was not. Screen sharing in the desktop app had never worked, on any platform, since the day it shipped. It failed in complete silence, and the reason it looked distro-specific is the reason it was hard to see.

Share on X

Electron will not grant a display-media request you never handled

Since Electron 17, a renderer calling getDisplayMedia() is denied outright unless the main process registers a handler for it. There is no default picker and no prompt:

const { session, desktopCapturer } = require('electron')

session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
  desktopCapturer.getSources({ types: ['screen', 'window'] }).then(sources => {
    callback({ video: sources[0] })
  })
}, { useSystemPicker: true })

The trap is that microphone and camera use a different handler, setPermissionRequestHandler, which most apps do register because voice chat forced them to. So the mic works, the camera works, and the screen share silently does not, which strongly suggests the problem is somewhere in your screen-capture code rather than in permissions.

Why nothing appears in the logs

BE WARNED: Electron's denial raises NotAllowedError, exactly like a user cancelling the picker. That is the entire reason this hides. Every WebRTC wrapper deliberately swallows NotAllowedError, because a user closing the share dialog is a normal thing to do and should not log an error or show a toast. So a real, systemic, every-launch failure arrives disguised as the one thing the code is built to ignore. The button does nothing, the console stays clean, and there is no evidence to search for.

When you suspect this, put the raw call somewhere you can see it, with no wrapper in between:

// in devtools, in the running app
navigator.mediaDevices.getDisplayMedia({ video: true })
  .then(s => console.log('ok', s.getTracks()))
  .catch(e => console.log('FAILED', e.name, e.message))

If that prints FAILED NotAllowedError instantly, with no picker ever appearing, it is the handler. A real cancellation requires a picker to cancel.

It works in the browser, which is the misleading part

The same code served over HTTPS in Chrome or Firefox works, because the browser owns the picker and the permission model. Only the packaged desktop build is affected.

That asymmetry is what sends people to distro theories. Someone tests in a browser on one machine, tests the app on another, and the difference in results maps neatly onto the difference in operating systems. Test the same build on two machines before believing any distro explanation.

On Wayland, hand the picker to the portal

useSystemPicker: true routes selection through xdg-desktop-portal. On Wayland that is not a nicety, it is the only way to capture at all, because a compositor does not let an application enumerate other windows' contents by itself.

It also matters for packaging. A Flatpak build reaches the portal through the sandbox in the same way, so the system picker is what makes screen share work there too rather than needing a second mechanism.

Gate the platform-specific options

audio: 'loopback' captures system audio alongside the video, and it is Windows-only in Electron. Sending it everywhere risks the whole request being refused on platforms that do not understand it, which turns a missing feature into a broken one:

const opts = { video: source }
if (process.platform === 'win32') opts.audio = 'loopback'
callback(opts)

The general lesson: an ignored error class hides real failures

Any error your code discards by design is a place a genuine bug can live indefinitely. Cancellation errors, aborted fetches, expected 404s. They are all deliberately silenced, and something else can arrive wearing the same name.

Where you swallow an error, log it at debug level with the name attached rather than dropping it entirely. The cost is a line in a log nobody reads until the day they need it, which is exactly when it is worth having.

When this isn't your problem