Files
ProdDash/band-lineup-display/propresenter-app/main.js
T

144 lines
4.0 KiB
JavaScript
Raw Normal View History

'use strict';
/**
* Stage Now / Next — Electron kiosk wrapper.
*
* Optional. The display itself is just a web page served by server.js, so a studio
* can simply open the URL in a browser. This wrapper exists for the machine that
* should *host* the display: it starts the same server in-process and puts the page
* in a borderless window, while still serving every other screen on the network.
*
* npm run kiosk → window on the host machine
* npm run kiosk -- --kiosk → true kiosk (no window chrome, always fullscreen)
*/
const { app, BrowserWindow, Menu, shell, dialog } = require('electron');
const { createDisplayServer, DEFAULT_PORT } = require('./server');
/** How many ports to try past the preferred one before giving up. */
const PORT_ATTEMPTS = 5;
let mainWindow = null;
let display = null;
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
app.whenReady().then(async () => {
try {
display = await startServer();
} catch (error) {
dialog.showErrorBox(
'Stage Now / Next',
`Could not start the display server.\n\n${error instanceof Error ? error.message : String(error)}`,
);
app.quit();
return;
}
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('before-quit', async () => {
if (display) await display.close().catch(() => {});
});
}
async function startServer() {
const preferred = Number.parseInt(process.env.PORT || '', 10) || DEFAULT_PORT;
let lastError = null;
for (let attempt = 0; attempt < PORT_ATTEMPTS; attempt += 1) {
const candidate = createDisplayServer({
port: preferred + attempt,
dataDir: app.getPath('userData'),
});
try {
await candidate.listen();
return candidate;
} catch (error) {
await candidate.close().catch(() => {});
lastError = error;
// Another display (or anything else) already holds that port — step to the next one.
if (error?.code !== 'EADDRINUSE') throw error;
}
}
throw lastError || new Error('No free port found.');
}
function createWindow() {
const kiosk = process.argv.includes('--kiosk');
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
backgroundColor: '#0f0f12',
autoHideMenuBar: true,
kiosk,
fullscreen: kiosk,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
},
});
buildMenu();
mainWindow.loadURL(`http://127.0.0.1:${display.port}/`);
// Anything that tries to open a new window (there is nothing today) goes to the real browser.
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
function buildMenu() {
const urls = display.status().viewerUrls;
const template = [
...(process.platform === 'darwin' ? [{ role: 'appMenu' }] : []),
{
label: 'Display',
submenu: [
{
label: 'Toggle Fullscreen',
accelerator: process.platform === 'darwin' ? 'Ctrl+Cmd+F' : 'F11',
click: () => mainWindow?.setFullScreen(!mainWindow.isFullScreen()),
},
{ role: 'reload' },
{ type: 'separator' },
{
label: 'Copy Address For Other Screens',
enabled: urls.length > 0,
click: () => {
const { clipboard } = require('electron');
clipboard.writeText(urls[0]);
},
},
{
label: urls.length ? `Serving ${urls.join(' · ')}` : 'No network address detected',
enabled: false,
},
{ type: 'separator' },
{ role: 'quit' },
],
},
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}