mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
Merge pull request #486 from ExtraBinoss/fix/mouse-passthrough
fix: Mouse passthrough bug, v 1.3.0beta
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 =
|
||||
|
||||
@@ -515,6 +515,7 @@ function LaunchWindowContent() {
|
||||
<div
|
||||
ref={recordingWebcamPreviewContainerRef}
|
||||
className={`${styles.recordingWebcamPreview} ${styles.electronNoDrag} pointer-events-auto`}
|
||||
data-hud-interactive
|
||||
title={t("recording.webcam")}
|
||||
style={{
|
||||
transform: `translate(${webcamPreviewOffset.x}px, ${webcamPreviewOffset.y}px)`,
|
||||
|
||||
@@ -11,23 +11,54 @@ export function useLaunchHudInteractionState({
|
||||
isWebcamPreviewDraggingRef: RefObject<boolean>;
|
||||
webcamPreviewDragStartRef: RefObject<unknown>;
|
||||
}) {
|
||||
const anyPopoverOpenRef = useRef(false);
|
||||
const isMouseOverHudRef = useRef(false);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
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-hud-interactive], [data-radix-popper-content-wrapper]"
|
||||
);
|
||||
|
||||
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);
|
||||
@@ -39,8 +70,6 @@ export function useLaunchHudInteractionState({
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
}, []);
|
||||
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleHudMouseLeave = useCallback((event: MouseEvent<HTMLDivElement>) => {
|
||||
const nextTarget = event.relatedTarget;
|
||||
if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
|
||||
@@ -56,11 +85,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,
|
||||
|
||||
@@ -80,6 +80,7 @@ export function HudPopover({
|
||||
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={`launch-theme ${styles.menuCard} ${styles.electronNoDrag}`}
|
||||
data-hud-interactive
|
||||
unstyled
|
||||
side="top"
|
||||
align={align}
|
||||
|
||||
Reference in New Issue
Block a user