diff --git a/electron/gpuSwitches.test.ts b/electron/gpuSwitches.test.ts index 4cbb1cdf..718cc04c 100644 --- a/electron/gpuSwitches.test.ts +++ b/electron/gpuSwitches.test.ts @@ -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"], }); }); diff --git a/electron/gpuSwitches.ts b/electron/gpuSwitches.ts index 7b7c81ee..a9b53f5f 100644 --- a/electron/gpuSwitches.ts +++ b/electron/gpuSwitches.ts @@ -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 { @@ -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"], }; } diff --git a/electron/main.ts b/electron/main.ts index ed05ffeb..b16fb1c8 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -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, @@ -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 }, diff --git a/electron/windows.ts b/electron/windows.ts index 478584b5..d22ba74d 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -301,9 +301,13 @@ function setHudOverlayMousePassthrough(ignore: boolean) { } if (!isHudOverlayMousePassthroughSupported()) { - if (process.platform !== "linux") { - setHudOverlayFallbackExpanded(!ignore); - } + // Expand the fallback overlay while the HUD is interactive so that + // popovers/menus have room to render. Without this the window stays at + // the compact 860x160 fallback and upward popovers are clipped by the + // window edge (e.g. only the last items visible). This previously + // skipped Linux, where passthrough is unsupported, so the bug showed up + // exactly there. + setHudOverlayFallbackExpanded(!ignore); hudOverlayWindow.setIgnoreMouseEvents(false); return; }