fix(desktop): enable PulseAudio loopback flag so screen share starts on Linux

Chromium gates the PulseAudio loopback path behind the
PulseaudioLoopbackForScreenShare feature flag. Without it, returning
audio: 'loopback' from setDisplayMediaRequestHandler rejects the whole
getDisplayMedia request, so screen share never starts when the user has
"Share system audio" enabled. Also surface a clear warning toast on
loopback failure (PipeWire-only without pulse compat, macOS without
Catap) instead of failing silently — no auto-retry, since the picker
selection is already consumed.
This commit is contained in:
Jannis Braun
2026-04-29 23:43:44 +02:00
parent e0861597bc
commit f538f1d92e
3 changed files with 38 additions and 3 deletions
+14 -2
View File
@@ -739,6 +739,11 @@ function handleDeepLink(url: string): void {
// libraries are loaded in the same process. Force GTK 3 for compatibility.
if (process.platform === 'linux') {
app.commandLine.appendSwitch('gtk-version', '3');
// Chromium ships PulseAudio loopback for screen-share behind a feature flag.
// Without it, returning `audio: 'loopback'` from setDisplayMediaRequestHandler
// fails the whole getDisplayMedia request — screen share never starts when the
// user has "Share system audio" enabled.
app.commandLine.appendSwitch('enable-features', 'PulseaudioLoopbackForScreenShare');
}
// Set as default protocol handler
@@ -896,8 +901,15 @@ if (!gotTheLock) {
return;
}
// Provide the selected source — Electron creates the MediaStream
// System audio loopback: Windows/Linux native, macOS 13+ via ScreenCaptureKit
// Provide the selected source — Electron creates the MediaStream.
// System audio loopback support varies:
// - Windows: native (Chromium default).
// - macOS 13+: CoreAudio Tap; requires NSAudioCaptureUsageDescription
// in Info.plist (electron-builder injects it via mac.extendInfo).
// - Linux: PulseAudio loopback, gated behind the
// `PulseaudioLoopbackForScreenShare` feature flag we enable above.
// Fails on PipeWire-only systems without pulse compat — the
// renderer catches that and toasts the user.
callback({ video: selected, ...(shareAudio ? { audio: 'loopback' } : {}) });
} catch (err) {
console.error('[Main:ScreenShare] Handler error:', err);
+10
View File
@@ -306,6 +306,16 @@ export async function startScreenShare(room: Room): Promise<boolean> {
} catch (err) {
console.error('[ScreenShare] Failed to start screen share:', err);
if (hwOverdrive) deactivateHwOverdrive();
// Loopback unsupported (Linux without pulse, macOS without Catap) makes
// the whole getDisplayMedia call reject. No auto-retry: the picker
// selection was consumed, retrying would re-prompt it.
if (config.shareAudio && err instanceof Error && err.name !== 'NotAllowedError') {
useUIStore.getState().addToast(
'Could not start stream with system audio. Disable "Share system audio" in the picker if your system does not support it.',
'warning',
8000,
);
}
return false;
}
}