Skip to content
Open
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
27 changes: 27 additions & 0 deletions electron/hudOverlayBounds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";

import {
getHudOverlayWindowBounds,
recordingForcesHudOverlayFallback,
resizeHudOverlayFallbackBounds,
shouldExpandHudOverlayFallback,
} from "./hudOverlayBounds";
Expand Down Expand Up @@ -145,6 +146,32 @@ describe("resizeHudOverlayFallbackBounds", () => {
});
});

describe("recordingForcesHudOverlayFallback", () => {
it("pins the HUD to the compact fallback while recording on Windows", () => {
expect(
recordingForcesHudOverlayFallback({ platform: "win32", recordingActive: true }),
).toBe(true);
});

it("keeps the full-work-area click-through overlay while recording on macOS", () => {
expect(
recordingForcesHudOverlayFallback({ platform: "darwin", recordingActive: true }),
).toBe(false);
});

it("keeps the full-work-area click-through overlay while recording on Linux", () => {
expect(
recordingForcesHudOverlayFallback({ platform: "linux", recordingActive: true }),
).toBe(false);
});

it("never forces the fallback outside recording", () => {
expect(
recordingForcesHudOverlayFallback({ platform: "win32", recordingActive: false }),
).toBe(false);
});
});

describe("shouldExpandHudOverlayFallback", () => {
it("expands while recording only when the floating webcam preview is visible", () => {
expect(
Expand Down
20 changes: 20 additions & 0 deletions electron/hudOverlayBounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}

/**
* While recording, Windows pins the HUD to a small, always-interactive window
* because focus changes there silently corrupt the WS_EX_TRANSPARENT flag that
* backs setIgnoreMouseEvents forwarding, which would leave the stop button
* unclickable. Every other platform keeps the full-work-area click-through
* overlay: shrinking it to a fixed rectangle turns the transparent margins
* around the bar into a dead zone that swallows clicks aimed at the app being
* recorded, and the bar can only be dragged inside that rectangle so moving it
* never frees the blocked area.
*/
export function recordingForcesHudOverlayFallback({
platform,
recordingActive,
}: {
platform: string;
recordingActive: boolean;
}): boolean {
return recordingActive && platform === "win32";
}

export function getHudOverlayWindowBounds(
workArea: HudOverlayWorkArea,
mousePassthroughSupported: boolean,
Expand Down
43 changes: 36 additions & 7 deletions electron/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { app, BrowserWindow, ipcMain } from "electron";
import { USER_DATA_PATH } from "./appPaths";
import {
getHudOverlayWindowBounds,
recordingForcesHudOverlayFallback,
resizeHudOverlayFallbackBounds,
shouldExpandHudOverlayFallback,
} from "./hudOverlayBounds";
Expand Down Expand Up @@ -193,6 +194,13 @@ function getHudOverlayDisplay() {
return getScreen().getPrimaryDisplay();
}

function recordingForcesHudFallback(): boolean {
return recordingForcesHudOverlayFallback({
platform: process.platform,
recordingActive: hudOverlayRecordingActive,
});
}

function getHudOverlayBounds() {
const { workArea } = getHudOverlayDisplay();
const fallbackExpanded = shouldExpandHudOverlayFallback({
Expand All @@ -202,7 +210,7 @@ function getHudOverlayBounds() {
});
return getHudOverlayWindowBounds(
workArea,
isHudOverlayMousePassthroughSupported() && !hudOverlayRecordingActive,
isHudOverlayMousePassthroughSupported() && !recordingForcesHudFallback(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep the full-work-area overlay on Linux.

isHudOverlayMousePassthroughSupported() returns false on Linux. Line 213 therefore always selects compact fallback bounds on Linux. recordingForcesHudFallback() returning false cannot preserve the full-work-area overlay.

Implement a Linux full-work-area click-through path, or remove Linux from this fallback contract and the PR objective.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/windows.ts` at line 213, Update the overlay bounds-selection logic
around isHudOverlayMousePassthroughSupported() so Linux retains full-work-area
click-through behavior instead of always selecting compact fallback bounds.
Implement the Linux-specific click-through path, or exclude Linux from the
fallback contract while preserving the intended full-work-area overlay
objective.

fallbackExpanded,
);
}
Expand Down Expand Up @@ -259,7 +267,7 @@ function positionUpdateToastWindow() {
}

function setHudOverlayFallbackExpanded(expanded: boolean) {
if (hudOverlayRecordingActive) {
if (recordingForcesHudFallback()) {
hudOverlayFallbackExpanded = false;
return;
}
Expand Down Expand Up @@ -290,7 +298,7 @@ function setHudOverlayMousePassthrough(ignore: boolean) {
hudOverlayIgnoringMouse =
hudOverlaySourceSelectionActive && !hudOverlayRecordingActive
? true
: hudOverlayRecordingActive
: recordingForcesHudFallback()
? false
: ignore;

Expand All @@ -303,7 +311,7 @@ function setHudOverlayMousePassthrough(ignore: boolean) {
return;
}

if (hudOverlayRecordingActive) {
if (recordingForcesHudFallback()) {
hudOverlayFallbackExpanded = false;
applyHudOverlayBounds();
hudOverlayWindow.setIgnoreMouseEvents(false);
Comment on lines +314 to 317

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Force compact bounds when the Windows fallback is active.

Setting hudOverlayFallbackExpanded = false does not keep the fallback compact. getHudOverlayBounds() calls shouldExpandHudOverlayFallback(), which expands during any active recording with a visible webcam preview. The Windows fallback can therefore become a 540-DIP interactive window and block clicks outside the HUD bar.

Override expansion when recordingForcesHudFallback() is true. Add a regression test for an active Windows recording with a visible webcam preview.

Proposed fix
 function getHudOverlayBounds() {
 	const { workArea } = getHudOverlayDisplay();
-	const fallbackExpanded = shouldExpandHudOverlayFallback({
-		fallbackExpanded: hudOverlayFallbackExpanded,
-		recordingActive: hudOverlayRecordingActive,
-		webcamPreviewVisible: hudOverlayWebcamPreviewVisible,
-	});
+	const fallbackExpanded = recordingForcesHudFallback()
+		? false
+		: shouldExpandHudOverlayFallback({
+				fallbackExpanded: hudOverlayFallbackExpanded,
+				recordingActive: hudOverlayRecordingActive,
+				webcamPreviewVisible: hudOverlayWebcamPreviewVisible,
+			});

Also applies to: 669-677

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/windows.ts` around lines 314 - 317, Update getHudOverlayBounds() or
its shouldExpandHudOverlayFallback() decision so recordingForcesHudFallback()
always forces compact bounds, regardless of active recording or visible webcam
preview. Preserve normal expansion behavior when the Windows fallback is not
forced, and add a regression test covering an active Windows recording with a
visible webcam preview.

Expand Down Expand Up @@ -503,7 +511,7 @@ export function createHudOverlayWindow(): BrowserWindow {
}

if (isHudOverlayMousePassthroughSupported()) {
if (hudOverlayRecordingActive) {
if (recordingForcesHudFallback()) {
hudOverlayIgnoringMouse = false;
win.setIgnoreMouseEvents(false);
} else {
Expand Down Expand Up @@ -638,7 +646,7 @@ export function reassertHudOverlayMousePassthrough(): void {
return;
}

if (hudOverlayRecordingActive) {
if (recordingForcesHudFallback()) {
hud.setIgnoreMouseEvents(false);
return;
}
Expand All @@ -658,10 +666,31 @@ export function reassertHudOverlayMousePassthrough(): void {
}

export function setHudOverlayRecordingActive(recording: boolean): void {
const wasFallbackForced = recordingForcesHudFallback();
hudOverlayRecordingActive = Boolean(recording);
hudOverlayFallbackExpanded = false;
applyHudOverlayBounds();
setHudOverlayMousePassthrough(!hudOverlayRecordingActive);

if (recordingForcesHudFallback()) {
// Compact, always-interactive HUD window: the whole window is the bar.
setHudOverlayMousePassthrough(false);
return;
}

if (wasFallbackForced) {
// Leaving the compact fallback re-expands the overlay to the whole work
// area, so it has to become click-through again before it swallows every
// click on the desktop.
setHudOverlayMousePassthrough(true);
return;
}

// The overlay already spans the work area and stays click-through while
// recording; the renderer's hover tracking owns the interactive state, so
// preserve it instead of forcing the window interactive (which would block
// clicks everywhere) or click-through (which would drop a click already
// aimed at the bar).
setHudOverlayMousePassthrough(hudOverlayIgnoringMouse);
}

export function createUpdateToastWindow(): BrowserWindow {
Expand Down
14 changes: 9 additions & 5 deletions src/components/launch/LaunchWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,17 @@ function LaunchWindowContent() {
ref={hudContentRef}
className="flex items-center overflow-visible flex-col-reverse pointer-events-none"
>
<div
className="flex flex-col items-center pointer-events-auto p-2"
onMouseEnter={handleHudMouseEnter}
onMouseLeave={handleHudMouseLeave}
>
<div className="flex flex-col items-center pointer-events-none p-2">
{/* The interactive area has to sit on the transformed wrapper, not on
the static column above it: transforms do not move layout boxes, so
a pointer-events-auto parent would keep swallowing clicks at the
bar's original bottom-centre position after the bar is dragged
away. */}
<div
ref={hudBarTransformRef}
className="pointer-events-auto"
onMouseEnter={handleHudMouseEnter}
onMouseLeave={handleHudMouseLeave}
style={{
transform: `translate3d(${recordingHudOffset.x}px, ${recordingHudOffset.y}px, 0)`,
}}
Expand Down