Merge pull request #321 from meiiie/fix/win10-floating-webcam-preview

fix(launch): disable floating webcam preview without passthrough
This commit is contained in:
webadderall
2026-04-25 00:44:41 +10:00
committed by GitHub
6 changed files with 107 additions and 21 deletions
+4
View File
@@ -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 }>;
+3
View File
@@ -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);
},
+7
View File
@@ -413,6 +413,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);
+55 -21
View File
@@ -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,
@@ -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<{
@@ -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,
@@ -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]);
@@ -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 && (
@@ -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);
});
});
@@ -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;
}