mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
fix: mouse passthrough on windows and other oses
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
resolveLinuxWindowBounds,
|
||||
stopWindowBoundsCapture,
|
||||
} from "../cursor/bounds";
|
||||
import { reassertHudOverlayMousePassthrough } from "../../windows";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const SOURCE_LIST_CACHE_TTL_MS = 1200;
|
||||
@@ -487,11 +488,24 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
throw loadError
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!highlightWin.isDestroyed()) highlightWin.close()
|
||||
}, 1700)
|
||||
// The highlight window appearing (even with focusable:false) can corrupt
|
||||
// the WS_EX_TRANSPARENT flag on the HUD on Windows 11+, breaking hover
|
||||
// detection until the user moves their mouse over the bar again.
|
||||
// Re-assert passthrough immediately so click-through is restored at once.
|
||||
reassertHudOverlayMousePassthrough();
|
||||
|
||||
return { success: true }
|
||||
const highlightCloseTimer = setTimeout(() => {
|
||||
if (!highlightWin.isDestroyed()) highlightWin.close()
|
||||
}, 1700)
|
||||
|
||||
highlightWin.on("closed", () => {
|
||||
clearTimeout(highlightCloseTimer);
|
||||
// Re-assert once more when the window is actually destroyed so the
|
||||
// native flag is clean regardless of timing.
|
||||
reassertHudOverlayMousePassthrough();
|
||||
});
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Failed to show source highlight:', error)
|
||||
return { success: false }
|
||||
|
||||
@@ -373,6 +373,7 @@ export function createHudOverlayWindow(): BrowserWindow {
|
||||
skipTaskbar: true,
|
||||
hasShadow: false,
|
||||
show: false,
|
||||
focusable: false,
|
||||
webPreferences: {
|
||||
preload: path.join(electronWindowsDir, "preload.mjs"),
|
||||
nodeIntegration: false,
|
||||
@@ -513,6 +514,36 @@ export function getHudOverlayWindow(): BrowserWindow | null {
|
||||
return hudOverlayWindow && !hudOverlayWindow.isDestroyed() ? hudOverlayWindow : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initialise the HUD overlay's mouse passthrough state.
|
||||
*
|
||||
* On Windows 11+, any new BrowserWindow appearing (even focusable:false ones
|
||||
* like the source highlight overlay) can silently corrupt the
|
||||
* WS_EX_TRANSPARENT flag that backs setIgnoreMouseEvents forwarding. Call
|
||||
* this after any operation that creates or destroys a sibling window so that
|
||||
* hover detection on the HUD is immediately restored without requiring the
|
||||
* user to move their mouse over the bar.
|
||||
*/
|
||||
export function reassertHudOverlayMousePassthrough(): void {
|
||||
if (process.platform !== "win32" || !isHudOverlayMousePassthroughSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hud = getHudOverlayWindow();
|
||||
if (!hud) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle off then back on so the native WS_EX_TRANSPARENT flag is fully
|
||||
// re-initialised rather than merely re-asserted in a potentially broken state.
|
||||
hud.setIgnoreMouseEvents(false);
|
||||
setTimeout(() => {
|
||||
if (!hud.isDestroyed()) {
|
||||
hud.setIgnoreMouseEvents(true, { forward: true });
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
export function createUpdateToastWindow(): BrowserWindow {
|
||||
const initialBounds = getUpdateToastBounds();
|
||||
const parentWindow =
|
||||
|
||||
@@ -11,23 +11,53 @@ export function useLaunchHudInteractionState({
|
||||
isWebcamPreviewDraggingRef: RefObject<boolean>;
|
||||
webcamPreviewDragStartRef: RefObject<unknown>;
|
||||
}) {
|
||||
const anyPopoverOpenRef = useRef(false);
|
||||
const isMouseOverHudRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
anyPopoverOpenRef.current = openId !== null;
|
||||
if (openId !== null) {
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
} else {
|
||||
// Proactively check if we should ignore mouse when popover closes
|
||||
setTimeout(() => {
|
||||
if (!isMouseOverHudRef.current && !anyPopoverOpenRef.current) {
|
||||
if (!isMouseOverHudRef.current) {
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
}, [openId]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseOver = (e: globalThis.MouseEvent) => {
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (!target) return;
|
||||
const isInteractive = !!target.closest(
|
||||
'.pointer-events-auto, [data-radix-popper-content-wrapper], [class*="menuCard"], [class*="recordingWebcamPreview"]'
|
||||
);
|
||||
|
||||
if (isInteractive) {
|
||||
isMouseOverHudRef.current = true;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
} else {
|
||||
isMouseOverHudRef.current = false;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
if (
|
||||
!isHudDraggingRef.current &&
|
||||
!isWebcamPreviewDraggingRef.current &&
|
||||
!webcamPreviewDragStartRef.current &&
|
||||
!isMouseOverHudRef.current
|
||||
) {
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("mouseover", handleMouseOver);
|
||||
return () => window.removeEventListener("mouseover", handleMouseOver);
|
||||
}, [isHudDraggingRef, isWebcamPreviewDraggingRef, webcamPreviewDragStartRef]);
|
||||
|
||||
const beginInteractiveHudAction = useCallback(() => {
|
||||
isMouseOverHudRef.current = true;
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
@@ -56,11 +86,8 @@ export function useLaunchHudInteractionState({
|
||||
!isHudDraggingRef.current &&
|
||||
!isWebcamPreviewDraggingRef.current &&
|
||||
!webcamPreviewDragStartRef.current &&
|
||||
!isMouseOverHudRef.current &&
|
||||
!anyPopoverOpenRef.current
|
||||
!isMouseOverHudRef.current
|
||||
) {
|
||||
// If a popover is open, we can still ignore mouse if the mouse is truly gone,
|
||||
// but we give a bit more breathing room (the 300ms timeout).
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
|
||||
interface LaunchPopoverCoordinatorValue {
|
||||
openId: string | null;
|
||||
@@ -22,6 +22,12 @@ export function LaunchPopoverCoordinatorProvider({ children }: { children: React
|
||||
|
||||
const isOpen = useCallback((id: string) => openId === id, [openId]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleBlur = () => setOpenId(null);
|
||||
window.addEventListener("blur", handleBlur);
|
||||
return () => window.removeEventListener("blur", handleBlur);
|
||||
}, []);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
openId,
|
||||
|
||||
Reference in New Issue
Block a user