import { ipcMain, screen, BrowserWindow, desktopCapturer, shell, app, dialog, nativeImage, Tray, Menu } from "electron"; import { fileURLToPath } from "node:url"; import path from "node:path"; import fs from "node:fs/promises"; const __dirname$1 = path.dirname(fileURLToPath(import.meta.url)); const APP_ROOT = path.join(__dirname$1, ".."); const VITE_DEV_SERVER_URL$1 = process.env["VITE_DEV_SERVER_URL"]; const RENDERER_DIST$1 = path.join(APP_ROOT, "dist"); let hudOverlayWindow = null; ipcMain.on("hud-overlay-hide", () => { if (hudOverlayWindow && !hudOverlayWindow.isDestroyed()) { hudOverlayWindow.minimize(); } }); function createHudOverlayWindow() { const primaryDisplay = screen.getPrimaryDisplay(); const { workArea } = primaryDisplay; const windowWidth = 500; const windowHeight = 100; const x = Math.floor(workArea.x + (workArea.width - windowWidth) / 2); const y = Math.floor(workArea.y + workArea.height - windowHeight - 5); const win = new BrowserWindow({ width: windowWidth, height: windowHeight, minWidth: 500, maxWidth: 500, minHeight: 100, maxHeight: 100, x, y, frame: false, transparent: true, resizable: false, alwaysOnTop: true, skipTaskbar: true, hasShadow: false, webPreferences: { preload: path.join(__dirname$1, "preload.mjs"), nodeIntegration: false, contextIsolation: true, backgroundThrottling: false } }); win.webContents.on("did-finish-load", () => { win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString()); }); hudOverlayWindow = win; win.on("closed", () => { if (hudOverlayWindow === win) { hudOverlayWindow = null; } }); if (VITE_DEV_SERVER_URL$1) { win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=hud-overlay"); } else { win.loadFile(path.join(RENDERER_DIST$1, "index.html"), { query: { windowType: "hud-overlay" } }); } return win; } function createEditorWindow() { const win = new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, titleBarStyle: "hiddenInset", trafficLightPosition: { x: 12, y: 12 }, transparent: false, resizable: true, alwaysOnTop: false, skipTaskbar: false, title: "OpenScreen", backgroundColor: "#000000", webPreferences: { preload: path.join(__dirname$1, "preload.mjs"), nodeIntegration: false, contextIsolation: true, webSecurity: false, backgroundThrottling: false } }); win.maximize(); win.webContents.on("did-finish-load", () => { win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString()); }); if (VITE_DEV_SERVER_URL$1) { win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=editor"); } else { win.loadFile(path.join(RENDERER_DIST$1, "index.html"), { query: { windowType: "editor" } }); } return win; } function createSourceSelectorWindow() { const { width, height } = screen.getPrimaryDisplay().workAreaSize; const win = new BrowserWindow({ width: 620, height: 420, minHeight: 350, maxHeight: 500, x: Math.round((width - 620) / 2), y: Math.round((height - 420) / 2), frame: false, resizable: false, alwaysOnTop: true, transparent: true, backgroundColor: "#00000000", webPreferences: { preload: path.join(__dirname$1, "preload.mjs"), nodeIntegration: false, contextIsolation: true } }); if (VITE_DEV_SERVER_URL$1) { win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=source-selector"); } else { win.loadFile(path.join(RENDERER_DIST$1, "index.html"), { query: { windowType: "source-selector" } }); } return win; } let selectedSource = null; function registerIpcHandlers(createEditorWindow2, createSourceSelectorWindow2, getMainWindow, getSourceSelectorWindow, onRecordingStateChange) { 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; } createSourceSelectorWindow2(); }); ipcMain.handle("switch-to-editor", () => { const mainWin = getMainWindow(); if (mainWin) { mainWin.close(); } createEditorWindow2(); }); ipcMain.handle("store-recorded-video", async (_, videoData, fileName) => { 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) => { const source = selectedSource || { name: "Screen" }; if (onRecordingStateChange) { onRecordingStateChange(recording, source.name); } }); ipcMain.handle("open-external-url", async (_, url) => { try { await shell.openExternal(url); return { success: true }; } catch (error) { console.error("Failed to open URL:", error); return { success: false, error: String(error) }; } }); 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, fileName) => { try { const result = await dialog.showSaveDialog({ title: "Save Exported Video", defaultPath: path.join(app.getPath("downloads"), fileName), filters: [ { name: "MP4 Video", extensions: ["mp4"] } ], 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 = null; ipcMain.handle("set-current-video-path", (_, path2) => { currentVideoPath = path2; 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 }; }); } const __dirname = path.dirname(fileURLToPath(import.meta.url)); const RECORDINGS_DIR = path.join(app.getPath("userData"), "recordings"); async function ensureRecordingsDir() { try { await fs.mkdir(RECORDINGS_DIR, { recursive: true }); console.log("RECORDINGS_DIR:", RECORDINGS_DIR); console.log("User Data Path:", app.getPath("userData")); } catch (error) { console.error("Failed to create recordings directory:", error); } } process.env.APP_ROOT = path.join(__dirname, ".."); const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron"); 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; let mainWindow = null; let sourceSelectorWindow = null; let tray = null; let selectedSourceName = ""; function createWindow() { mainWindow = createHudOverlayWindow(); } function createTray() { const iconPath = path.join(process.env.VITE_PUBLIC || RENDERER_DIST, "rec-button.png"); let icon = nativeImage.createFromPath(iconPath); icon = icon.resize({ width: 24, height: 24, quality: "best" }); tray = new Tray(icon); updateTrayMenu(); } function updateTrayMenu() { if (!tray) return; const menuTemplate = [ { label: "Stop Recording", click: () => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("stop-recording-from-tray"); } } } ]; const contextMenu = Menu.buildFromTemplate(menuTemplate); tray.setContextMenu(contextMenu); tray.setToolTip(`Recording: ${selectedSourceName}`); } function createEditorWindowWrapper() { if (mainWindow) { mainWindow.close(); mainWindow = null; } mainWindow = createEditorWindow(); } function createSourceSelectorWindowWrapper() { sourceSelectorWindow = createSourceSelectorWindow(); sourceSelectorWindow.on("closed", () => { sourceSelectorWindow = null; }); return sourceSelectorWindow; } app.on("window-all-closed", () => { }); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); app.whenReady().then(async () => { const { ipcMain: ipcMain2 } = await import("electron"); ipcMain2.on("hud-overlay-close", () => { if (process.platform === "darwin") { app.quit(); } }); await ensureRecordingsDir(); registerIpcHandlers( createEditorWindowWrapper, createSourceSelectorWindowWrapper, () => mainWindow, () => sourceSelectorWindow, (recording, sourceName) => { selectedSourceName = sourceName; if (recording) { if (!tray) createTray(); updateTrayMenu(); } else { if (tray) { tray.destroy(); tray = null; } if (mainWindow) mainWindow.restore(); } } ); createWindow(); }); export { MAIN_DIST, RECORDINGS_DIR, RENDERER_DIST, VITE_DEV_SERVER_URL };