From 571bbb9434d4da7078f944a16040e4cd197dfb37 Mon Sep 17 00:00:00 2001 From: Uri Date: Sat, 18 Apr 2026 16:17:08 +0300 Subject: [PATCH] fix(linux/wayland): collapse 3-step capture flow into a single portal dialog Previously on Linux/Wayland, starting a fullscreen recording required three separate picker interactions: an in-app source dropdown plus two xdg-desktop-portal dialogs. The duplicate portal dialogs were caused by: 1. resolveBrowserCaptureSource() calling desktopCapturer.getSources() in the renderer, which itself triggers the portal on Wayland. 2. setDisplayMediaRequestHandler() in main also calling getSources(), which triggers another portal. 3. Returning a pre-enumerated source id to Chromium, which on Wayland is stale and forces Chromium to re-prompt via the portal during MediaStream creation. Additionally, the editor window failed to appear after recording on some Wayland sessions because 'ready-to-show' did not fire reliably. Changes: - LaunchWindow: skip the in-app source dropdown on Linux and start recording directly; the OS portal becomes the source picker. - useScreenRecorder: introduce a 'screen:linux-portal' sentinel source. When set, route capture through navigator.mediaDevices.getDisplayMedia() so the portal handles selection in a single dialog. Skip resolveBrowserCaptureSource for the sentinel to avoid an extra getSources() call. - electron/main: in setDisplayMediaRequestHandler, when the sentinel is set, skip desktopCapturer.getSources() entirely and return a synthetic source so Chromium opens the portal exactly once for the actual capture. - electron/windows: in createEditorWindow, also call win.show() from did-finish-load as a fallback for Linux/Wayland where ready-to-show may not fire. --- electron/main.ts | 16 ++++- electron/windows.ts | 5 ++ src/components/launch/LaunchWindow.tsx | 6 +- src/hooks/useScreenRecorder.ts | 92 ++++++++++++++++++++------ 4 files changed, 97 insertions(+), 22 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index 4adcc99c..f00f264d 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -891,8 +891,22 @@ app.whenReady().then(async () => { // ignored by the native capture pipeline. session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => { try { - const sources = await desktopCapturer.getSources({ types: ["screen", "window"] }); 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. + const isLinuxPortalSentinel = + process.platform === "linux" && sourceId === "screen:linux-portal"; + 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]; diff --git a/electron/windows.ts b/electron/windows.ts index 7439f716..819c2b5c 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -788,6 +788,11 @@ export function createEditorWindow(): BrowserWindow { win.webContents.on("did-finish-load", () => { console.log("[editor-window] did-finish-load", win.webContents.getURL()); win?.webContents.send("main-process-message", new Date().toLocaleString()); + // Fallback for Linux/Wayland where `ready-to-show` may not fire reliably. + if (!win.isDestroyed() && !win.isVisible()) { + console.log("[editor-window] forcing show after did-finish-load"); + win.show(); + } }); win.webContents.on("did-fail-load", (_event, errorCode, errorDescription, validatedURL) => { diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 2b12ea29..9bbcd2f0 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -1144,7 +1144,11 @@ export function LaunchWindow() {