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
4 changes: 4 additions & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ interface Window {
setHudOverlayCompactWidth: (width: number) => void;
setHudOverlayMeasuredHeight: (height: number, expanded: boolean) => void;
getHudOverlayCaptureProtection: () => Promise<{ success: boolean; enabled: boolean }>;
getHudOverlayMousePassthroughSupported: () => Promise<{
success: boolean;
supported: boolean;
}>;
setHudOverlayCaptureProtection: (
enabled: boolean,
) => Promise<{ success: boolean; enabled: boolean }>;
Expand Down
3 changes: 3 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
getHudOverlayCaptureProtection: () => {
return ipcRenderer.invoke("get-hud-overlay-capture-protection");
},
getHudOverlayMousePassthroughSupported: () => {
return ipcRenderer.invoke("get-hud-overlay-mouse-passthrough-supported");
},
setHudOverlayCaptureProtection: (enabled: boolean) => {
return ipcRenderer.invoke("set-hud-overlay-capture-protection", enabled);
},
Expand Down
7 changes: 7 additions & 0 deletions electron/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,13 @@ ipcMain.handle("get-hud-overlay-capture-protection", () => {
};
});

ipcMain.handle("get-hud-overlay-mouse-passthrough-supported", () => {
return {
success: true,
supported: isHudOverlayMousePassthroughSupported(),
};
});

ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean) => {
loadHudOverlayCaptureProtectionSetting();
hudOverlayHiddenFromCapture = Boolean(enabled);
Expand Down
76 changes: 55 additions & 21 deletions src/components/launch/LaunchWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ import { ContentClamp } from "../ui/content-clamp";
import ProjectBrowserDialog, {
type ProjectLibraryEntry,
} from "../video-editor/ProjectBrowserDialog";
import {
canShowFloatingWebcamPreview,
canToggleFloatingWebcamPreview,
} from "./floatingWebcamPreview";
import {
mergeHudInteractiveBounds,
shouldRestoreHudMousePassthroughAfterDrag,
Expand Down Expand Up @@ -199,6 +203,9 @@ export function LaunchWindow() {
const [showFloatingWebcamPreview, setShowFloatingWebcamPreview] = useState(true);
const [webcamPreviewOffset, setWebcamPreviewOffset] = useState(DEFAULT_WEBCAM_PREVIEW_OFFSET);
const [recordingHudOffset, setRecordingHudOffset] = useState(DEFAULT_RECORDING_HUD_OFFSET);
const [hudOverlayMousePassthroughSupported, setHudOverlayMousePassthroughSupported] = useState<
boolean | null
>(null);
const [platform, setPlatform] = useState<string | null>(null);
const [appVersion, setAppVersion] = useState<string | null>(null);
const [updateStatus, setUpdateStatus] = useState<{
Expand Down Expand Up @@ -264,9 +271,14 @@ export function LaunchWindow() {
const micDropdownOpen = activeDropdown === "mic";
const webcamDropdownOpen = activeDropdown === "webcam";
const showWebcamControls = webcamEnabled && !recording;
const showRecordingWebcamPreview = webcamEnabled && showFloatingWebcamPreview;
const showRecordingWebcamPreview =
webcamEnabled &&
canShowFloatingWebcamPreview(
showFloatingWebcamPreview,
hudOverlayMousePassthroughSupported,
);
const shouldStreamWebcamPreview =
webcamEnabled && (showFloatingWebcamPreview || (showWebcamControls && webcamDropdownOpen));
webcamEnabled && (showRecordingWebcamPreview || (showWebcamControls && webcamDropdownOpen));
const { devices, selectedDeviceId, setSelectedDeviceId } = useMicrophoneDevices(
microphoneEnabled || micDropdownOpen,
microphoneDeviceId,
Expand Down Expand Up @@ -680,6 +692,24 @@ export function LaunchWindow() {
};
}, []);

useEffect(() => {
let cancelled = false;
const loadHudOverlayMousePassthroughSupport = async () => {
try {
const result = await window.electronAPI.getHudOverlayMousePassthroughSupported();
if (!cancelled && result.success) {
setHudOverlayMousePassthroughSupported(result.supported);
}
} catch (error) {
console.error("Failed to load HUD overlay mouse passthrough support:", error);
}
};
void loadHudOverlayMousePassthroughSupport();
return () => {
cancelled = true;
};
}, []);

useEffect(() => {
void preparePermissions({ startup: true });
}, [preparePermissions]);
Expand Down Expand Up @@ -1403,25 +1433,29 @@ export function LaunchWindow() {
>
{t("recording.turnOffWebcam")}
</DropdownItem>
<DropdownItem
icon={
showFloatingWebcamPreview ? (
<EyeOff size={16} />
) : (
<Eye size={16} />
)
}
selected={showFloatingWebcamPreview}
onClick={() => {
setShowFloatingWebcamPreview(
(current) => !current,
);
}}
>
{showFloatingWebcamPreview
? t("recording.hideFloatingWebcamPreview")
: t("recording.showFloatingWebcamPreview")}
</DropdownItem>
{canToggleFloatingWebcamPreview(
hudOverlayMousePassthroughSupported,
) ? (
<DropdownItem
icon={
showFloatingWebcamPreview ? (
<EyeOff size={16} />
) : (
<Eye size={16} />
)
}
selected={showFloatingWebcamPreview}
onClick={() => {
setShowFloatingWebcamPreview(
(current) => !current,
);
}}
>
{showFloatingWebcamPreview
? t("recording.hideFloatingWebcamPreview")
: t("recording.showFloatingWebcamPreview")}
</DropdownItem>
) : null}
</>
)}
{!webcamEnabled && (
Expand Down
26 changes: 26 additions & 0 deletions src/components/launch/floatingWebcamPreview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";

import {
canShowFloatingWebcamPreview,
canToggleFloatingWebcamPreview,
} from "./floatingWebcamPreview";

describe("canShowFloatingWebcamPreview", () => {
it("shows the floating preview only when it was requested and passthrough is supported", () => {
expect(canShowFloatingWebcamPreview(true, true)).toBe(true);
expect(canShowFloatingWebcamPreview(false, true)).toBe(false);
expect(canShowFloatingWebcamPreview(true, false)).toBe(false);
expect(canShowFloatingWebcamPreview(true, null)).toBe(false);
});
});

describe("canToggleFloatingWebcamPreview", () => {
it("keeps the toggle visible while support is unknown or available", () => {
expect(canToggleFloatingWebcamPreview(null)).toBe(true);
expect(canToggleFloatingWebcamPreview(true)).toBe(true);
});

it("hides the toggle when the platform cannot support the floating preview", () => {
expect(canToggleFloatingWebcamPreview(false)).toBe(false);
});
});
12 changes: 12 additions & 0 deletions src/components/launch/floatingWebcamPreview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function canShowFloatingWebcamPreview(
requested: boolean,
hudOverlayMousePassthroughSupported: boolean | null,
): boolean {
return requested && hudOverlayMousePassthroughSupported === true;
}

export function canToggleFloatingWebcamPreview(
hudOverlayMousePassthroughSupported: boolean | null,
): boolean {
return hudOverlayMousePassthroughSupported !== false;
}