diff --git a/src/cli/CliRecordRunner.test.tsx b/src/cli/CliRecordRunner.test.tsx index 3e01a0d47..04c0029ea 100644 --- a/src/cli/CliRecordRunner.test.tsx +++ b/src/cli/CliRecordRunner.test.tsx @@ -52,6 +52,8 @@ describe("CliRecordRunner", () => { recorder.startRecordingImmediately.mockClear(); recorder.toggleRecording.mockClear(); window.electronAPI = { + getPlatform: vi.fn(() => "win32"), + isNativeLinuxCaptureAvailable: vi.fn(async () => ({ success: true, available: false })), cliGetRequest: vi.fn(async () => request), cliLog: vi.fn(), cliDone: vi.fn(async () => undefined), @@ -81,4 +83,45 @@ describe("CliRecordRunner", () => { await waitFor(() => expect(recorder.startRecordingImmediately).toHaveBeenCalledTimes(1)); expect(window.electronAPI.cliDone).not.toHaveBeenCalled(); }); + + it("keeps source selection for Linux without the native helper", async () => { + vi.mocked(window.electronAPI.getPlatform).mockReturnValue("linux"); + render(); + await waitFor(() => expect(recorder.startRecordingImmediately).toHaveBeenCalledTimes(1)); + expect(window.electronAPI.getSources).toHaveBeenCalledTimes(1); + expect(window.electronAPI.selectSource).toHaveBeenCalledWith(screenSource, { persist: false }); + }); + + it("reports missing sources in the browser fallback", async () => { + vi.mocked(window.electronAPI.getPlatform).mockReturnValue("linux"); + vi.mocked(window.electronAPI.getSources).mockResolvedValueOnce([]); + render(); + await waitFor(() => + expect(window.electronAPI.cliDone).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + error: expect.stringContaining("Display index 0 not found"), + }), + ), + ); + expect(recorder.startRecordingImmediately).not.toHaveBeenCalled(); + }); + + it("lets the native Linux portal choose the source without Chromium enumeration", async () => { + vi.mocked(window.electronAPI.getPlatform).mockReturnValue("linux"); + vi.mocked(window.electronAPI.isNativeLinuxCaptureAvailable).mockResolvedValue({ + success: true, + available: true, + }); + vi.mocked(window.electronAPI.getSources).mockResolvedValueOnce([]); + render(); + await waitFor(() => expect(recorder.startRecordingImmediately).toHaveBeenCalledTimes(1)); + expect(window.electronAPI.getSources).not.toHaveBeenCalled(); + expect(window.electronAPI.selectSource).not.toHaveBeenCalled(); + expect(window.electronAPI.cliDone).not.toHaveBeenCalled(); + expect(window.electronAPI.cliLog).toHaveBeenCalledWith( + "info", + expect.stringContaining("--window do not apply"), + ); + }); }); diff --git a/src/cli/CliRecordRunner.tsx b/src/cli/CliRecordRunner.tsx index b7325120b..d585d855d 100644 --- a/src/cli/CliRecordRunner.tsx +++ b/src/cli/CliRecordRunner.tsx @@ -9,6 +9,7 @@ import { } from "@/components/video-editor/projectPersistence"; import { useScreenRecorder } from "@/hooks/useScreenRecorder"; import type { CliRecordRequest } from "@/lib/cliContracts"; +import { portalOwnsSourceSelection } from "@/lib/nativeLinuxRecording"; import { DEFAULT_PROJECT_APPEARANCE } from "@/lib/projectDefaults"; type Phase = "init" | "recording" | "stopping" | "done"; @@ -164,15 +165,24 @@ export function CliRecordRunner() { } requestRef.current = request; - const source = await pickSource(request); - const selected = await window.electronAPI.selectSource(source, { persist: false }); - if (!selected) { - throw new Error( - `Recording source "${source.name}" (${source.id}) is no longer available. ` + - "Re-list sources with `openscreen sources` and pick one that is currently shared.", + // The native Linux helper asks the portal to choose a source. Enumerating + // Chromium sources first opens a redundant picker and can return no screens. + if (await portalOwnsSourceSelection(window.electronAPI)) { + window.electronAPI.cliLog( + "info", + "Recording source: chosen in the system picker (--display and --window do not apply)", ); + } else { + const source = await pickSource(request); + const selected = await window.electronAPI.selectSource(source, { persist: false }); + if (!selected) { + throw new Error( + `Recording source "${source.name}" (${source.id}) is no longer available. ` + + "Re-list sources with `openscreen sources` and pick one that is currently shared.", + ); + } + window.electronAPI.cliLog("info", `Recording source: ${selected.name}`); } - window.electronAPI.cliLog("info", `Recording source: ${selected.name}`); setMicrophoneEnabled(Boolean(request.mic)); if (request.mic) {