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
1 change: 1 addition & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ interface Window {
switchToEditor: () => Promise<void>;
openSourceSelector: () => Promise<void>;
showRecordingControls: () => Promise<{ success: boolean }>;
setHudOverlayWebcamPreviewActive: (active: boolean) => Promise<{ success: boolean }>;
selectSource: (source: ProcessedDesktopSource) => Promise<ProcessedDesktopSource>;
showSourceHighlight: (source: ProcessedDesktopSource) => Promise<{ success: boolean }>;
getSelectedSource: () => Promise<ProcessedDesktopSource | null>;
Expand Down
27 changes: 27 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
isHudOverlayMousePassthroughSupported,
reassertHudOverlayMousePassthrough as reassertHudOverlayMouseState,
setHudOverlayRecordingActive,
setHudOverlayWebcamPreviewActive,
showUpdateToastWindow,
} from "./windows";

Expand Down Expand Up @@ -311,6 +312,16 @@ function showHudOverlayFromTray() {
}

function showOrCreateRecordingControls() {
// The Studio window has no reason to stay in front once the user is
// about to record - it just sits in the way of whatever they're trying
// to capture, forcing them to manually switch away from VybeClip before
// they can even start. Hiding (not closing) it lets the existing
// restore-on-return logic in createWindow()/focusOrCreateMainWindow()
// bring it back later exactly as before.
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.hide();
}

const existingHud = getHudOverlayWindow();
if (existingHud) {
showHudOverlayFromTray();
Expand Down Expand Up @@ -1040,6 +1051,17 @@ app.whenReady().then(async () => {
hud.close();
}

// The HUD also closes on a successful recording, right as switch-to-editor
// hands off to the editor window - in that case Studio should stay hidden
// so the editor is the only thing on screen. But if the user canceled
// before ever reaching the editor, Studio (hidden in
// showOrCreateRecordingControls) needs to come back, or they're left
// with no visible VybeClip window at all until they happen to click the
// Dock icon.
if (!getExistingEditorWindow()) {
restoreWindowSafely(mainWindow);
}

// If this was the last window (or we are in a state where we should quit), do it.
// We use a small delay to allow window.close() to propagate.
setTimeout(() => {
Expand Down Expand Up @@ -1101,6 +1123,11 @@ app.whenReady().then(async () => {
return { success: true };
});

ipcMain.handle("set-hud-overlay-webcam-preview-active", (_event, active: boolean) => {
setHudOverlayWebcamPreviewActive(Boolean(active));
return { success: true };
});

if (IS_SMOKE_EXPORT || process.env.RECORDLY_DEV_OPEN_RECORDING_INPUT) {
await logSmokeExportGpuDiagnostics();
if (IS_SMOKE_EXPORT) {
Expand Down
3 changes: 3 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
showRecordingControls: () => {
return ipcRenderer.invoke("show-recording-controls");
},
setHudOverlayWebcamPreviewActive: (active: boolean) => {
return ipcRenderer.invoke("set-hud-overlay-webcam-preview-active", active);
},
selectSource: (source: ProcessedDesktopSource) => {
return ipcRenderer.invoke("select-source", source);
},
Expand Down
32 changes: 27 additions & 5 deletions electron/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ let hudOverlayIgnoringMouse = true;
let hudOverlaySourceSelectionActive = false;
let hudOverlayMouseReassertTimer: NodeJS.Timeout | null = null;
let hudOverlayRecordingActive = false;
// The HUD window normally shrinks to a compact, non-click-through band during
// active recording (see isHudOverlayCanvasActive) so the stop/pause controls
// stay reliably clickable without depending on hover-based passthrough
// toggling. That compact band is only tall enough for the HUD bar itself,
// which leaves nowhere for the draggable webcam preview bubble to go - it
// gets visually clipped at the window's real edge and can't move vertically
// at all. While a webcam preview is showing, use the full work area instead;
// the existing hover-driven mouse-passthrough toggling (already wired to the
// bubble's own pointer events) keeps clicks passing through everywhere except
// the HUD bar/bubble themselves, independent of window size.
let hudOverlayWebcamPreviewActive = false;
let countdownWindow: BrowserWindow | null = null;
let updateToastWindow: BrowserWindow | null = null;

Expand Down Expand Up @@ -198,11 +209,22 @@ function isHudOverlayCanvasActive(): boolean {

function getHudOverlayBounds() {
const { workArea } = getHudOverlayDisplay();
return getHudOverlayWindowBounds(
workArea,
isHudOverlayCanvasActive(),
hudOverlayFallbackExpanded,
);
// Deliberately not just isHudOverlayCanvasActive(): that flag also gates
// the monitor-boundary-crossing drag relocation logic below, which should
// stay tied to the platform/passthrough check alone. Sizing the window
// itself additionally needs to widen for an active webcam preview so the
// draggable bubble has somewhere to go, even mid-recording.
const needsFullCanvas = isHudOverlayCanvasActive() || hudOverlayWebcamPreviewActive;
return getHudOverlayWindowBounds(workArea, needsFullCanvas, hudOverlayFallbackExpanded);
}

export function setHudOverlayWebcamPreviewActive(active: boolean): void {
const next = Boolean(active);
if (next === hudOverlayWebcamPreviewActive) {
return;
}
hudOverlayWebcamPreviewActive = next;
applyHudOverlayBounds();
}

function applyHudOverlayBounds() {
Expand Down
13 changes: 13 additions & 0 deletions src/components/launch/hooks/useWebcamPreviewOverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ export function useWebcamPreviewOverlay({
const shouldStreamWebcamPreview =
webcamEnabled && (showRecordingWebcamPreview || (showWebcamControls && webcamPopoverOpen));

useEffect(() => {
// The HUD overlay window shrinks to a compact, non-click-through band
// during active recording so the stop/pause controls stay reliably
// clickable (see windows.ts). That band is only tall enough for the HUD
// bar itself, leaving the draggable webcam bubble stuck at the bottom
// edge with no vertical room. Tell the main process when a preview is
// actually showing so it can widen the window to fit it.
void window.electronAPI?.setHudOverlayWebcamPreviewActive?.(showRecordingWebcamPreview);
return () => {
void window.electronAPI?.setHudOverlayWebcamPreviewActive?.(false);
};
}, [showRecordingWebcamPreview]);

useEffect(() => {
if (!webcamEnabled) {
webcamPreviewOffsetRef.current = DEFAULT_WEBCAM_PREVIEW_OFFSET;
Expand Down