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