mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
Merge pull request #656 from webadderallorg/revert/hud-linux-fallback-fixes
Revert HUD passthrough and Linux fallback changes
This commit is contained in:
Vendored
+1
-9
@@ -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;
|
||||
@@ -677,7 +676,6 @@ interface Window {
|
||||
options?: {
|
||||
preserveProjectPath?: boolean;
|
||||
hideOverlayCursorByDefault?: boolean;
|
||||
nativeCaptureUnavailable?: boolean;
|
||||
},
|
||||
) => Promise<{ success: boolean; webcamPath: string | null }>;
|
||||
setCurrentRecordingSession: (
|
||||
@@ -686,7 +684,6 @@ interface Window {
|
||||
webcamPath?: string | null;
|
||||
timeOffsetMs?: number;
|
||||
hideOverlayCursorByDefault?: boolean;
|
||||
nativeCaptureUnavailable?: boolean;
|
||||
},
|
||||
options?: { preserveProjectPath?: boolean },
|
||||
) => Promise<{ success: boolean }>;
|
||||
@@ -697,7 +694,6 @@ interface Window {
|
||||
webcamPath?: string | null;
|
||||
timeOffsetMs?: number;
|
||||
hideOverlayCursorByDefault?: boolean;
|
||||
nativeCaptureUnavailable?: boolean;
|
||||
};
|
||||
}>;
|
||||
getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>;
|
||||
@@ -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;
|
||||
|
||||
@@ -3,8 +3,6 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
getHudOverlayWindowBounds,
|
||||
resizeHudOverlayFallbackBounds,
|
||||
shouldExpandHudOverlayFallback,
|
||||
shouldResizeHudOverlayFallback,
|
||||
} from "./hudOverlayBounds";
|
||||
|
||||
describe("getHudOverlayWindowBounds", () => {
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -548,10 +548,6 @@ export function registerProjectHandlers() {
|
||||
hideOverlayCursorByDefault:
|
||||
normalizeBoolean(options?.hideOverlayCursorByDefault) ||
|
||||
normalizeBoolean(resolvedSession.hideOverlayCursorByDefault),
|
||||
nativeCaptureUnavailable:
|
||||
normalizeBoolean(
|
||||
options?.nativeCaptureUnavailable ?? resolvedSession.nativeCaptureUnavailable,
|
||||
),
|
||||
}
|
||||
|
||||
setCurrentRecordingSession(nextSession)
|
||||
@@ -577,7 +573,7 @@ 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({
|
||||
@@ -585,7 +581,6 @@ export function registerProjectHandlers() {
|
||||
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)
|
||||
|
||||
@@ -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() };
|
||||
|
||||
@@ -3,7 +3,6 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
getScreenSourceIdForDisplay,
|
||||
LINUX_PORTAL_SCREEN_SOURCE_ID,
|
||||
shouldUseSyntheticLinuxPortalSource,
|
||||
} from "./sourceMapping";
|
||||
|
||||
describe("getScreenSourceIdForDisplay", () => {
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
getNativeMacWindowSources,
|
||||
resolveLinuxWindowBounds,
|
||||
resolveMacWindowBounds,
|
||||
resolveWindowsWindowBounds,
|
||||
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 { getDisplayBoundsForSource, getDisplayWorkAreaForSource } from "../recording/ffmpeg";
|
||||
import { getScreenSourceIdForDisplay } from "./sourceMapping";
|
||||
import {
|
||||
getNativeMacWindowSources,
|
||||
resolveMacWindowBounds,
|
||||
resolveWindowsWindowBounds,
|
||||
resolveLinuxWindowBounds,
|
||||
stopWindowBoundsCapture,
|
||||
} from "../cursor/bounds";
|
||||
import { reassertHudOverlayMousePassthrough } from "../../windows";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const SOURCE_LIST_CACHE_TTL_MS = 1200;
|
||||
|
||||
@@ -48,7 +48,6 @@ export type RecordingSessionData = {
|
||||
webcamPath?: string | null;
|
||||
timeOffsetMs?: number;
|
||||
hideOverlayCursorByDefault?: boolean;
|
||||
nativeCaptureUnavailable?: boolean;
|
||||
};
|
||||
|
||||
export type PauseSegment = {
|
||||
|
||||
+4
-11
@@ -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";
|
||||
@@ -232,7 +231,7 @@ function showHudOverlayFromTray() {
|
||||
if (process.platform === "win32" && isHudOverlayMousePassthroughSupported()) {
|
||||
hud.showInactive();
|
||||
hud.moveTop();
|
||||
reassertHudOverlayMouseState({ interactiveGraceMs: 1200 });
|
||||
reassertHudOverlayMouseState();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
@@ -693,7 +690,6 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
options?: {
|
||||
preserveProjectPath?: boolean;
|
||||
hideOverlayCursorByDefault?: boolean;
|
||||
nativeCaptureUnavailable?: boolean;
|
||||
},
|
||||
) => {
|
||||
return ipcRenderer.invoke("set-current-video-path", path, options);
|
||||
@@ -704,7 +700,6 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
webcamPath?: string | null;
|
||||
timeOffsetMs?: number;
|
||||
hideOverlayCursorByDefault?: boolean;
|
||||
nativeCaptureUnavailable?: boolean;
|
||||
},
|
||||
options?: { preserveProjectPath?: boolean },
|
||||
) => {
|
||||
|
||||
+6
-47
@@ -5,12 +5,7 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { app, BrowserWindow, ipcMain } from "electron";
|
||||
import { USER_DATA_PATH } from "./appPaths";
|
||||
import {
|
||||
getHudOverlayWindowBounds,
|
||||
resizeHudOverlayFallbackBounds,
|
||||
shouldExpandHudOverlayFallback,
|
||||
shouldResizeHudOverlayFallback,
|
||||
} from "./hudOverlayBounds";
|
||||
import { getHudOverlayWindowBounds, resizeHudOverlayFallbackBounds } from "./hudOverlayBounds";
|
||||
import { getPackagedRendererBaseUrl } from "./rendererServer";
|
||||
|
||||
const electronWindowsDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
@@ -35,7 +30,6 @@ let hudOverlayIgnoringMouse = true;
|
||||
let hudOverlaySourceSelectionActive = false;
|
||||
let hudOverlayMouseReassertTimer: NodeJS.Timeout | null = null;
|
||||
let hudOverlayRecordingActive = false;
|
||||
let hudOverlayWebcamPreviewVisible = false;
|
||||
let countdownWindow: BrowserWindow | null = null;
|
||||
let updateToastWindow: BrowserWindow | null = null;
|
||||
|
||||
@@ -196,15 +190,10 @@ function getHudOverlayDisplay() {
|
||||
|
||||
function getHudOverlayBounds() {
|
||||
const { workArea } = getHudOverlayDisplay();
|
||||
const fallbackExpanded = shouldExpandHudOverlayFallback({
|
||||
fallbackExpanded: hudOverlayFallbackExpanded,
|
||||
recordingActive: hudOverlayRecordingActive,
|
||||
webcamPreviewVisible: hudOverlayWebcamPreviewVisible,
|
||||
});
|
||||
return getHudOverlayWindowBounds(
|
||||
workArea,
|
||||
isHudOverlayMousePassthroughSupported() && !hudOverlayRecordingActive,
|
||||
fallbackExpanded,
|
||||
hudOverlayFallbackExpanded,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -311,21 +300,8 @@ function setHudOverlayMousePassthrough(ignore: boolean) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hudOverlaySourceSelectionActive) {
|
||||
hudOverlayFallbackExpanded = false;
|
||||
hudOverlayWindow.setIgnoreMouseEvents(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const mousePassthroughSupported = isHudOverlayMousePassthroughSupported();
|
||||
if (!mousePassthroughSupported) {
|
||||
if (
|
||||
shouldResizeHudOverlayFallback(
|
||||
mousePassthroughSupported,
|
||||
hudOverlayRecordingActive,
|
||||
hudOverlaySourceSelectionActive,
|
||||
)
|
||||
) {
|
||||
if (!isHudOverlayMousePassthroughSupported()) {
|
||||
if (process.platform !== "linux") {
|
||||
setHudOverlayFallbackExpanded(!ignore);
|
||||
}
|
||||
hudOverlayWindow.setIgnoreMouseEvents(false);
|
||||
@@ -434,18 +410,6 @@ ipcMain.handle("get-hud-overlay-mouse-passthrough-supported", () => {
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.on("hud-overlay-set-webcam-preview-visible", (_event, visible: boolean) => {
|
||||
const nextVisible = Boolean(visible);
|
||||
if (hudOverlayWebcamPreviewVisible === nextVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
hudOverlayWebcamPreviewVisible = nextVisible;
|
||||
if (hudOverlayRecordingActive) {
|
||||
applyHudOverlayBounds();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean) => {
|
||||
loadHudOverlayCaptureProtectionSetting();
|
||||
hudOverlayHiddenFromCapture = Boolean(enabled);
|
||||
@@ -468,7 +432,6 @@ ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean)
|
||||
export function createHudOverlayWindow(): BrowserWindow {
|
||||
loadHudOverlayCaptureProtectionSetting();
|
||||
hudOverlayFallbackExpanded = false;
|
||||
hudOverlayWebcamPreviewVisible = false;
|
||||
const initialBounds = getHudOverlayBounds();
|
||||
let hasShownHudWindow = false;
|
||||
|
||||
@@ -642,11 +605,7 @@ export function getHudOverlayWindow(): BrowserWindow | null {
|
||||
* hover detection on the HUD is immediately restored without requiring the
|
||||
* user to move their mouse over the bar.
|
||||
*/
|
||||
export function reassertHudOverlayMousePassthrough({
|
||||
interactiveGraceMs = 50,
|
||||
}: {
|
||||
interactiveGraceMs?: number;
|
||||
} = {}): void {
|
||||
export function reassertHudOverlayMousePassthrough(): void {
|
||||
if (process.platform !== "win32" || !isHudOverlayMousePassthroughSupported()) {
|
||||
return;
|
||||
}
|
||||
@@ -672,7 +631,7 @@ export function reassertHudOverlayMousePassthrough({
|
||||
if (!hud.isDestroyed()) {
|
||||
setHudOverlayMousePassthrough(hudOverlayIgnoringMouse);
|
||||
}
|
||||
}, interactiveGraceMs);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
export function setHudOverlayRecordingActive(recording: boolean): void {
|
||||
|
||||
@@ -152,16 +152,6 @@ function LaunchWindowContent() {
|
||||
hudOverlayMousePassthroughSupported,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
window.electronAPI?.hudOverlaySetWebcamPreviewVisible?.(showRecordingWebcamPreview);
|
||||
}, [showRecordingWebcamPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
window.electronAPI?.hudOverlaySetWebcamPreviewVisible?.(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const {
|
||||
recordingHudOffset,
|
||||
isHudDragging,
|
||||
|
||||
@@ -13,26 +13,10 @@ export function useLaunchHudInteractionState({
|
||||
}) {
|
||||
const isMouseOverHudRef = useRef(false);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const lastInteractiveReassertAtRef = useRef(0);
|
||||
|
||||
const setHudMouseInteractive = useCallback((force = false) => {
|
||||
const now = performance.now();
|
||||
if (
|
||||
!force &&
|
||||
isMouseOverHudRef.current &&
|
||||
now - lastInteractiveReassertAtRef.current < 250
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
isMouseOverHudRef.current = true;
|
||||
lastInteractiveReassertAtRef.current = now;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (openId !== null) {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
} else {
|
||||
// Proactively check if we should ignore mouse when popover closes
|
||||
@@ -45,7 +29,7 @@ export function useLaunchHudInteractionState({
|
||||
}, [openId]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseTracking = (e: globalThis.MouseEvent) => {
|
||||
const handleMouseOver = (e: globalThis.MouseEvent) => {
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (!target) return;
|
||||
const isInteractive = !!target.closest(
|
||||
@@ -53,12 +37,15 @@ export function useLaunchHudInteractionState({
|
||||
);
|
||||
|
||||
if (isInteractive) {
|
||||
setHudMouseInteractive();
|
||||
} else {
|
||||
isMouseOverHudRef.current = true;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
} else if (openId === null) {
|
||||
isMouseOverHudRef.current = false;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
if (
|
||||
openId === null &&
|
||||
!isHudDraggingRef.current &&
|
||||
!isWebcamPreviewDraggingRef.current &&
|
||||
!webcamPreviewDragStartRef.current &&
|
||||
@@ -70,26 +57,20 @@ export function useLaunchHudInteractionState({
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("mouseover", handleMouseTracking);
|
||||
window.addEventListener("mousemove", handleMouseTracking);
|
||||
return () => {
|
||||
window.removeEventListener("mouseover", handleMouseTracking);
|
||||
window.removeEventListener("mousemove", handleMouseTracking);
|
||||
};
|
||||
}, [
|
||||
isHudDraggingRef,
|
||||
isWebcamPreviewDraggingRef,
|
||||
setHudMouseInteractive,
|
||||
webcamPreviewDragStartRef,
|
||||
]);
|
||||
window.addEventListener("mouseover", handleMouseOver);
|
||||
return () => window.removeEventListener("mouseover", handleMouseOver);
|
||||
}, [openId, isHudDraggingRef, isWebcamPreviewDraggingRef, webcamPreviewDragStartRef]);
|
||||
|
||||
const beginInteractiveHudAction = useCallback(() => {
|
||||
setHudMouseInteractive(true);
|
||||
}, [setHudMouseInteractive]);
|
||||
isMouseOverHudRef.current = true;
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
}, []);
|
||||
|
||||
const handleHudMouseEnter = useCallback(() => {
|
||||
setHudMouseInteractive(true);
|
||||
}, [setHudMouseInteractive]);
|
||||
isMouseOverHudRef.current = true;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
}, []);
|
||||
|
||||
const handleHudMouseLeave = useCallback(
|
||||
(event: MouseEvent<HTMLDivElement>) => {
|
||||
@@ -104,6 +85,7 @@ export function useLaunchHudInteractionState({
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
if (
|
||||
openId === null &&
|
||||
!isHudDraggingRef.current &&
|
||||
!isWebcamPreviewDraggingRef.current &&
|
||||
!webcamPreviewDragStartRef.current &&
|
||||
@@ -113,7 +95,7 @@ export function useLaunchHudInteractionState({
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
[isHudDraggingRef, isWebcamPreviewDraggingRef, webcamPreviewDragStartRef],
|
||||
[openId, isHudDraggingRef, isWebcamPreviewDraggingRef, webcamPreviewDragStartRef],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -3587,7 +3587,6 @@ export function SettingsPanel({
|
||||
<Switch
|
||||
checked={showCursor}
|
||||
onCheckedChange={onShowCursorChange}
|
||||
disabled={nativeCaptureUnavailableSession}
|
||||
className="data-[state=checked]:bg-[#2563EB] scale-75"
|
||||
/>
|
||||
</label>
|
||||
@@ -3596,20 +3595,11 @@ export function SettingsPanel({
|
||||
<Switch
|
||||
checked={loopCursor}
|
||||
onCheckedChange={onLoopCursorChange}
|
||||
disabled={nativeCaptureUnavailableSession}
|
||||
className="data-[state=checked]:bg-[#2563EB] scale-75"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{nativeCaptureUnavailableSession ? (
|
||||
<div className="rounded-lg border border-amber-400/25 bg-amber-400/10 px-3 py-2 text-[10px] leading-4 text-muted-foreground">
|
||||
{tSettings(
|
||||
"effects.cursorOverlayUnavailable",
|
||||
"Cursor overlay is unavailable for this recording because the captured video already contains the system cursor.",
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="space-y-1.5">
|
||||
<ToggleGroup
|
||||
|
||||
@@ -1259,18 +1259,10 @@ export default function VideoEditor() {
|
||||
setExportProgress(resolveSavingExportProgress);
|
||||
}, []);
|
||||
|
||||
const handleShowCursorChange = useCallback(
|
||||
(nextShowCursor: boolean) => {
|
||||
if (nextShowCursor && sessionNativeCaptureUnavailable) {
|
||||
setNativeCaptureUnavailableModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setSessionShowCursorOverride(null);
|
||||
setShowCursor(nextShowCursor);
|
||||
},
|
||||
[sessionNativeCaptureUnavailable],
|
||||
);
|
||||
const handleShowCursorChange = useCallback((nextShowCursor: boolean) => {
|
||||
setSessionShowCursorOverride(null);
|
||||
setShowCursor(nextShowCursor);
|
||||
}, []);
|
||||
|
||||
const remountPreview = useCallback(() => {
|
||||
setIsPreviewReady(false);
|
||||
|
||||
@@ -3,12 +3,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createBrowserRecordingOptions,
|
||||
createProcessedMicrophoneConstraints,
|
||||
getScreenCaptureCursorSetting,
|
||||
normalizeBrowserMicrophoneProfile,
|
||||
resolveBrowserCaptureCursorPolicy,
|
||||
resolveLinuxPortalCursorPresentation,
|
||||
shouldLockHudDuringDisplaySelection,
|
||||
shouldUseLinuxPortalCapture,
|
||||
shouldUseNativeWindowsCaptureForSource,
|
||||
} from "./useScreenRecorder";
|
||||
|
||||
@@ -149,7 +145,6 @@ describe("resolveBrowserCaptureCursorPolicy", () => {
|
||||
streamCursor: "never",
|
||||
hideOsCursorBeforeRecording: true,
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,128 +155,8 @@ describe("resolveBrowserCaptureCursorPolicy", () => {
|
||||
streamCursor: "always",
|
||||
hideOsCursorBeforeRecording: false,
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not fake OS cursor hiding on Linux portal capture", () => {
|
||||
expect(resolveBrowserCaptureCursorPolicy({ platform: "linux" })).toEqual({
|
||||
streamCursor: "never",
|
||||
hideOsCursorBeforeRecording: false,
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveLinuxPortalCursorPresentation", () => {
|
||||
it("enables the Recordly overlay only when the portal confirms cursor-hidden capture", () => {
|
||||
expect(
|
||||
resolveLinuxPortalCursorPresentation({
|
||||
requestedCursor: "never",
|
||||
actualCursor: "never",
|
||||
}),
|
||||
).toEqual({
|
||||
hideEditorOverlayCursorByDefault: false,
|
||||
nativeCaptureUnavailable: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the overlay disabled when the portal embeds or omits cursor settings", () => {
|
||||
expect(
|
||||
resolveLinuxPortalCursorPresentation({
|
||||
requestedCursor: "never",
|
||||
actualCursor: "always",
|
||||
}),
|
||||
).toEqual({
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
});
|
||||
expect(
|
||||
resolveLinuxPortalCursorPresentation({
|
||||
requestedCursor: "never",
|
||||
actualCursor: null,
|
||||
}),
|
||||
).toEqual({
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldUseLinuxPortalCapture", () => {
|
||||
it("uses the portal when the selected source is the Linux sentinel", () => {
|
||||
expect(
|
||||
shouldUseLinuxPortalCapture({
|
||||
browserCaptureSourceId: "screen:linux-portal",
|
||||
selectedSourceId: "screen:linux-portal",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("uses the portal when a stale screen fallback resolves to the Linux sentinel", () => {
|
||||
expect(
|
||||
shouldUseLinuxPortalCapture({
|
||||
browserCaptureSourceId: "screen:linux-portal",
|
||||
selectedSourceId: "screen:fallback:42",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps live Electron screen sources on browser getUserMedia", () => {
|
||||
expect(
|
||||
shouldUseLinuxPortalCapture({
|
||||
browserCaptureSourceId: "screen:42:0",
|
||||
selectedSourceId: "screen:42:0",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("prefers a live Electron source over stale portal selection state", () => {
|
||||
expect(
|
||||
shouldUseLinuxPortalCapture({
|
||||
browserCaptureSourceId: "screen:42:0",
|
||||
selectedSourceId: "screen:linux-portal",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldLockHudDuringDisplaySelection", () => {
|
||||
it("locks HUD fallback resizing while Linux portal selection is active", () => {
|
||||
expect(
|
||||
shouldLockHudDuringDisplaySelection({
|
||||
platform: "linux",
|
||||
useLinuxPortal: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps non-portal capture flows interactive", () => {
|
||||
expect(
|
||||
shouldLockHudDuringDisplaySelection({
|
||||
platform: "linux",
|
||||
useLinuxPortal: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
shouldLockHudDuringDisplaySelection({
|
||||
platform: "win32",
|
||||
useLinuxPortal: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getScreenCaptureCursorSetting", () => {
|
||||
it("normalizes only supported screen-capture cursor settings", () => {
|
||||
expect(getScreenCaptureCursorSetting({ cursor: "motion" } as MediaTrackSettings)).toBe(
|
||||
"motion",
|
||||
);
|
||||
expect(
|
||||
getScreenCaptureCursorSetting({ cursor: "hidden" } as MediaTrackSettings),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldUseNativeWindowsCaptureForSource", () => {
|
||||
|
||||
@@ -46,12 +46,10 @@ export type BrowserMicrophoneProfile =
|
||||
| "no-noise-suppression"
|
||||
| "raw";
|
||||
type BrowserCaptureCursorMode = "always" | "never";
|
||||
type BrowserCaptureCursorSetting = BrowserCaptureCursorMode | "motion";
|
||||
export type BrowserCaptureCursorPolicy = {
|
||||
streamCursor: BrowserCaptureCursorMode;
|
||||
hideOsCursorBeforeRecording: boolean;
|
||||
hideEditorOverlayCursorByDefault: boolean;
|
||||
nativeCaptureUnavailable: boolean;
|
||||
};
|
||||
const DEFAULT_BROWSER_MICROPHONE_PROFILE: BrowserMicrophoneProfile = "processed";
|
||||
const BROWSER_MICROPHONE_PROFILES = new Set<BrowserMicrophoneProfile>([
|
||||
@@ -128,33 +126,6 @@ type DesktopCaptureMediaDevices = {
|
||||
getDisplayMedia: (constraints: unknown) => Promise<MediaStream>;
|
||||
};
|
||||
|
||||
export function shouldUseLinuxPortalCapture({
|
||||
browserCaptureSourceId,
|
||||
selectedSourceId,
|
||||
}: {
|
||||
browserCaptureSourceId?: string;
|
||||
selectedSourceId?: string;
|
||||
}) {
|
||||
if (browserCaptureSourceId && browserCaptureSourceId !== LINUX_PORTAL_SOURCE.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
selectedSourceId === LINUX_PORTAL_SOURCE.id ||
|
||||
browserCaptureSourceId === LINUX_PORTAL_SOURCE.id
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldLockHudDuringDisplaySelection({
|
||||
platform,
|
||||
useLinuxPortal,
|
||||
}: {
|
||||
platform?: string;
|
||||
useLinuxPortal: boolean;
|
||||
}) {
|
||||
return platform === "linux" && useLinuxPortal;
|
||||
}
|
||||
|
||||
type UseScreenRecorderReturn = {
|
||||
recording: boolean;
|
||||
paused: boolean;
|
||||
@@ -219,10 +190,8 @@ export function normalizeBrowserMicrophoneProfile(value?: string | null): Browse
|
||||
|
||||
export function resolveBrowserCaptureCursorPolicy({
|
||||
nativeWindowsCaptureStartFailed = false,
|
||||
platform,
|
||||
}: {
|
||||
nativeWindowsCaptureStartFailed?: boolean;
|
||||
platform?: string;
|
||||
} = {}): BrowserCaptureCursorPolicy {
|
||||
if (nativeWindowsCaptureStartFailed) {
|
||||
// If WGC already failed, avoid the telemetry overlay path that can lag on
|
||||
@@ -231,19 +200,6 @@ export function resolveBrowserCaptureCursorPolicy({
|
||||
streamCursor: "always",
|
||||
hideOsCursorBeforeRecording: false,
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (platform === "linux") {
|
||||
// Linux screen capture runs through xdg-desktop-portal/PipeWire. Ask the
|
||||
// portal to omit the cursor, but do not pretend we can globally hide the
|
||||
// OS cursor from Electron when the portal/compositor ignores that request.
|
||||
return {
|
||||
streamCursor: "never",
|
||||
hideOsCursorBeforeRecording: false,
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,37 +207,6 @@ export function resolveBrowserCaptureCursorPolicy({
|
||||
streamCursor: "never",
|
||||
hideOsCursorBeforeRecording: true,
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function getScreenCaptureCursorSetting(
|
||||
settings: MediaTrackSettings | null | undefined,
|
||||
): BrowserCaptureCursorSetting | null {
|
||||
const cursor = (settings as { cursor?: unknown } | null | undefined)?.cursor;
|
||||
return cursor === "always" || cursor === "never" || cursor === "motion" ? cursor : null;
|
||||
}
|
||||
|
||||
export function resolveLinuxPortalCursorPresentation({
|
||||
actualCursor,
|
||||
requestedCursor,
|
||||
}: {
|
||||
actualCursor: BrowserCaptureCursorSetting | null;
|
||||
requestedCursor: BrowserCaptureCursorMode;
|
||||
}): Pick<
|
||||
BrowserCaptureCursorPolicy,
|
||||
"hideEditorOverlayCursorByDefault" | "nativeCaptureUnavailable"
|
||||
> {
|
||||
if (requestedCursor === "never" && actualCursor === "never") {
|
||||
return {
|
||||
hideEditorOverlayCursorByDefault: false,
|
||||
nativeCaptureUnavailable: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hideEditorOverlayCursorByDefault: true,
|
||||
nativeCaptureUnavailable: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -447,7 +372,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
);
|
||||
const requestedBrowserMicrophoneProfile = useRef<string | null>(null);
|
||||
const hideEditorOverlayCursorByDefault = useRef(false);
|
||||
const nativeCaptureUnavailableForCursorOverlay = useRef(false);
|
||||
|
||||
const notifyRecordingFinalizationFailure = useCallback(async (message: string) => {
|
||||
setFinalizing(false);
|
||||
@@ -711,7 +635,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
// on Wayland that triggers an additional xdg-desktop-portal dialog.
|
||||
// The sentinel is handled later by routing through getDisplayMedia,
|
||||
// which lets the portal pick the source in a single dialog.
|
||||
if (source.id === LINUX_PORTAL_SOURCE.id) {
|
||||
if (source.id === "screen:linux-portal") {
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -756,7 +680,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const start = performance.now();
|
||||
console.log("[PERF:RENDERER] Finalize Session & Switch to Editor: STARTED");
|
||||
const shouldHideOverlayCursor = hideEditorOverlayCursorByDefault.current;
|
||||
const nativeCaptureUnavailable = nativeCaptureUnavailableForCursorOverlay.current;
|
||||
try {
|
||||
if (webcamPath) {
|
||||
await window.electronAPI.setCurrentRecordingSession({
|
||||
@@ -764,12 +687,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
webcamPath,
|
||||
timeOffsetMs: webcamTimeOffsetMs.current,
|
||||
hideOverlayCursorByDefault: shouldHideOverlayCursor,
|
||||
nativeCaptureUnavailable,
|
||||
});
|
||||
} else {
|
||||
await window.electronAPI.setCurrentVideoPath(videoPath, {
|
||||
hideOverlayCursorByDefault: shouldHideOverlayCursor,
|
||||
nativeCaptureUnavailable,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -778,7 +699,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
try {
|
||||
await window.electronAPI.setCurrentVideoPath(videoPath, {
|
||||
hideOverlayCursorByDefault: shouldHideOverlayCursor,
|
||||
nativeCaptureUnavailable,
|
||||
});
|
||||
} catch (fallbackError) {
|
||||
console.error("Failed to persist fallback video path:", fallbackError);
|
||||
@@ -1246,8 +1166,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
webcamPath,
|
||||
timeOffsetMs: webcamTimeOffsetMs.current,
|
||||
hideOverlayCursorByDefault: hideEditorOverlayCursorByDefault.current,
|
||||
nativeCaptureUnavailable:
|
||||
nativeCaptureUnavailableForCursorOverlay.current,
|
||||
});
|
||||
|
||||
console.log(
|
||||
@@ -1456,7 +1374,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
try {
|
||||
const platform = await window.electronAPI.getPlatform();
|
||||
hideEditorOverlayCursorByDefault.current = false;
|
||||
nativeCaptureUnavailableForCursorOverlay.current = false;
|
||||
const existingSource = await window.electronAPI.getSelectedSource();
|
||||
const selectedSource =
|
||||
existingSource ?? (platform === "linux" ? LINUX_PORTAL_SOURCE : null);
|
||||
@@ -1467,7 +1384,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
// Persist the synthetic Linux portal sentinel to main so that the
|
||||
// setDisplayMediaRequestHandler can short-circuit getSources() and
|
||||
// avoid triggering an extra portal dialog.
|
||||
if (!existingSource && selectedSource.id === LINUX_PORTAL_SOURCE.id) {
|
||||
if (!existingSource && selectedSource.id === "screen:linux-portal") {
|
||||
try {
|
||||
await window.electronAPI.selectSource(selectedSource);
|
||||
} catch (err) {
|
||||
@@ -1663,12 +1580,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
const browserCursorPolicy = resolveBrowserCaptureCursorPolicy({
|
||||
nativeWindowsCaptureStartFailed,
|
||||
platform,
|
||||
});
|
||||
hideEditorOverlayCursorByDefault.current =
|
||||
browserCursorPolicy.hideEditorOverlayCursorByDefault;
|
||||
nativeCaptureUnavailableForCursorOverlay.current =
|
||||
browserCursorPolicy.nativeCaptureUnavailable;
|
||||
|
||||
const wantsAudioCapture = microphoneEnabled || systemAudioEnabled;
|
||||
const browserCaptureSource = await resolveBrowserCaptureSource(selectedSource);
|
||||
@@ -1699,13 +1613,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
let videoTrack: MediaStreamTrack | undefined;
|
||||
let systemAudioIncluded = false;
|
||||
const mediaDevices = navigator.mediaDevices as DesktopCaptureMediaDevices;
|
||||
const useLinuxPortal = shouldUseLinuxPortalCapture({
|
||||
browserCaptureSourceId: browserCaptureSource.id,
|
||||
selectedSourceId: selectedSource.id,
|
||||
});
|
||||
if (shouldLockHudDuringDisplaySelection({ platform, useLinuxPortal })) {
|
||||
setHudSourceSelectionActive(true);
|
||||
}
|
||||
const useLinuxPortal = selectedSource.id === "screen:linux-portal";
|
||||
const browserScreenVideoConstraints = {
|
||||
mandatory: {
|
||||
chromeMediaSource: CHROME_MEDIA_SOURCE,
|
||||
@@ -1858,27 +1766,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
throw new Error("Media stream is not available.");
|
||||
}
|
||||
|
||||
if (useLinuxPortal) {
|
||||
const actualCursor = getScreenCaptureCursorSetting(videoTrack.getSettings());
|
||||
const cursorPresentation = resolveLinuxPortalCursorPresentation({
|
||||
actualCursor,
|
||||
requestedCursor: browserCursorPolicy.streamCursor,
|
||||
});
|
||||
hideEditorOverlayCursorByDefault.current =
|
||||
cursorPresentation.hideEditorOverlayCursorByDefault;
|
||||
nativeCaptureUnavailableForCursorOverlay.current =
|
||||
cursorPresentation.nativeCaptureUnavailable;
|
||||
if (cursorPresentation.nativeCaptureUnavailable) {
|
||||
console.warn(
|
||||
"Linux portal did not confirm cursor-hidden capture; disabling Recordly cursor overlay for this recording.",
|
||||
{
|
||||
actualCursor,
|
||||
requestedCursor: browserCursorPolicy.streamCursor,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await videoTrack.applyConstraints({
|
||||
frameRate: { ideal: TARGET_FRAME_RATE, max: TARGET_FRAME_RATE },
|
||||
@@ -1984,8 +1871,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
timeOffsetMs: webcamTimeOffsetMs.current,
|
||||
hideOverlayCursorByDefault:
|
||||
hideEditorOverlayCursorByDefault.current,
|
||||
nativeCaptureUnavailable:
|
||||
nativeCaptureUnavailableForCursorOverlay.current,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user