Files
Recordly/electron/main.ts
webadderall 5d040fbf2c refactor: split large files into focused modules
Split all source files exceeding 500 lines into smaller, focused modules:

- electron/main.ts → mainBootstrapHelpers, mainRuntimeState, mainUpdateIntegration, mainWindowControls
- electron/windows.ts → editorWindows, hudWindows, windowShared
- electron/updater.ts → updaterDialogs, updaterEventHandlers, updaterShared
- electron/preload.ts → preloadExtensionsBridge, preloadUpdateBridge
- electron/ipc/register/recording.ts → recording/ directory with focused handlers
- src/components/video-editor/VideoEditor.tsx → EditorContent, EditorHeader, EditorSidebar, EditorToolbar, hooks/
- src/components/video-editor/SettingsPanel.tsx → settings/ directory with per-section components
- src/components/video-editor/AnnotationSettingsPanel.tsx → tab components
- src/components/video-editor/ExtensionManager.tsx → extension-manager/ directory
- src/components/video-editor/VideoPlayback.tsx → videoPlaybackComponent/ directory
- src/components/video-editor/projectPersistence.ts → projectPersistence{Normalization,Paths,Regions,Shared}.ts
- src/components/video-editor/timeline/TimelineEditor.tsx → TimelineEditor/ directory
- src/components/video-editor/types.ts → focused type modules
- src/components/launch/LaunchWindow.tsx → LaunchWindow/ directory
- src/hooks/useScreenRecorder.ts → useScreenRecorder/ directory
- src/lib/exporter/audioEncoder.ts → audioEncoder/ directory
- src/lib/exporter/frameRenderer.ts → frameRenderer modules
- src/lib/exporter/modernFrameRenderer.ts → filters and lifecycle modules
- src/lib/exporter/modernVideoExporter.ts → modernVideoExporter/ directory
- src/lib/exporter/videoExporter.ts → video-exporter/ directory
- src/lib/exporter/streamingDecoder.ts → streamingDecoderHelpers.ts
- src/lib/extensions/extensionHost.ts → extensionHost{ApiFactory,QueryApi,RegistrationApi,Shared,State}.ts
- src/lib/extensions/types.ts → focused type modules
- electron/native/ScreenCaptureKitRecorder.swift → ScreenCaptureKitRecorder/ directory
- scripts/benchmark-export-queues.mjs → benchmark-export-queues/ directory
- .github/workflows/release.yml → extracted merge-macos-metadata composite action
- scripts/build-native-helpers.mjs → updated for multi-file Swift compilation

Also incorporates upstream default export quality change (good → source).
2026-04-20 20:41:42 +10:00

226 lines
6.3 KiB
TypeScript

import path from "node:path";
import { fileURLToPath } from "node:url";
import { app, desktopCapturer, ipcMain, session, systemPreferences } from "electron";
import { showCursor } from "./cursorHider";
import { registerExtensionIpcHandlers } from "./extensions/extensionIpc";
import {
cleanupNativeVideoExportSessions,
getSelectedSourceId,
killWindowsCaptureProcess,
registerIpcHandlers,
} from "./ipc/handlers";
import {
configureGpuAccelerationSwitches,
ensureRecordingsDir,
logSmokeExportGpuDiagnostics,
} from "./mainBootstrapHelpers";
import { mainRuntimeState } from "./mainRuntimeState";
import {
initializeMainUpdateIntegration,
registerUpdateIpcHandlers,
runManualUpdateCheck,
setupMainAutoUpdates,
} from "./mainUpdateIntegration";
import {
createEditorWindowWrapper,
createSourceSelectorWindowWrapper,
createTray,
createWindow,
focusOrCreateMainWindow,
initializeMainWindowControls,
reassertHudOverlayMouseState,
restoreWindowSafely,
setupApplicationMenu,
syncDockIcon,
updateTrayMenu,
} from "./mainWindowControls";
import { ensureMediaServer } from "./mediaServer";
import { ensurePackagedRendererServer } from "./rendererServer";
import {
createEditorWindow,
createHudOverlayWindow,
createSourceSelectorWindow,
getHudOverlayWindow,
isHudOverlayMousePassthroughSupported,
} from "./windows";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const IS_SMOKE_EXPORT = process.env.RECORDLY_SMOKE_EXPORT === "1";
app.commandLine.appendSwitch("ignore-gpu-blocklist");
app.commandLine.appendSwitch("enable-unsafe-webgpu");
app.commandLine.appendSwitch("enable-gpu-rasterization");
configureGpuAccelerationSwitches();
process.env.APP_ROOT = path.join(__dirname, "..");
export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"];
export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
? path.join(process.env.APP_ROOT, "public")
: RENDERER_DIST;
const hasSingleInstanceLock = app.requestSingleInstanceLock();
if (!hasSingleInstanceLock) {
app.quit();
}
initializeMainWindowControls({
rendererDist: RENDERER_DIST,
createHudOverlayWindow,
createEditorWindow,
createSourceSelectorWindow,
getHudOverlayWindow,
isHudOverlayMousePassthroughSupported,
onCheckForUpdates: runManualUpdateCheck,
});
initializeMainUpdateIntegration({
rendererDist: RENDERER_DIST,
focusOrCreateMainWindow,
reassertHudOverlayMouseState,
});
app.on("before-quit", () => {
killWindowsCaptureProcess();
showCursor();
cleanupNativeVideoExportSessions();
});
app.on("window-all-closed", () => {
if (IS_SMOKE_EXPORT || process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
focusOrCreateMainWindow();
});
app.on("second-instance", () => {
focusOrCreateMainWindow();
});
app.whenReady().then(async () => {
if (process.platform === "win32") {
app.setAppUserModelId("dev.recordly.app");
}
session.defaultSession.setPermissionCheckHandler((_webContents, permission) => {
const allowed = ["media", "audioCapture", "microphone", "camera", "videoCapture"];
return allowed.includes(permission);
});
session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback) => {
const allowed = ["media", "audioCapture", "microphone", "camera", "videoCapture"];
callback(allowed.includes(permission));
});
session.defaultSession.setDevicePermissionHandler(() => true);
if (process.platform === "darwin") {
const cameraStatus = systemPreferences.getMediaAccessStatus("camera");
if (cameraStatus !== "granted") {
await systemPreferences.askForMediaAccess("camera");
}
const micStatus = systemPreferences.getMediaAccessStatus("microphone");
if (micStatus !== "granted") {
await systemPreferences.askForMediaAccess("microphone");
}
} else if (process.platform === "win32") {
const cameraStatus = systemPreferences.getMediaAccessStatus("camera");
const micStatus = systemPreferences.getMediaAccessStatus("microphone");
if (cameraStatus !== "granted") {
console.warn(
`[permissions] Camera access is "${cameraStatus}" — webcam may not work. Check Windows Settings > Privacy > Camera.`,
);
}
if (micStatus !== "granted") {
console.warn(
`[permissions] Microphone access is "${micStatus}" — mic recording may not work. Check Windows Settings > Privacy > Microphone.`,
);
}
}
ipcMain.on("hud-overlay-close", () => {
app.quit();
});
registerUpdateIpcHandlers();
syncDockIcon();
createTray();
updateTrayMenu();
setupApplicationMenu();
await ensureRecordingsDir();
if (!VITE_DEV_SERVER_URL) {
try {
await ensurePackagedRendererServer(RENDERER_DIST);
} catch (error) {
console.warn("[renderer-server] Failed to start packaged renderer server:", error);
}
}
try {
await ensureMediaServer();
} catch (error) {
console.warn("[media-server] Failed to start media server:", error);
}
registerIpcHandlers(
createEditorWindowWrapper,
createSourceSelectorWindowWrapper,
() => mainRuntimeState.mainWindow,
() => mainRuntimeState.sourceSelectorWindow,
(recording: boolean, sourceName: string) => {
mainRuntimeState.selectedSourceName = sourceName;
if (!mainRuntimeState.tray) {
createTray();
}
updateTrayMenu(recording);
if (recording) {
reassertHudOverlayMouseState();
}
if (!recording) {
restoreWindowSafely(mainRuntimeState.mainWindow);
}
},
);
registerExtensionIpcHandlers();
if (IS_SMOKE_EXPORT) {
await logSmokeExportGpuDiagnostics(IS_SMOKE_EXPORT);
console.log(
`[smoke-export] Starting editor smoke export for ${process.env.RECORDLY_SMOKE_EXPORT_INPUT ?? "<missing input>"}`,
);
createEditorWindowWrapper();
return;
}
createWindow();
setupMainAutoUpdates();
session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => {
try {
const sources = await desktopCapturer.getSources({ types: ["screen", "window"] });
const sourceId = getSelectedSourceId();
const source = sourceId
? (sources.find((candidate) => candidate.id === sourceId) ?? sources[0])
: sources[0];
if (source) {
callback({ video: { id: source.id, name: source.name } });
} else {
callback({});
}
} catch (error) {
console.error("setDisplayMediaRequestHandler error:", error);
callback({});
}
});
});