import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { BrowserWindow, ipcMain, screen } from "electron"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const APP_ROOT = path.join(__dirname, ".."); const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; const RENDERER_DIST = path.join(APP_ROOT, "dist"); const HEADLESS = process.env["HEADLESS"] === "true"; // Asset base URL for renderer (wallpapers, etc.). Packaged: extraResources copies // public/wallpapers -> resources/wallpapers. Unpackaged: /public/. const ASSET_BASE_DIR = process.defaultApp ? path.join(__dirname, "..", "public") : process.resourcesPath; const ASSET_BASE_URL_ARG = `--asset-base-url=${pathToFileURL(`${ASSET_BASE_DIR}${path.sep}`).toString()}`; let hudOverlayWindow: BrowserWindow | null = null; ipcMain.on("hud-overlay-hide", () => { if (hudOverlayWindow && !hudOverlayWindow.isDestroyed()) { hudOverlayWindow.minimize(); } }); ipcMain.on("hud-overlay-ignore-mouse-events", (_event, ignore: boolean) => { if (hudOverlayWindow && !hudOverlayWindow.isDestroyed()) { hudOverlayWindow.setIgnoreMouseEvents(ignore, { forward: true }); } }); ipcMain.on("hud-overlay-move-by", (_event, deltaX: number, deltaY: number) => { if ( !hudOverlayWindow || hudOverlayWindow.isDestroyed() || !Number.isFinite(deltaX) || !Number.isFinite(deltaY) ) { return; } const [x, y] = hudOverlayWindow.getPosition(); hudOverlayWindow.setPosition(Math.round(x + deltaX), Math.round(y + deltaY), false); }); /** * Creates the always-on-top HUD overlay window centred at the bottom of the * primary display. The window is frameless, transparent, and follows the user * across macOS Spaces so it is never lost when switching virtual desktops. */ export function createHudOverlayWindow(): BrowserWindow { const primaryDisplay = screen.getPrimaryDisplay(); const { workArea } = primaryDisplay; const windowWidth = 600; const windowHeight = 160; 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: 600, maxWidth: 600, minHeight: 160, maxHeight: 160, x: x, y: y, frame: false, transparent: true, resizable: false, alwaysOnTop: true, skipTaskbar: true, hasShadow: false, show: !HEADLESS, webPreferences: { preload: path.join(__dirname, "preload.mjs"), additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, backgroundThrottling: false, }, }); win.setIgnoreMouseEvents(true, { forward: true }); // Follow the user across macOS Spaces (virtual desktops). // Without this the HUD stays pinned to the Space it was first opened on. if (process.platform === "darwin") { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } win.webContents.on("did-finish-load", () => { win?.webContents.send("main-process-message", new Date().toLocaleString()); }); hudOverlayWindow = win; win.on("closed", () => { if (hudOverlayWindow === win) { hudOverlayWindow = null; } }); if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL + "?windowType=hud-overlay"); } else { win.loadFile(path.join(RENDERER_DIST, "index.html"), { query: { windowType: "hud-overlay" }, }); } return win; } /** * Creates the main editor window. Starts maximised with a hidden title bar on * macOS. This window is not always-on-top and appears in the taskbar/dock. */ export function createEditorWindow(): BrowserWindow { const isMac = process.platform === "darwin"; const win = new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, ...(isMac && { titleBarStyle: "hiddenInset", trafficLightPosition: { x: 12, y: 12 }, }), transparent: false, resizable: true, alwaysOnTop: false, skipTaskbar: false, title: "OpenScreen", backgroundColor: "#000000", show: !HEADLESS, webPreferences: { preload: path.join(__dirname, "preload.mjs"), additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, webSecurity: false, backgroundThrottling: false, }, }); // Maximize the window by default win.maximize(); win.webContents.on("did-finish-load", () => { win?.webContents.send("main-process-message", new Date().toLocaleString()); }); if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL + "?windowType=editor"); } else { win.loadFile(path.join(RENDERER_DIST, "index.html"), { query: { windowType: "editor" }, }); } return win; } /** * Creates the floating source-selector window used to pick a screen or window * to record. Frameless, transparent, and follows the user across macOS Spaces. */ export function createSourceSelectorWindow(): BrowserWindow { 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, "preload.mjs"), additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, }, }); // Follow the user across macOS Spaces so the selector appears on the // active desktop regardless of where the HUD was originally opened. if (process.platform === "darwin") { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL + "?windowType=source-selector"); } else { win.loadFile(path.join(RENDERER_DIST, "index.html"), { query: { windowType: "source-selector" }, }); } return win; } /** * Creates a centered transparent countdown overlay window that sits above the * HUD while recording pre-roll is running. */ export function createCountdownOverlayWindow(): BrowserWindow { const { workArea } = screen.getPrimaryDisplay(); const overlayWidth = 420; const overlayHeight = 260; const win = new BrowserWindow({ width: overlayWidth, height: overlayHeight, minWidth: overlayWidth, maxWidth: overlayWidth, minHeight: overlayHeight, maxHeight: overlayHeight, x: Math.round(workArea.x + (workArea.width - overlayWidth) / 2), y: Math.round(workArea.y + (workArea.height - overlayHeight) / 2), frame: false, resizable: false, alwaysOnTop: true, skipTaskbar: true, focusable: false, transparent: true, backgroundColor: "#00000000", hasShadow: false, show: false, webPreferences: { preload: path.join(__dirname, "preload.mjs"), additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, backgroundThrottling: false, }, }); win.setIgnoreMouseEvents(true); if (process.platform === "darwin") { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL + "?windowType=countdown-overlay"); } else { win.loadFile(path.join(RENDERER_DIST, "index.html"), { query: { windowType: "countdown-overlay" }, }); } return win; }