Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions electron/gpuSwitches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ describe("getGpuSwitches", () => {
});
});

it("returns the X11 EGL workaround on Linux X11", () => {
it("does not force EGL on Linux X11 (default ANGLE backend)", () => {
expect(getGpuSwitches("linux", { XDG_SESSION_TYPE: "x11" })).toEqual({
useGl: "egl",
disableFeatures: ["VaapiVideoDecoder", "VaapiVideoEncoder"],
});
});
Expand Down
9 changes: 7 additions & 2 deletions electron/gpuSwitches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function shouldForceLinuxEgl(env: NodeJS.ProcessEnv): boolean {

export function getGpuSwitches(
platform: NodeJS.Platform,
env: NodeJS.ProcessEnv = process.env,
_env: NodeJS.ProcessEnv = process.env,
): GpuSwitches {
if (platform === "darwin") {
return {
Expand All @@ -56,8 +56,13 @@ export function getGpuSwitches(
}

if (platform === "linux") {
// Do NOT force --use-gl=egl: native EGL (gl=egl-gles2,angle=none) is not
// an allowed GL implementation on Electron's Linux builds, which only
// ship ANGLE (gl=egl-angle). Requesting it makes the GPU process
// crash-loop on init, which in turn breaks screen capture and forces the
// renderer into software compositing. Let Chromium use its default ANGLE
// backend instead.
return {
useGl: shouldForceLinuxEgl(env) ? "egl" : undefined,
disableFeatures: ["VaapiVideoDecoder", "VaapiVideoEncoder"],
};
}
Expand Down
30 changes: 14 additions & 16 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { RECORDINGS_DIR } from "./appPaths";
import { showCursor } from "./cursorHider";
import { registerExtensionIpcHandlers } from "./extensions/extensionIpc";
import { getGpuSwitches } from "./gpuSwitches";
import { getGpuSwitches, shouldForceLinuxEgl } from "./gpuSwitches";
import {
cleanupAllExportStreams,
cleanupNativeVideoExportSessions,
Expand Down Expand Up @@ -1006,28 +1006,26 @@ app.whenReady().then(async () => {
session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => {
try {
const sourceId = getSelectedSourceId();
// On Linux/Wayland, calling desktopCapturer.getSources() itself
// invokes the xdg-desktop-portal picker. If we then return one of
// those sources, Chromium triggers a SECOND portal because the
// pre-enumerated source IDs are stale on Wayland. To collapse this
// into a single portal invocation, when the Linux portal sentinel
// is set we skip getSources entirely and hand back a synthetic
// source id; Chromium then opens the portal once to actually
// resolve the capture.
// Default to the sentinel on Linux when no source has been
// pre-selected (e.g. fresh session where the renderer skipped the
// source picker entirely). This avoids calling getSources() which
// would itself trigger an extra portal dialog.
// The synthetic sentinel source id is ONLY correct on Wayland, where
// Chromium resolves getDisplayMedia through xdg-desktop-portal and
// hand-back of a real desktopCapturer id would trigger a duplicate
// portal dialog. On X11 there is no portal path for getDisplayMedia:
// Chromium must receive a REAL desktopCapturer source id, otherwise it
// cannot resolve the capture and throws "Could not start video source".
const isWaylandSession =
process.platform === "linux" && !shouldForceLinuxEgl(process.env);
const isLinuxPortalSentinel =
process.platform === "linux" && (sourceId === "screen:linux-portal" || !sourceId);
isWaylandSession && (sourceId === "screen:linux-portal" || !sourceId);
if (isLinuxPortalSentinel) {
callback({ video: { id: "screen:0:0", name: "Entire screen" } });
return;
}
const sources = await desktopCapturer.getSources({ types: ["screen", "window"] });
const source = sourceId
? (sources.find((s) => s.id === sourceId) ?? sources[0])
: sources[0];
? (sources.find((s) => s.id === sourceId) ??
sources.find((s) => s.id.startsWith("screen:")) ??
sources[0])
: (sources.find((s) => s.id.startsWith("screen:")) ?? sources[0]);
if (source) {
callback({
video: { id: source.id, name: source.name },
Expand Down