Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 1 addition & 9 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ interface Window {
hudOverlayHide: () => void;
hudOverlayClose: () => void;
hudOverlayRendererReady: () => void;
hudOverlaySetWebcamPreviewVisible: (visible: boolean) => void;
getHudOverlayCaptureProtection: () => Promise<{ success: boolean; enabled: boolean }>;
getHudOverlayMousePassthroughSupported: () => Promise<{
success: boolean;
Expand Down Expand Up @@ -677,7 +676,6 @@ interface Window {
options?: {
preserveProjectPath?: boolean;
hideOverlayCursorByDefault?: boolean;
nativeCaptureUnavailable?: boolean;
},
) => Promise<{ success: boolean; webcamPath: string | null }>;
setCurrentRecordingSession: (
Expand All @@ -686,7 +684,6 @@ interface Window {
webcamPath?: string | null;
timeOffsetMs?: number;
hideOverlayCursorByDefault?: boolean;
nativeCaptureUnavailable?: boolean;
},
options?: { preserveProjectPath?: boolean },
) => Promise<{ success: boolean }>;
Expand All @@ -697,7 +694,6 @@ interface Window {
webcamPath?: string | null;
timeOffsetMs?: number;
hideOverlayCursorByDefault?: boolean;
nativeCaptureUnavailable?: boolean;
};
}>;
getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>;
Expand Down Expand Up @@ -843,11 +839,7 @@ interface Window {
/** Returns the app version from package.json */
getAppVersion: () => Promise<string>;
/** Hide the OS cursor before browser capture starts. */
hideOsCursor: () => Promise<{
success: boolean;
unsupported?: boolean;
platform?: string;
}>;
hideOsCursor: () => Promise<{ success: boolean }>;
/** Recording preferences (mic, system audio) */
getRecordingPreferences: () => Promise<{
success: boolean;
Expand Down
52 changes: 0 additions & 52 deletions electron/hudOverlayBounds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { describe, expect, it } from "vitest";
import {
getHudOverlayWindowBounds,
resizeHudOverlayFallbackBounds,
shouldExpandHudOverlayFallback,
shouldResizeHudOverlayFallback,
} from "./hudOverlayBounds";

describe("getHudOverlayWindowBounds", () => {
Expand Down Expand Up @@ -145,53 +143,3 @@ describe("resizeHudOverlayFallbackBounds", () => {
});
});
});

describe("shouldResizeHudOverlayFallback", () => {
it("allows non-passthrough HUD windows to expand for menus when idle", () => {
expect(shouldResizeHudOverlayFallback(false, false)).toBe(true);
});

it("does not resize full passthrough HUD windows", () => {
expect(shouldResizeHudOverlayFallback(true, false)).toBe(false);
});

it("keeps the recording HUD compact in non-passthrough mode", () => {
expect(shouldResizeHudOverlayFallback(false, true)).toBe(false);
});

it("keeps the fallback stable while source selection is active", () => {
expect(shouldResizeHudOverlayFallback(false, false, true)).toBe(false);
});
});

describe("shouldExpandHudOverlayFallback", () => {
it("expands while recording only when the floating webcam preview is visible", () => {
expect(
shouldExpandHudOverlayFallback({
fallbackExpanded: false,
recordingActive: true,
webcamPreviewVisible: true,
}),
).toBe(true);
});

it("keeps the compact recording fallback when there is no webcam preview", () => {
expect(
shouldExpandHudOverlayFallback({
fallbackExpanded: false,
recordingActive: true,
webcamPreviewVisible: false,
}),
).toBe(false);
});

it("preserves manual fallback expansion outside recording", () => {
expect(
shouldExpandHudOverlayFallback({
fallbackExpanded: true,
recordingActive: false,
webcamPreviewVisible: false,
}),
).toBe(true);
});
});
21 changes: 0 additions & 21 deletions electron/hudOverlayBounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,6 @@ export function getHudOverlayWindowBounds(
height,
};
}

export function shouldResizeHudOverlayFallback(
mousePassthroughSupported: boolean,
recordingActive: boolean,
interactionLocked = false,
): boolean {
return !mousePassthroughSupported && !recordingActive && !interactionLocked;
}

export function shouldExpandHudOverlayFallback({
fallbackExpanded,
recordingActive,
webcamPreviewVisible,
}: {
fallbackExpanded: boolean;
recordingActive: boolean;
webcamPreviewVisible: boolean;
}): boolean {
return fallbackExpanded || (recordingActive && webcamPreviewVisible);
}

export function resizeHudOverlayFallbackBounds(
workArea: HudOverlayWorkArea,
currentBounds: HudOverlayWorkArea,
Expand Down
9 changes: 2 additions & 7 deletions electron/ipc/register/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ export function registerProjectHandlers() {
return { success: false, error: String(error), message: 'Failed to open projects folder.' }
}
})
ipcMain.handle('set-current-video-path', async (_, path: string, options?: { preserveProjectPath?: boolean; hideOverlayCursorByDefault?: boolean; nativeCaptureUnavailable?: boolean }) => {
ipcMain.handle('set-current-video-path', async (_, path: string, options?: { preserveProjectPath?: boolean; hideOverlayCursorByDefault?: boolean }) => {
setCurrentVideoPath(normalizeVideoSourcePath(path) ?? path)
approveUserPath(currentVideoPath)
const resolvedSession = await resolveRecordingSession(currentVideoPath)
Expand All @@ -548,10 +548,6 @@ export function registerProjectHandlers() {
hideOverlayCursorByDefault:
normalizeBoolean(options?.hideOverlayCursorByDefault) ||
normalizeBoolean(resolvedSession.hideOverlayCursorByDefault),
nativeCaptureUnavailable:
normalizeBoolean(
options?.nativeCaptureUnavailable ?? resolvedSession.nativeCaptureUnavailable,
),
}

setCurrentRecordingSession(nextSession)
Expand All @@ -577,15 +573,14 @@ export function registerProjectHandlers() {
return { success: true, webcamPath: nextSession.webcamPath ?? null }
})

ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number; hideOverlayCursorByDefault?: boolean; nativeCaptureUnavailable?: boolean }, options?: { preserveProjectPath?: boolean }) => {
ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number; hideOverlayCursorByDefault?: boolean }, options?: { preserveProjectPath?: boolean }) => {
const normalizedVideoPath = normalizeVideoSourcePath(session.videoPath) ?? session.videoPath
setCurrentVideoPath(normalizedVideoPath)
setCurrentRecordingSession({
videoPath: normalizedVideoPath,
webcamPath: normalizeVideoSourcePath(session.webcamPath ?? null),
timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs),
hideOverlayCursorByDefault: normalizeBoolean(session.hideOverlayCursorByDefault),
nativeCaptureUnavailable: normalizeBoolean(session.nativeCaptureUnavailable),
});
await rememberApprovedLocalReadPath(currentRecordingSession!.videoPath)
await rememberApprovedLocalReadPath(currentRecordingSession!.webcamPath)
Expand Down
2 changes: 1 addition & 1 deletion electron/ipc/register/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function registerSettingsHandlers() {
// ---------------------------------------------------------------------------
ipcMain.handle("hide-cursor", () => {
if (process.platform !== "win32") {
return { success: false, unsupported: true, platform: process.platform };
return { success: true };
}

return { success: hideCursor() };
Expand Down
65 changes: 1 addition & 64 deletions electron/ipc/register/sourceMapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { describe, expect, it } from "vitest";
import {
getScreenSourceIdForDisplay,
LINUX_PORTAL_SCREEN_SOURCE_ID,
shouldUseSyntheticLinuxPortalSource,
} from "./sourceMapping";

describe("getScreenSourceIdForDisplay", () => {
Expand Down Expand Up @@ -48,66 +47,4 @@ describe("getScreenSourceIdForDisplay", () => {
}),
).toBe("screen:fallback:42");
});
});

describe("shouldUseSyntheticLinuxPortalSource", () => {
it("keeps Wayland portal capture on the synthetic source path", () => {
expect(
shouldUseSyntheticLinuxPortalSource({
env: { XDG_SESSION_TYPE: "wayland", WAYLAND_DISPLAY: "wayland-0" },
platform: "linux",
sourceId: LINUX_PORTAL_SCREEN_SOURCE_ID,
}),
).toBe(true);
});

it("lets X11 use Electron desktopCapturer sources instead of a synthetic id", () => {
expect(
shouldUseSyntheticLinuxPortalSource({
env: { XDG_SESSION_TYPE: "x11", DISPLAY: ":0" },
platform: "linux",
sourceId: LINUX_PORTAL_SCREEN_SOURCE_ID,
}),
).toBe(false);
});

it("recovers stale fallback ids through the synthetic path on Wayland", () => {
expect(
shouldUseSyntheticLinuxPortalSource({
env: { WAYLAND_DISPLAY: "wayland-0" },
platform: "linux",
sourceId: "screen:fallback:0",
}),
).toBe(true);
});

it("defaults unknown Linux sessions with WAYLAND_DISPLAY to the synthetic path", () => {
expect(
shouldUseSyntheticLinuxPortalSource({
env: { WAYLAND_DISPLAY: "wayland-0" },
platform: "linux",
sourceId: null,
}),
).toBe(true);
});

it("does not synthesize for concrete source ids", () => {
expect(
shouldUseSyntheticLinuxPortalSource({
env: { XDG_SESSION_TYPE: "wayland", WAYLAND_DISPLAY: "wayland-0" },
platform: "linux",
sourceId: "screen:42:0",
}),
).toBe(false);
});

it("does not synthesize outside Linux", () => {
expect(
shouldUseSyntheticLinuxPortalSource({
env: { XDG_SESSION_TYPE: "wayland", WAYLAND_DISPLAY: "wayland-0" },
platform: "win32",
sourceId: LINUX_PORTAL_SCREEN_SOURCE_ID,
}),
).toBe(false);
});
});
});
26 changes: 1 addition & 25 deletions electron/ipc/register/sourceMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,4 @@ export function getScreenSourceIdForDisplay({
}

return `screen:fallback:${displayId}`;
}

export function shouldUseSyntheticLinuxPortalSource({
env,
platform,
sourceId,
}: {
env: NodeJS.ProcessEnv;
platform: NodeJS.Platform | string;
sourceId?: string | null;
}) {
if (platform !== "linux") {
return false;
}

if (
sourceId &&
sourceId !== LINUX_PORTAL_SCREEN_SOURCE_ID &&
!sourceId.startsWith("screen:fallback:")
) {
return false;
}

return isLikelyLinuxWaylandSession(env);
}
}
14 changes: 7 additions & 7 deletions electron/ipc/register/sources.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { app, BrowserWindow, desktopCapturer, ipcMain } from "electron";
import { reassertHudOverlayMousePassthrough } from "../../windows";
import { ALLOW_RECORDLY_WINDOW_CAPTURE } from "../constants";
import { selectedSource, setSelectedSource } from "../state";
import type { SelectedSource } from "../types";
import { getScreen, parseWindowId } from "../utils";
import { getDisplayBoundsForSource, getDisplayWorkAreaForSource } from "../recording/ffmpeg";
import { getScreenSourceIdForDisplay } from "./sourceMapping";
import {
getNativeMacWindowSources,
resolveLinuxWindowBounds,
resolveMacWindowBounds,
resolveWindowsWindowBounds,
resolveLinuxWindowBounds,
stopWindowBoundsCapture,
} from "../cursor/bounds";
import { getDisplayBoundsForSource, getDisplayWorkAreaForSource } from "../recording/ffmpeg";
import { selectedSource, setSelectedSource } from "../state";
import type { SelectedSource } from "../types";
import { getScreen, parseWindowId } from "../utils";
import { getScreenSourceIdForDisplay } from "./sourceMapping";
import { reassertHudOverlayMousePassthrough } from "../../windows";

const execFileAsync = promisify(execFile);
const SOURCE_LIST_CACHE_TTL_MS = 1200;
Expand Down
1 change: 0 additions & 1 deletion electron/ipc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export type RecordingSessionData = {
webcamPath?: string | null;
timeOffsetMs?: number;
hideOverlayCursorByDefault?: boolean;
nativeCaptureUnavailable?: boolean;
};

export type PauseSegment = {
Expand Down
15 changes: 4 additions & 11 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
killWindowsCaptureProcess,
registerIpcHandlers,
} from "./ipc/handlers";
import { shouldUseSyntheticLinuxPortalSource } from "./ipc/register/sourceMapping";
import { ensureMediaServer } from "./mediaServer";
import { ensurePackagedRendererServer } from "./rendererServer";
import type { UpdateToastPayload } from "./updater";
Expand Down Expand Up @@ -232,7 +231,7 @@ function showHudOverlayFromTray() {
if (process.platform === "win32" && isHudOverlayMousePassthroughSupported()) {
hud.showInactive();
hud.moveTop();
reassertHudOverlayMouseState({ interactiveGraceMs: 1200 });
reassertHudOverlayMouseState();
return true;
}

Expand Down Expand Up @@ -1015,18 +1014,12 @@ app.whenReady().then(async () => {
// 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/Wayland when no source has been
// 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.
// X11 does not need this synthetic path; use Electron's documented
// desktopCapturer source flow there so getDisplayMedia receives a
// real source id instead of a Wayland-only portal sentinel.
const isLinuxPortalSentinel = shouldUseSyntheticLinuxPortalSource({
env: process.env,
platform: process.platform,
sourceId,
});
const isLinuxPortalSentinel =
process.platform === "linux" && (sourceId === "screen:linux-portal" || !sourceId);
if (isLinuxPortalSentinel) {
callback({ video: { id: "screen:0:0", name: "Entire screen" } });
return;
Expand Down
5 changes: 0 additions & 5 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,6 @@ contextBridge.exposeInMainWorld("electronAPI", {
hudOverlayRendererReady: () => {
ipcRenderer.send("hud-overlay-renderer-ready");
},
hudOverlaySetWebcamPreviewVisible: (visible: boolean) => {
ipcRenderer.send("hud-overlay-set-webcam-preview-visible", visible);
},
getHudOverlayCaptureProtection: () => {
return ipcRenderer.invoke("get-hud-overlay-capture-protection");
},
Expand Down Expand Up @@ -693,7 +690,6 @@ contextBridge.exposeInMainWorld("electronAPI", {
options?: {
preserveProjectPath?: boolean;
hideOverlayCursorByDefault?: boolean;
nativeCaptureUnavailable?: boolean;
},
) => {
return ipcRenderer.invoke("set-current-video-path", path, options);
Expand All @@ -704,7 +700,6 @@ contextBridge.exposeInMainWorld("electronAPI", {
webcamPath?: string | null;
timeOffsetMs?: number;
hideOverlayCursorByDefault?: boolean;
nativeCaptureUnavailable?: boolean;
},
options?: { preserveProjectPath?: boolean },
) => {
Expand Down
Loading