mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
fix(linux): stabilize portal selection HUD
This commit is contained in:
Vendored
+1
@@ -204,6 +204,7 @@ interface RendererNativeExportCapabilities {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
hudOverlaySetIgnoreMouse: (ignore: boolean) => void;
|
||||
hudOverlaySetSourceSelectionActive: (active: boolean) => void;
|
||||
hudOverlayDrag: (phase: "start" | "move" | "end", screenX: number, screenY: number) => void;
|
||||
hudOverlayHide: () => void;
|
||||
hudOverlayClose: () => void;
|
||||
|
||||
@@ -158,6 +158,10 @@ describe("shouldResizeHudOverlayFallback", () => {
|
||||
it("keeps the recording HUD compact in non-passthrough mode", () => {
|
||||
expect(shouldResizeHudOverlayFallback(false, true)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the fallback stable while source selection is active", () => {
|
||||
expect(shouldResizeHudOverlayFallback(false, false, true)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldExpandHudOverlayFallback", () => {
|
||||
|
||||
@@ -41,8 +41,9 @@ export function getHudOverlayWindowBounds(
|
||||
export function shouldResizeHudOverlayFallback(
|
||||
mousePassthroughSupported: boolean,
|
||||
recordingActive: boolean,
|
||||
interactionLocked = false,
|
||||
): boolean {
|
||||
return !mousePassthroughSupported && !recordingActive;
|
||||
return !mousePassthroughSupported && !recordingActive && !interactionLocked;
|
||||
}
|
||||
|
||||
export function shouldExpandHudOverlayFallback({
|
||||
|
||||
+8
-3
@@ -167,6 +167,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
hudOverlaySetIgnoreMouse: (ignore: boolean) => {
|
||||
ipcRenderer.send("hud-overlay-set-ignore-mouse", ignore);
|
||||
},
|
||||
hudOverlaySetSourceSelectionActive: (active: boolean) => {
|
||||
ipcRenderer.send("hud-overlay-set-source-selection-active", active);
|
||||
},
|
||||
hudOverlayDrag: (phase: "start" | "move" | "end", screenX: number, screenY: number) => {
|
||||
ipcRenderer.send("hud-overlay-drag", phase, screenX, screenY);
|
||||
},
|
||||
@@ -708,8 +711,10 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
return ipcRenderer.invoke("set-current-recording-session", session, options);
|
||||
},
|
||||
onRecordingSessionChanged: (callback: (session: RecordingSessionData | null) => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: RecordingSessionData | null) =>
|
||||
callback(payload);
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
payload: RecordingSessionData | null,
|
||||
) => callback(payload);
|
||||
ipcRenderer.on("recording-session-changed", listener);
|
||||
return () => ipcRenderer.removeListener("recording-session-changed", listener);
|
||||
},
|
||||
@@ -896,7 +901,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
success?: boolean;
|
||||
value?: unknown;
|
||||
};
|
||||
return result?.success ? result.value ?? null : null;
|
||||
return result?.success ? (result.value ?? null) : null;
|
||||
},
|
||||
setAppSetting: (key: string, value: unknown) => {
|
||||
const result = ipcRenderer.sendSync("app-settings:set", key, value) as {
|
||||
|
||||
+31
-2
@@ -32,6 +32,7 @@ let hudOverlayHiddenFromCapture = true;
|
||||
let hudOverlayCaptureProtectionLoaded = false;
|
||||
let hudOverlayFallbackExpanded = false;
|
||||
let hudOverlayIgnoringMouse = true;
|
||||
let hudOverlaySourceSelectionActive = false;
|
||||
let hudOverlayMouseReassertTimer: NodeJS.Timeout | null = null;
|
||||
let hudOverlayRecordingActive = false;
|
||||
let hudOverlayWebcamPreviewVisible = false;
|
||||
@@ -287,7 +288,12 @@ function setHudOverlayFallbackExpanded(expanded: boolean) {
|
||||
}
|
||||
|
||||
function setHudOverlayMousePassthrough(ignore: boolean) {
|
||||
hudOverlayIgnoringMouse = hudOverlayRecordingActive ? false : ignore;
|
||||
hudOverlayIgnoringMouse =
|
||||
hudOverlaySourceSelectionActive && !hudOverlayRecordingActive
|
||||
? true
|
||||
: hudOverlayRecordingActive
|
||||
? false
|
||||
: ignore;
|
||||
|
||||
if (hudOverlayMouseReassertTimer) {
|
||||
clearTimeout(hudOverlayMouseReassertTimer);
|
||||
@@ -305,9 +311,21 @@ function setHudOverlayMousePassthrough(ignore: boolean) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hudOverlaySourceSelectionActive) {
|
||||
hudOverlayFallbackExpanded = false;
|
||||
hudOverlayWindow.setIgnoreMouseEvents(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const mousePassthroughSupported = isHudOverlayMousePassthroughSupported();
|
||||
if (!mousePassthroughSupported) {
|
||||
if (shouldResizeHudOverlayFallback(mousePassthroughSupported, hudOverlayRecordingActive)) {
|
||||
if (
|
||||
shouldResizeHudOverlayFallback(
|
||||
mousePassthroughSupported,
|
||||
hudOverlayRecordingActive,
|
||||
hudOverlaySourceSelectionActive,
|
||||
)
|
||||
) {
|
||||
setHudOverlayFallbackExpanded(!ignore);
|
||||
}
|
||||
hudOverlayWindow.setIgnoreMouseEvents(false);
|
||||
@@ -326,6 +344,17 @@ ipcMain.on("hud-overlay-set-ignore-mouse", (_event, ignore: boolean) => {
|
||||
setHudOverlayMousePassthrough(Boolean(ignore));
|
||||
});
|
||||
|
||||
ipcMain.on("hud-overlay-set-source-selection-active", (_event, active: boolean) => {
|
||||
hudOverlaySourceSelectionActive = Boolean(active);
|
||||
if (hudOverlaySourceSelectionActive) {
|
||||
hudOverlayFallbackExpanded = false;
|
||||
applyHudOverlayBounds();
|
||||
return;
|
||||
}
|
||||
|
||||
setHudOverlayMousePassthrough(hudOverlayIgnoringMouse);
|
||||
});
|
||||
|
||||
// Keep compatibility with existing drag IPC/state.
|
||||
let hudUserPosition: { x: number; y: number } | null = null;
|
||||
let hudDragOffset: { x: number; y: number } | null = null;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
normalizeBrowserMicrophoneProfile,
|
||||
resolveBrowserCaptureCursorPolicy,
|
||||
resolveLinuxPortalCursorPresentation,
|
||||
shouldLockHudDuringDisplaySelection,
|
||||
shouldUseLinuxPortalCapture,
|
||||
shouldUseNativeWindowsCaptureForSource,
|
||||
} from "./useScreenRecorder";
|
||||
@@ -246,6 +247,32 @@ describe("shouldUseLinuxPortalCapture", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldLockHudDuringDisplaySelection", () => {
|
||||
it("locks HUD fallback resizing while Linux portal selection is active", () => {
|
||||
expect(
|
||||
shouldLockHudDuringDisplaySelection({
|
||||
platform: "linux",
|
||||
useLinuxPortal: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps non-portal capture flows interactive", () => {
|
||||
expect(
|
||||
shouldLockHudDuringDisplaySelection({
|
||||
platform: "linux",
|
||||
useLinuxPortal: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
shouldLockHudDuringDisplaySelection({
|
||||
platform: "win32",
|
||||
useLinuxPortal: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getScreenCaptureCursorSetting", () => {
|
||||
it("normalizes only supported screen-capture cursor settings", () => {
|
||||
expect(getScreenCaptureCursorSetting({ cursor: "motion" } as MediaTrackSettings)).toBe(
|
||||
|
||||
@@ -145,6 +145,16 @@ export function shouldUseLinuxPortalCapture({
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldLockHudDuringDisplaySelection({
|
||||
platform,
|
||||
useLinuxPortal,
|
||||
}: {
|
||||
platform?: string;
|
||||
useLinuxPortal: boolean;
|
||||
}) {
|
||||
return platform === "linux" && useLinuxPortal;
|
||||
}
|
||||
|
||||
type UseScreenRecorderReturn = {
|
||||
recording: boolean;
|
||||
paused: boolean;
|
||||
@@ -1429,6 +1439,16 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
return;
|
||||
}
|
||||
|
||||
let hudSourceSelectionActive = false;
|
||||
const setHudSourceSelectionActive = (active: boolean) => {
|
||||
if (hudSourceSelectionActive === active) {
|
||||
return;
|
||||
}
|
||||
|
||||
hudSourceSelectionActive = active;
|
||||
window.electronAPI?.hudOverlaySetSourceSelectionActive?.(active);
|
||||
};
|
||||
|
||||
hasPromptedForReselect.current = false;
|
||||
startInFlight.current = true;
|
||||
setStarting(true);
|
||||
@@ -1628,7 +1648,14 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
|
||||
setRecording(true);
|
||||
window.electronAPI?.setRecordingState(true);
|
||||
try {
|
||||
await window.electronAPI?.setRecordingState(true);
|
||||
} catch (stateError) {
|
||||
console.warn(
|
||||
"Failed to notify main process that native recording started:",
|
||||
stateError,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1676,6 +1703,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
browserCaptureSourceId: browserCaptureSource.id,
|
||||
selectedSourceId: selectedSource.id,
|
||||
});
|
||||
if (shouldLockHudDuringDisplaySelection({ platform, useLinuxPortal })) {
|
||||
setHudSourceSelectionActive(true);
|
||||
}
|
||||
const browserScreenVideoConstraints = {
|
||||
mandatory: {
|
||||
chromeMediaSource: CHROME_MEDIA_SOURCE,
|
||||
@@ -1990,7 +2020,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
webcamStartTime.current === null ? 0 : webcamStartTime.current - mainStartedAt;
|
||||
recorder.start(RECORDER_TIMESLICE_MS);
|
||||
setRecording(true);
|
||||
window.electronAPI?.setRecordingState(true);
|
||||
try {
|
||||
await window.electronAPI?.setRecordingState(true);
|
||||
} catch (stateError) {
|
||||
console.warn("Failed to notify main process that recording started:", stateError);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to start recording:", error);
|
||||
alert(
|
||||
@@ -2008,6 +2042,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
await stopWebcamRecorder();
|
||||
}
|
||||
} finally {
|
||||
setHudSourceSelectionActive(false);
|
||||
startInFlight.current = false;
|
||||
setStarting(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user