249 lines
6.3 KiB
JavaScript
249 lines
6.3 KiB
JavaScript
const express = require('express');
|
|
const bodyParser = require('body-parser');
|
|
const puppeteer = require('puppeteer-core');
|
|
const { spawn, exec, execSync } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.use(bodyParser.json());
|
|
|
|
const PORT = process.env.PORT || 5011;
|
|
const MONITOR = process.env.MONITOR || 'HDMI-1';
|
|
|
|
// State variables
|
|
let browserProcess = null;
|
|
let browser = null;
|
|
let page = null;
|
|
let autorefreshInterval = null;
|
|
|
|
let currentUrl = process.env.LAUNCH_URL || 'about:blank';
|
|
let isKiosk = process.env.KIOSK !== '0'; // default 1
|
|
let isGpu = process.env.GPU !== '0'; // default 1
|
|
let chromiumFlags = [];
|
|
|
|
function getMonitorGeometry(monitorName) {
|
|
try {
|
|
const stdout = execSync('xrandr').toString();
|
|
const lines = stdout.split('\n');
|
|
for (const line of lines) {
|
|
if (line.includes(monitorName) && line.includes('connected')) {
|
|
// Match geometry string like 1920x1080+1920+0
|
|
const match = line.match(/(\d+)x(\d+)\+(\d+)\+(\d+)/);
|
|
if (match) {
|
|
return {
|
|
width: match[1],
|
|
height: match[2],
|
|
x: match[3],
|
|
y: match[4]
|
|
};
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to parse xrandr:', e);
|
|
}
|
|
// Fallback geometry if xrandr fails
|
|
return { width: 1920, height: 1080, x: 0, y: 0 };
|
|
}
|
|
|
|
function buildFlags() {
|
|
const geometry = getMonitorGeometry(MONITOR);
|
|
console.log(`Monitor ${MONITOR} resolved to position ${geometry.x},${geometry.y} with size ${geometry.width}x${geometry.height}`);
|
|
|
|
const flags = [
|
|
'--no-sandbox',
|
|
'--disable-dev-shm-usage',
|
|
'--remote-debugging-port=9222',
|
|
`--user-data-dir=/data/chromium-${MONITOR}`,
|
|
`--window-position=${geometry.x},${geometry.y}`,
|
|
`--window-size=${geometry.width},${geometry.height}`
|
|
];
|
|
|
|
if (isKiosk) {
|
|
flags.push('--kiosk');
|
|
// To ensure full screen stays on the target monitor
|
|
flags.push(`--app=${currentUrl}`);
|
|
}
|
|
|
|
if (!isGpu) {
|
|
flags.push('--disable-gpu');
|
|
flags.push('--disable-software-rasterizer');
|
|
} else {
|
|
flags.push('--enable-gpu');
|
|
}
|
|
|
|
chromiumFlags = flags;
|
|
return flags;
|
|
}
|
|
|
|
async function startChromium() {
|
|
if (browserProcess) {
|
|
console.log('Stopping existing Chromium...');
|
|
browserProcess.kill('SIGTERM');
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
}
|
|
|
|
const flags = buildFlags();
|
|
|
|
console.log('Starting Chromium with flags:', flags.join(' '));
|
|
|
|
// If kiosk is enabled, we pass the URL via --app to enforce window boundaries
|
|
// Otherwise we just pass the URL as a positional argument.
|
|
const args = [...flags];
|
|
if (!isKiosk) {
|
|
args.push(currentUrl);
|
|
}
|
|
|
|
browserProcess = spawn('/usr/bin/chromium', args, {
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Connect Puppeteer
|
|
setTimeout(async () => {
|
|
try {
|
|
console.log('Connecting puppeteer...');
|
|
browser = await puppeteer.connect({
|
|
browserURL: 'http://localhost:9222',
|
|
defaultViewport: null
|
|
});
|
|
|
|
const pages = await browser.pages();
|
|
page = pages.length > 0 ? pages[0] : await browser.newPage();
|
|
|
|
if (!isKiosk) {
|
|
console.log(`Navigating to ${currentUrl}`);
|
|
await page.goto(currentUrl);
|
|
}
|
|
} catch (e) {
|
|
console.error('Puppeteer connect error:', e);
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
// API Endpoints
|
|
|
|
app.get('/ping', (req, res) => {
|
|
res.status(200).send('OK');
|
|
});
|
|
|
|
app.post('/refresh', async (req, res) => {
|
|
if (page) {
|
|
await page.reload();
|
|
res.status(200).send('Refreshed');
|
|
} else {
|
|
res.status(503).send('Browser not ready');
|
|
}
|
|
});
|
|
|
|
app.post('/autorefresh/:interval', (req, res) => {
|
|
const interval = parseInt(req.params.interval, 10);
|
|
if (autorefreshInterval) {
|
|
clearInterval(autorefreshInterval);
|
|
autorefreshInterval = null;
|
|
}
|
|
|
|
if (interval > 0 && interval <= 60) {
|
|
autorefreshInterval = setInterval(() => {
|
|
if (page) page.reload().catch(console.error);
|
|
}, interval * 1000);
|
|
res.status(200).send(`Autorefresh enabled: ${interval}s`);
|
|
} else {
|
|
res.status(200).send('Autorefresh disabled');
|
|
}
|
|
});
|
|
|
|
app.post('/scan', (req, res) => {
|
|
res.status(200).send('Scanned');
|
|
});
|
|
|
|
app.get('/url', (req, res) => {
|
|
res.status(200).send(currentUrl);
|
|
});
|
|
|
|
app.post('/url', async (req, res) => {
|
|
let needRestart = false;
|
|
|
|
if (req.body.url) currentUrl = req.body.url;
|
|
|
|
if (req.body.gpu !== undefined) {
|
|
const newGpu = req.body.gpu === '1';
|
|
if (newGpu !== isGpu) {
|
|
isGpu = newGpu;
|
|
needRestart = true;
|
|
}
|
|
}
|
|
|
|
if (req.body.kiosk !== undefined) {
|
|
const newKiosk = req.body.kiosk === '1';
|
|
if (newKiosk !== isKiosk) {
|
|
isKiosk = newKiosk;
|
|
needRestart = true;
|
|
}
|
|
}
|
|
|
|
// If we're in kiosk mode, --app flag is used so changing URL requires restart to stay full screen
|
|
// If we're not in kiosk mode, we can just navigate the page
|
|
if (needRestart || isKiosk) {
|
|
await startChromium();
|
|
} else if (page) {
|
|
await page.goto(currentUrl);
|
|
}
|
|
|
|
res.status(200).send(`URL set to ${currentUrl}`);
|
|
});
|
|
|
|
app.get('/gpu', (req, res) => {
|
|
res.status(200).send(isGpu ? '1' : '0');
|
|
});
|
|
|
|
app.put('/gpu/:value', async (req, res) => {
|
|
const val = req.params.value === '1';
|
|
if (val !== isGpu) {
|
|
isGpu = val;
|
|
await startChromium();
|
|
}
|
|
res.status(200).send(isGpu ? '1' : '0');
|
|
});
|
|
|
|
app.get('/kiosk', (req, res) => {
|
|
res.status(200).send(isKiosk ? '1' : '0');
|
|
});
|
|
|
|
app.put('/kiosk/:value', async (req, res) => {
|
|
const val = req.params.value === '1';
|
|
if (val !== isKiosk) {
|
|
isKiosk = val;
|
|
await startChromium();
|
|
}
|
|
res.status(200).send(isKiosk ? '1' : '0');
|
|
});
|
|
|
|
app.get('/flags', (req, res) => {
|
|
res.status(200).json(chromiumFlags);
|
|
});
|
|
|
|
app.get('/version', (req, res) => {
|
|
exec('/usr/bin/chromium --version', (err, stdout) => {
|
|
if (err) res.status(500).send('Unknown');
|
|
else res.status(200).send(stdout.trim());
|
|
});
|
|
});
|
|
|
|
app.get('/screenshot', (req, res) => {
|
|
const file = `/tmp/screenshot-${Date.now()}.png`;
|
|
// We use scrot for X11 screenshots.
|
|
exec(`scrot ${file}`, (err) => {
|
|
if (err) return res.status(500).send('Screenshot failed');
|
|
res.sendFile(file);
|
|
});
|
|
});
|
|
|
|
// Start initially
|
|
startChromium();
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Kiosk API running on port ${PORT} for monitor ${MONITOR}`);
|
|
});
|