mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
fix(hud): persist capture hiding review fixes
This commit is contained in:
+61
-5
@@ -1,7 +1,8 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { BrowserWindow, ipcMain } from "electron";
|
||||
import { app, BrowserWindow, ipcMain } from "electron";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const nodeRequire = createRequire(import.meta.url);
|
||||
@@ -16,7 +17,50 @@ const WINDOW_ICON_PATH = path.join(
|
||||
);
|
||||
|
||||
let hudOverlayWindow: BrowserWindow | null = null;
|
||||
let hudOverlayHiddenFromCapture = false;
|
||||
let hudOverlayHiddenFromCapture = true;
|
||||
let hudOverlayCaptureProtectionLoaded = false;
|
||||
|
||||
const HUD_OVERLAY_SETTINGS_FILE = path.join(app.getPath("userData"), "hud-overlay-settings.json");
|
||||
|
||||
function isHudOverlayCaptureProtectionSupported(): boolean {
|
||||
return process.platform !== "linux";
|
||||
}
|
||||
|
||||
function loadHudOverlayCaptureProtectionSetting(): boolean {
|
||||
if (hudOverlayCaptureProtectionLoaded) {
|
||||
return hudOverlayHiddenFromCapture;
|
||||
}
|
||||
|
||||
hudOverlayCaptureProtectionLoaded = true;
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(HUD_OVERLAY_SETTINGS_FILE)) {
|
||||
return hudOverlayHiddenFromCapture;
|
||||
}
|
||||
|
||||
const raw = fs.readFileSync(HUD_OVERLAY_SETTINGS_FILE, "utf-8");
|
||||
const parsed = JSON.parse(raw) as { hiddenFromCapture?: unknown };
|
||||
if (typeof parsed.hiddenFromCapture === "boolean") {
|
||||
hudOverlayHiddenFromCapture = parsed.hiddenFromCapture;
|
||||
}
|
||||
} catch {
|
||||
// Ignore settings read failures and fall back to defaults.
|
||||
}
|
||||
|
||||
return hudOverlayHiddenFromCapture;
|
||||
}
|
||||
|
||||
function persistHudOverlayCaptureProtectionSetting(enabled: boolean): void {
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
HUD_OVERLAY_SETTINGS_FILE,
|
||||
JSON.stringify({ hiddenFromCapture: enabled }, null, 2),
|
||||
"utf-8",
|
||||
);
|
||||
} catch {
|
||||
// Ignore settings write failures and keep runtime state working.
|
||||
}
|
||||
}
|
||||
|
||||
function getScreen() {
|
||||
return nodeRequire("electron").screen as typeof import("electron").screen;
|
||||
@@ -29,16 +73,24 @@ ipcMain.on("hud-overlay-hide", () => {
|
||||
});
|
||||
|
||||
ipcMain.handle("get-hud-overlay-capture-protection", () => {
|
||||
const enabled = loadHudOverlayCaptureProtectionSetting();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
enabled: hudOverlayHiddenFromCapture,
|
||||
enabled,
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean) => {
|
||||
loadHudOverlayCaptureProtectionSetting();
|
||||
hudOverlayHiddenFromCapture = Boolean(enabled);
|
||||
persistHudOverlayCaptureProtectionSetting(hudOverlayHiddenFromCapture);
|
||||
|
||||
if (hudOverlayWindow && !hudOverlayWindow.isDestroyed()) {
|
||||
if (
|
||||
isHudOverlayCaptureProtectionSupported() &&
|
||||
hudOverlayWindow &&
|
||||
!hudOverlayWindow.isDestroyed()
|
||||
) {
|
||||
hudOverlayWindow.setContentProtection(hudOverlayHiddenFromCapture);
|
||||
}
|
||||
|
||||
@@ -49,6 +101,8 @@ ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean)
|
||||
});
|
||||
|
||||
export function createHudOverlayWindow(): BrowserWindow {
|
||||
loadHudOverlayCaptureProtectionSetting();
|
||||
|
||||
const primaryDisplay = getScreen().getPrimaryDisplay();
|
||||
const { workArea } = primaryDisplay;
|
||||
|
||||
@@ -81,7 +135,9 @@ export function createHudOverlayWindow(): BrowserWindow {
|
||||
},
|
||||
});
|
||||
|
||||
win.setContentProtection(hudOverlayHiddenFromCapture);
|
||||
if (isHudOverlayCaptureProtectionSupported()) {
|
||||
win.setContentProtection(hudOverlayHiddenFromCapture);
|
||||
}
|
||||
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
|
||||
@@ -85,7 +85,8 @@ export function LaunchWindow() {
|
||||
const [selectedSource, setSelectedSource] = useState("Screen");
|
||||
const [hasSelectedSource, setHasSelectedSource] = useState(false);
|
||||
const [recordingsDirectory, setRecordingsDirectory] = useState<string | null>(null);
|
||||
const [hideHudFromCapture, setHideHudFromCapture] = useState(false);
|
||||
const [hideHudFromCapture, setHideHudFromCapture] = useState(true);
|
||||
const [platform, setPlatform] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkSelectedSource = async () => {
|
||||
@@ -106,6 +107,27 @@ export function LaunchWindow() {
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const loadPlatform = async () => {
|
||||
try {
|
||||
const nextPlatform = await window.electronAPI.getPlatform();
|
||||
if (!cancelled) {
|
||||
setPlatform(nextPlatform);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load platform:", error);
|
||||
}
|
||||
};
|
||||
|
||||
void loadPlatform();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
@@ -204,6 +226,7 @@ export function LaunchWindow() {
|
||||
? recordingsDirectory.split(/[\\/]/).filter(Boolean).pop() || recordingsDirectory
|
||||
: "recordings";
|
||||
const dividerClass = "mx-1 h-5 w-px shrink-0 bg-white/35";
|
||||
const supportsHudCaptureProtection = platform !== "linux";
|
||||
|
||||
const toggleMicrophone = () => {
|
||||
if (!recording) {
|
||||
@@ -266,21 +289,25 @@ export function LaunchWindow() {
|
||||
<div className={dividerClass} />
|
||||
|
||||
<div className={`flex items-center gap-1 ${styles.electronNoDrag}`}>
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
onClick={() => void toggleHudCaptureProtection()}
|
||||
title={
|
||||
hideHudFromCapture ? t("recording.showHudInVideo") : t("recording.hideHudFromVideo")
|
||||
}
|
||||
className="text-white/80 hover:bg-transparent"
|
||||
>
|
||||
{hideHudFromCapture ? (
|
||||
<EyeOff size={16} className="text-[#2563EB]" />
|
||||
) : (
|
||||
<Eye size={16} className="text-white/35" />
|
||||
)}
|
||||
</Button>
|
||||
{supportsHudCaptureProtection && (
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
onClick={() => void toggleHudCaptureProtection()}
|
||||
title={
|
||||
hideHudFromCapture
|
||||
? t("recording.showHudInVideo")
|
||||
: t("recording.hideHudFromVideo")
|
||||
}
|
||||
className="text-white/80 hover:bg-transparent"
|
||||
>
|
||||
{hideHudFromCapture ? (
|
||||
<EyeOff size={16} className="text-white/35" />
|
||||
) : (
|
||||
<Eye size={16} className="text-[#2563EB]" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
|
||||
Vendored
-208
@@ -1,210 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="../electron/electron-env" />
|
||||
|
||||
interface ProcessedDesktopSource {
|
||||
id: string;
|
||||
name: string;
|
||||
display_id: string;
|
||||
thumbnail: string | null;
|
||||
appIcon: string | null;
|
||||
originalName?: string;
|
||||
sourceType?: "screen" | "window";
|
||||
appName?: string;
|
||||
windowTitle?: string;
|
||||
}
|
||||
|
||||
interface CursorTelemetryPoint {
|
||||
timeMs: number;
|
||||
cx: number;
|
||||
cy: number;
|
||||
interactionType?: "move" | "click" | "double-click" | "right-click" | "middle-click" | "mouseup";
|
||||
cursorType?:
|
||||
| "arrow"
|
||||
| "text"
|
||||
| "pointer"
|
||||
| "crosshair"
|
||||
| "open-hand"
|
||||
| "closed-hand"
|
||||
| "resize-ew"
|
||||
| "resize-ns"
|
||||
| "not-allowed";
|
||||
}
|
||||
|
||||
interface SystemCursorAsset {
|
||||
dataUrl: string;
|
||||
hotspotX: number;
|
||||
hotspotY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
hudOverlayHide: () => void;
|
||||
hudOverlayClose: () => void;
|
||||
getHudOverlayCaptureProtection: () => Promise<{ success: boolean; enabled: boolean }>;
|
||||
setHudOverlayCaptureProtection: (
|
||||
enabled: boolean,
|
||||
) => Promise<{ success: boolean; enabled: boolean }>;
|
||||
getSources: (opts: Electron.SourcesOptions) => Promise<ProcessedDesktopSource[]>;
|
||||
switchToEditor: () => Promise<void>;
|
||||
openSourceSelector: () => Promise<void>;
|
||||
selectSource: (source: any) => Promise<any>;
|
||||
getSelectedSource: () => Promise<any>;
|
||||
startNativeScreenRecording: (
|
||||
source: any,
|
||||
options?: {
|
||||
capturesSystemAudio?: boolean;
|
||||
capturesMicrophone?: boolean;
|
||||
microphoneDeviceId?: string;
|
||||
microphoneLabel?: string;
|
||||
},
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
stopNativeScreenRecording: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
startFfmpegRecording: (source: any) => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
stopFfmpegRecording: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
storeRecordedVideo: (
|
||||
videoData: ArrayBuffer,
|
||||
fileName: string,
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message: string;
|
||||
error?: string;
|
||||
}>;
|
||||
getRecordedVideoPath: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
readLocalFile: (filePath: string) => Promise<{
|
||||
success: boolean;
|
||||
data?: Uint8Array;
|
||||
error?: string;
|
||||
}>;
|
||||
getAssetBasePath: () => Promise<string | null>;
|
||||
setRecordingState: (recording: boolean) => Promise<void>;
|
||||
getCursorTelemetry: (videoPath?: string) => Promise<{
|
||||
success: boolean;
|
||||
samples: CursorTelemetryPoint[];
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
getSystemCursorAssets: () => Promise<{
|
||||
success: boolean;
|
||||
cursors: Record<string, SystemCursorAsset>;
|
||||
error?: string;
|
||||
}>;
|
||||
onStopRecordingFromTray: (callback: () => void) => () => void;
|
||||
onRecordingStateChanged: (
|
||||
callback: (state: { recording: boolean; sourceName: string }) => void,
|
||||
) => () => void;
|
||||
onRecordingInterrupted: (
|
||||
callback: (state: { reason: string; message: string }) => void,
|
||||
) => () => void;
|
||||
onCursorStateChanged: (
|
||||
callback: (state: { cursorType: CursorTelemetryPoint["cursorType"] }) => void,
|
||||
) => () => void;
|
||||
openExternalUrl: (url: string) => Promise<{ success: boolean; error?: string }>;
|
||||
getAccessibilityPermissionStatus: () => Promise<{
|
||||
success: boolean;
|
||||
trusted: boolean;
|
||||
prompted: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
requestAccessibilityPermission: () => Promise<{
|
||||
success: boolean;
|
||||
trusted: boolean;
|
||||
prompted: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
getScreenRecordingPermissionStatus: () => Promise<{
|
||||
success: boolean;
|
||||
status: string;
|
||||
error?: string;
|
||||
}>;
|
||||
openScreenRecordingPreferences: () => Promise<{ success: boolean; error?: string }>;
|
||||
openAccessibilityPreferences: () => Promise<{ success: boolean; error?: string }>;
|
||||
saveExportedVideo: (
|
||||
videoData: ArrayBuffer,
|
||||
fileName: string,
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
canceled?: boolean;
|
||||
}>;
|
||||
openVideoFilePicker: () => Promise<{ success: boolean; path?: string; canceled?: boolean }>;
|
||||
setCurrentVideoPath: (path: string) => Promise<{ success: boolean }>;
|
||||
getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>;
|
||||
clearCurrentVideoPath: () => Promise<{ success: boolean }>;
|
||||
saveProjectFile: (
|
||||
projectData: unknown,
|
||||
suggestedName?: string,
|
||||
existingProjectPath?: string,
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
canceled?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
loadProjectFile: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
project?: unknown;
|
||||
message?: string;
|
||||
canceled?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
loadCurrentProjectFile: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
project?: unknown;
|
||||
message?: string;
|
||||
canceled?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
onMenuLoadProject: (callback: () => void) => () => void;
|
||||
onMenuSaveProject: (callback: () => void) => () => void;
|
||||
onMenuSaveProjectAs: (callback: () => void) => () => void;
|
||||
hideOsCursor: () => Promise<{ success: boolean }>;
|
||||
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
||||
onRequestSaveBeforeClose: (callback: () => Promise<void>) => () => void;
|
||||
getRecordingsDirectory: () => Promise<{
|
||||
success: boolean;
|
||||
path: string;
|
||||
isDefault: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
chooseRecordingsDirectory: () => Promise<{
|
||||
success: boolean;
|
||||
canceled?: boolean;
|
||||
path?: string;
|
||||
isDefault?: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user