"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SleepBlocker = exports.showOrCreateWindow = exports.showQuitConfirmation = void 0; exports.setShowQuitConfirmation = setShowQuitConfirmation; exports.isMacOS = isMacOS; exports.createWindow = createWindow; exports.getNodeWrapperPaths = getNodeWrapperPaths; exports.setupNodeWrapper = setupNodeWrapper; const electron_1 = require("electron"); const constants_1 = require("./constants"); const keybindings_1 = require("./keybindings"); const path_1 = __importDefault(require("path")); const fs = __importStar(require("fs")); const paths_1 = require("./paths"); const loadingOverlay_1 = require("./loadingOverlay"); exports.showQuitConfirmation = false; function setShowQuitConfirmation(value) { exports.showQuitConfirmation = value; } function isMacOS() { return process.platform === 'darwin'; } /** * Reads the user's theme preference from the settings file. */ function getThemeMode() { try { const filePath = (0, paths_1.getSettingsPbPath)(); if (!fs.existsSync(filePath)) { return 'DARK'; } const content = fs.readFileSync(filePath, 'utf-8'); const config = JSON.parse(content); const themeMode = config?.userSettings?.themeMode; if (themeMode && themeMode.includes('INHERIT')) { return electron_1.nativeTheme.shouldUseDarkColors ? 'DARK' : 'LIGHT'; } if (themeMode && themeMode.includes('LIGHT')) { return 'LIGHT'; } return 'DARK'; } catch (e) { console.error('Error reading theme mode:', e); return 'DARK'; } } /** * Ensures the app is visible in the dock for MacOS with the icon set. * When refocusing the app after being hidden in the dock, the icon is sometimes lost. * This ensures the icon is always visible. */ function ensureAppIsInDock() { void electron_1.app.dock?.show(); if (isMacOS() && electron_1.app.dock) { const iconPath = path_1.default.join(__dirname, '..', 'icon.png'); electron_1.app.dock.setIcon(electron_1.nativeImage.createFromPath(iconPath)); } } // --------------------------------------------------------------------------- // Window Management // --------------------------------------------------------------------------- /** * Creates and returns a new BrowserWindow pointed at `url`. * Uses a hidden title bar with native traffic lights on macOS. * Node integration is disabled and context isolation is enabled for security. */ function createWindow(url) { ensureAppIsInDock(); const theme = getThemeMode().toUpperCase(); const isLight = theme.includes('LIGHT'); const backgroundColor = isLight ? '#FAFAFA' : '#131313'; const foregroundColor = isLight ? '#383A42' : '#FAFAFA'; const win = new electron_1.BrowserWindow({ width: 1400, height: 900, title: electron_1.app.getName(), icon: path_1.default.join(__dirname, '..', 'icon.png'), titleBarStyle: 'hidden', titleBarOverlay: isMacOS() ? false : { color: backgroundColor, symbolColor: foregroundColor, height: 30, }, backgroundColor, trafficLightPosition: { x: 12, y: 12 }, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path_1.default.join(__dirname, 'preload.js'), }, }); win.webContents.setWindowOpenHandler((details) => { void electron_1.shell.openExternal(details.url); return { action: 'deny' }; }); (0, loadingOverlay_1.attachLoadingOverlay)(win, foregroundColor, backgroundColor); (0, keybindings_1.registerKeybindings)(win, { createNewWindow: () => { void createWindow(url); }, onQuitRequested: () => { exports.showQuitConfirmation = true; electron_1.app.quit(); }, }); void win.loadURL(url); return win; } /** * Focuses a window if it exists, or creates a new one. */ const showOrCreateWindow = (port) => { const wins = electron_1.BrowserWindow.getAllWindows(); if (wins.length > 0) { wins[0].show(); wins[0].focus(); } else { createWindow(`${constants_1.WINDOW_ORIGIN}:${port}/`); } }; exports.showOrCreateWindow = showOrCreateWindow; /** * Manages the power save blocker to keep the computer awake. */ class SleepBlocker { constructor() { this.currentBlockerId = null; } static getInstance() { if (!SleepBlocker.instance) { SleepBlocker.instance = new SleepBlocker(); } return SleepBlocker.instance; } shouldKeepComputerAwake(keep) { if (keep) { if (this.currentBlockerId === null) { this.currentBlockerId = electron_1.powerSaveBlocker.start('prevent-display-sleep'); console.log('Power save blocker started:', this.currentBlockerId); } } else { if (this.currentBlockerId !== null) { electron_1.powerSaveBlocker.stop(this.currentBlockerId); console.log('Power save blocker stopped:', this.currentBlockerId); this.currentBlockerId = null; } } } } exports.SleepBlocker = SleepBlocker; function getNodeWrapperPaths(envPath, os, isPackaged, userDataPath, baseDir) { const delimiter = os === 'win32' ? ';' : ':'; if (!isPackaged) { const devBinPath = path_1.default.join(baseDir, '..', 'node_modules', '.bin'); return { newEnvPath: `${devBinPath}${delimiter}${envPath || ''}`, nodeWrapperPath: undefined, binPath: undefined, }; } const binPath = path_1.default.join(userDataPath, 'bin'); const nodeWrapperPath = path_1.default.join(binPath, os === 'win32' ? 'agy-node.cmd' : 'agy-node'); return { newEnvPath: `${binPath}${delimiter}${envPath || ''}`, nodeWrapperPath, binPath, }; } /** * Sets up a wrapper script for Node.js that runs Electron as Node. * This allows running standard Node scripts using the Electron binary. */ function setupNodeWrapper(env) { const userDataPath = electron_1.app.isPackaged ? electron_1.app.getPath('userData') : ''; // Windows environment variables are case-insensitive, but when copying process.env // into a plain object, we might get 'Path' instead of 'PATH'. We need to find // the actual key used to avoid creating case-duplicate keys (e.g. 'Path' and 'PATH') // which can confuse child_process.spawn on Windows. const isWindows = process.platform === 'win32'; const pathKey = isWindows ? Object.keys(env).find((k) => k.toUpperCase() === 'PATH') || 'PATH' : 'PATH'; const { newEnvPath, nodeWrapperPath, binPath } = getNodeWrapperPaths(env[pathKey], process.platform, electron_1.app.isPackaged, userDataPath, __dirname); env[pathKey] = newEnvPath; // In non-packaged dev mode, we don't create a wrapper and it'll just use machine node if (!nodeWrapperPath || !binPath) { return; } if (!fs.existsSync(binPath)) { fs.mkdirSync(binPath, { recursive: true }); } let nodeWrapperContent = ''; switch (process.platform) { case 'win32': nodeWrapperContent = `@echo off\nset ELECTRON_RUN_AS_NODE=1\n"${process.execPath}" %*\n`; break; case 'darwin': { // Use the Helper app instead of the main executable to prevent macOS // from bouncing a new Dock icon when this script is executed. The Helper // has LSUIElement=true in its Info.plist, running it invisibly. const appName = path_1.default.basename(process.execPath); let electronBinary = process.execPath; const helperPath = path_1.default.join(path_1.default.dirname(process.execPath), '..', 'Frameworks', `${appName} Helper.app`, 'Contents', 'MacOS', `${appName} Helper`); if (fs.existsSync(helperPath)) { electronBinary = helperPath; } nodeWrapperContent = `#!/bin/sh\nELECTRON_RUN_AS_NODE=1 exec "${electronBinary}" "$@"\n`; break; } default: // linux, etc. nodeWrapperContent = `#!/bin/sh\nELECTRON_RUN_AS_NODE=1 exec "${process.execPath}" "$@"\n`; break; } try { const existingContent = fs.existsSync(nodeWrapperPath) ? fs.readFileSync(nodeWrapperPath, 'utf-8') : ''; if (existingContent !== nodeWrapperContent) { fs.writeFileSync(nodeWrapperPath, nodeWrapperContent); if (process.platform !== 'win32') { fs.chmodSync(nodeWrapperPath, 0o755); } } } catch (err) { console.error(`Failed to create node wrapper: ${err}`); } }