Files
openscreen/electron/ipc/handlers.ts
T
2026-01-20 20:05:14 -08:00

221 lines
6.2 KiB
TypeScript

import { ipcMain, desktopCapturer, BrowserWindow, shell, app, dialog } from 'electron'
import fs from 'node:fs/promises'
import path from 'node:path'
import { RECORDINGS_DIR } from '../main'
let selectedSource: any = null
export function registerIpcHandlers(
createEditorWindow: () => void,
createSourceSelectorWindow: () => BrowserWindow,
getMainWindow: () => BrowserWindow | null,
getSourceSelectorWindow: () => BrowserWindow | null,
onRecordingStateChange?: (recording: boolean, sourceName: string) => void
) {
ipcMain.handle('get-sources', async (_, opts) => {
const sources = await desktopCapturer.getSources(opts)
return sources.map(source => ({
id: source.id,
name: source.name,
display_id: source.display_id,
thumbnail: source.thumbnail ? source.thumbnail.toDataURL() : null,
appIcon: source.appIcon ? source.appIcon.toDataURL() : null
}))
})
ipcMain.handle('select-source', (_, source) => {
selectedSource = source
const sourceSelectorWin = getSourceSelectorWindow()
if (sourceSelectorWin) {
sourceSelectorWin.close()
}
return selectedSource
})
ipcMain.handle('get-selected-source', () => {
return selectedSource
})
ipcMain.handle('open-source-selector', () => {
const sourceSelectorWin = getSourceSelectorWindow()
if (sourceSelectorWin) {
sourceSelectorWin.focus()
return
}
createSourceSelectorWindow()
})
ipcMain.handle('switch-to-editor', () => {
const mainWin = getMainWindow()
if (mainWin) {
mainWin.close()
}
createEditorWindow()
})
ipcMain.handle('store-recorded-video', async (_, videoData: ArrayBuffer, fileName: string) => {
try {
const videoPath = path.join(RECORDINGS_DIR, fileName)
await fs.writeFile(videoPath, Buffer.from(videoData))
currentVideoPath = videoPath;
return {
success: true,
path: videoPath,
message: 'Video stored successfully'
}
} catch (error) {
console.error('Failed to store video:', error)
return {
success: false,
message: 'Failed to store video',
error: String(error)
}
}
})
ipcMain.handle('get-recorded-video-path', async () => {
try {
const files = await fs.readdir(RECORDINGS_DIR)
const videoFiles = files.filter(file => file.endsWith('.webm'))
if (videoFiles.length === 0) {
return { success: false, message: 'No recorded video found' }
}
const latestVideo = videoFiles.sort().reverse()[0]
const videoPath = path.join(RECORDINGS_DIR, latestVideo)
return { success: true, path: videoPath }
} catch (error) {
console.error('Failed to get video path:', error)
return { success: false, message: 'Failed to get video path', error: String(error) }
}
})
ipcMain.handle('set-recording-state', (_, recording: boolean) => {
const source = selectedSource || { name: 'Screen' }
if (onRecordingStateChange) {
onRecordingStateChange(recording, source.name)
}
})
ipcMain.handle('open-external-url', async (_, url: string) => {
try {
await shell.openExternal(url)
return { success: true }
} catch (error) {
console.error('Failed to open URL:', error)
return { success: false, error: String(error) }
}
})
// Return base path for assets so renderer can resolve file:// paths in production
ipcMain.handle('get-asset-base-path', () => {
try {
if (app.isPackaged) {
return path.join(process.resourcesPath, 'assets')
}
return path.join(app.getAppPath(), 'public', 'assets')
} catch (err) {
console.error('Failed to resolve asset base path:', err)
return null
}
})
ipcMain.handle('save-exported-video', async (_, videoData: ArrayBuffer, fileName: string) => {
try {
// Determine file type from extension
const isGif = fileName.toLowerCase().endsWith('.gif');
const filters = isGif
? [{ name: 'GIF Image', extensions: ['gif'] }]
: [{ name: 'MP4 Video', extensions: ['mp4'] }];
const result = await dialog.showSaveDialog({
title: isGif ? 'Save Exported GIF' : 'Save Exported Video',
defaultPath: path.join(app.getPath('downloads'), fileName),
filters,
properties: ['createDirectory', 'showOverwriteConfirmation']
});
if (result.canceled || !result.filePath) {
return {
success: false,
cancelled: true,
message: 'Export cancelled'
};
}
await fs.writeFile(result.filePath, Buffer.from(videoData));
return {
success: true,
path: result.filePath,
message: 'Video exported successfully'
};
} catch (error) {
console.error('Failed to save exported video:', error)
return {
success: false,
message: 'Failed to save exported video',
error: String(error)
}
}
})
ipcMain.handle('open-video-file-picker', async () => {
try {
const result = await dialog.showOpenDialog({
title: 'Select Video File',
defaultPath: RECORDINGS_DIR,
filters: [
{ name: 'Video Files', extensions: ['webm', 'mp4', 'mov', 'avi', 'mkv'] },
{ name: 'All Files', extensions: ['*'] }
],
properties: ['openFile']
});
if (result.canceled || result.filePaths.length === 0) {
return { success: false, cancelled: true };
}
return {
success: true,
path: result.filePaths[0]
};
} catch (error) {
console.error('Failed to open file picker:', error);
return {
success: false,
message: 'Failed to open file picker',
error: String(error)
};
}
});
let currentVideoPath: string | null = null;
ipcMain.handle('set-current-video-path', (_, path: string) => {
currentVideoPath = path;
return { success: true };
});
ipcMain.handle('get-current-video-path', () => {
return currentVideoPath ? { success: true, path: currentVideoPath } : { success: false };
});
ipcMain.handle('clear-current-video-path', () => {
currentVideoPath = null;
return { success: true };
});
ipcMain.handle('get-platform', () => {
return process.platform;
});
}