refactor(*): move to x11

This commit is contained in:
2026-07-05 23:54:18 +02:00
parent 38aba59345
commit 3d5aa94aa0
7 changed files with 95 additions and 112 deletions
+7 -11
View File
@@ -1,23 +1,19 @@
FROM debian:bullseye-slim FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
sway \ xserver-xorg-core \
xwayland \ xserver-xorg-video-fbdev \
dbus-x11 \ xserver-xorg-video-fbturbo \
libwayland-client0 \ xserver-xorg-video-all \
libwayland-server0 \
libwayland-egl1 \
mesa-vulkan-drivers \
libvulkan1 \
fonts-liberation \
x11-xserver-utils \ x11-xserver-utils \
xinit \
dbus-x11 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY sway.conf /etc/sway/config
COPY start.sh /start.sh COPY start.sh /start.sh
RUN chmod +x /start.sh RUN chmod +x /start.sh
# Need to run as root to access DRM devices on many kiosk setups # Xorg needs root to access DRM and VT
USER root USER root
CMD ["/start.sh"] CMD ["/start.sh"]
+9 -13
View File
@@ -1,16 +1,12 @@
#!/bin/bash #!/bin/bash
# Ensure runtime directory exists # Ensure X11 socket directory exists
mkdir -p /run/wayland mkdir -p /tmp/.X11-unix
chmod 0700 /run/wayland chmod 1777 /tmp/.X11-unix
# Set env vars for Wayland/Sway echo "Starting Xorg server..."
export XDG_RUNTIME_DIR=/run/wayland # Run Xorg on DISPLAY :0.
export WAYLAND_DISPLAY=wayland-1 # We disable DPMS (screen blanking) and screen saver (-s 0).
export WLR_BACKENDS=drm,libinput # We also avoid requiring a virtual terminal using vt7 and -sharevts if needed,
# Fallback to headless if DRM is not available (useful for testing on mac/desktop without actual DRM) # though running as root with devices mapped usually succeeds.
# export WLR_BACKENDS=headless exec Xorg :0 -nocursor -s 0 -dpms vt7
# Run sway
echo "Starting Sway..."
exec sway -d -c /etc/sway/config
-29
View File
@@ -1,29 +0,0 @@
# Default sway config for Kiosk
# Disable default bar
bar {
mode invisible
}
# Remove window borders and title bars
default_border none
default_floating_border none
hide_edge_borders both
# Map workspaces to specific outputs
# Note: In a real Pi 4 environment, the outputs might be named HDMI-A-1 and HDMI-A-2.
# You can customize these output names based on `swaymsg -t get_outputs`.
workspace 1 output HDMI-A-1
workspace 2 output HDMI-A-2
# Set a black background
output * bg #000000 solid_color
# Idle configuration
# We don't want the screen to turn off in a kiosk.
exec swayidle -w \
timeout 3600 'swaymsg "output * dpms on"' \
resume 'swaymsg "output * dpms on"'
# Include any other specific configurations
include /etc/sway/config.d/*
+14 -14
View File
@@ -5,12 +5,16 @@ services:
build: ./display-manager build: ./display-manager
privileged: true privileged: true
restart: always restart: always
tty: true
stdin_open: true
volumes: volumes:
- wayland-socket:/run/wayland - x11-socket:/tmp/.X11-unix
- /dev:/dev - /dev:/dev
devices:
- "/dev/tty7:/dev/tty7"
- "/dev/dri:/dev/dri"
environment: environment:
- XDG_RUNTIME_DIR=/run/wayland - DISPLAY=:0
- WAYLAND_DISPLAY=wayland-1
kiosk-1: kiosk-1:
build: ./kiosk build: ./kiosk
@@ -20,12 +24,10 @@ services:
ports: ports:
- "5011:5011" - "5011:5011"
volumes: volumes:
- wayland-socket:/run/wayland - x11-socket:/tmp/.X11-unix
environment: environment:
- XDG_RUNTIME_DIR=/run/wayland - DISPLAY=:0
- WAYLAND_DISPLAY=wayland-1 - MONITOR=HDMI-1
- SWAYSOCK=/run/wayland/sway-ipc.sock
- WORKSPACE=1
- PORT=5011 - PORT=5011
- LAUNCH_URL=https://github.com - LAUNCH_URL=https://github.com
- KIOSK=1 - KIOSK=1
@@ -39,16 +41,14 @@ services:
ports: ports:
- "5012:5012" - "5012:5012"
volumes: volumes:
- wayland-socket:/run/wayland - x11-socket:/tmp/.X11-unix
environment: environment:
- XDG_RUNTIME_DIR=/run/wayland - DISPLAY=:0
- WAYLAND_DISPLAY=wayland-1 - MONITOR=HDMI-2
- SWAYSOCK=/run/wayland/sway-ipc.sock
- WORKSPACE=2
- PORT=5012 - PORT=5012
- LAUNCH_URL=https://google.com - LAUNCH_URL=https://google.com
- KIOSK=1 - KIOSK=1
- GPU=1 - GPU=1
volumes: volumes:
wayland-socket: x11-socket:
+3 -3
View File
@@ -1,10 +1,10 @@
FROM node:18-bullseye-slim FROM node:18-bullseye-slim
# Install dependencies for Chromium, Wayland, and screenshot tool # Install dependencies for Chromium and X11
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
chromium \ chromium \
sway \ x11-xserver-utils \
grim \ scrot \
jq \ jq \
curl \ curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
+55 -38
View File
@@ -1,7 +1,7 @@
const express = require('express'); const express = require('express');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const puppeteer = require('puppeteer-core'); const puppeteer = require('puppeteer-core');
const { spawn, exec } = require('child_process'); const { spawn, exec, execSync } = require('child_process');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -10,7 +10,7 @@ app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); app.use(bodyParser.json());
const PORT = process.env.PORT || 5011; const PORT = process.env.PORT || 5011;
const WORKSPACE = process.env.WORKSPACE || '1'; const MONITOR = process.env.MONITOR || 'HDMI-1';
// State variables // State variables
let browserProcess = null; let browserProcess = null;
@@ -23,19 +23,48 @@ let isKiosk = process.env.KIOSK !== '0'; // default 1
let isGpu = process.env.GPU !== '0'; // default 1 let isGpu = process.env.GPU !== '0'; // default 1
let chromiumFlags = []; 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() { 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 = [ const flags = [
'--no-sandbox', '--no-sandbox',
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--remote-debugging-port=9222', '--remote-debugging-port=9222',
'--enable-features=UseOzonePlatform', `--user-data-dir=/data/chromium-${MONITOR}`,
'--ozone-platform=wayland', `--window-position=${geometry.x},${geometry.y}`,
`--user-data-dir=/data/chromium-${WORKSPACE}`, `--window-size=${geometry.width},${geometry.height}`
'--window-position=0,0'
]; ];
if (isKiosk) { if (isKiosk) {
flags.push('--kiosk'); flags.push('--kiosk');
// To ensure full screen stays on the target monitor
flags.push(`--app=${currentUrl}`);
} }
if (!isGpu) { if (!isGpu) {
@@ -57,24 +86,20 @@ async function startChromium() {
} }
const flags = buildFlags(); const flags = buildFlags();
// Start with a specific title so we can target it with swaymsg
const initialUrl = `data:text/html,<html><head><title>KioskInit${WORKSPACE}</title></head><body>Loading...</body></html>`;
console.log('Starting Chromium with flags:', flags.join(' ')); console.log('Starting Chromium with flags:', flags.join(' '));
browserProcess = spawn('/usr/bin/chromium', [...flags, initialUrl], { // 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' stdio: 'inherit'
}); });
// Wait a bit for the window to appear in Sway
setTimeout(() => {
console.log(`Assigning window to workspace ${WORKSPACE}...`);
exec(`swaymsg "[title=KioskInit${WORKSPACE}] move to workspace ${WORKSPACE}"`, (err, stdout, stderr) => {
if (err) console.error('Swaymsg error:', stderr);
else console.log('Swaymsg success:', stdout);
});
}, 2000);
// Connect Puppeteer // Connect Puppeteer
setTimeout(async () => { setTimeout(async () => {
try { try {
@@ -87,8 +112,10 @@ async function startChromium() {
const pages = await browser.pages(); const pages = await browser.pages();
page = pages.length > 0 ? pages[0] : await browser.newPage(); page = pages.length > 0 ? pages[0] : await browser.newPage();
console.log(`Navigating to ${currentUrl}`); if (!isKiosk) {
await page.goto(currentUrl); console.log(`Navigating to ${currentUrl}`);
await page.goto(currentUrl);
}
} catch (e) { } catch (e) {
console.error('Puppeteer connect error:', e); console.error('Puppeteer connect error:', e);
} }
@@ -128,7 +155,6 @@ app.post('/autorefresh/:interval', (req, res) => {
}); });
app.post('/scan', (req, res) => { app.post('/scan', (req, res) => {
// Stub for local service discovery
res.status(200).send('Scanned'); res.status(200).send('Scanned');
}); });
@@ -157,7 +183,9 @@ app.post('/url', async (req, res) => {
} }
} }
if (needRestart) { // 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(); await startChromium();
} else if (page) { } else if (page) {
await page.goto(currentUrl); await page.goto(currentUrl);
@@ -205,21 +233,10 @@ app.get('/version', (req, res) => {
app.get('/screenshot', (req, res) => { app.get('/screenshot', (req, res) => {
const file = `/tmp/screenshot-${Date.now()}.png`; const file = `/tmp/screenshot-${Date.now()}.png`;
// Using grim instead of scrot for Wayland // We use scrot for X11 screenshots.
// We specify the output by workspace name if needed, but grim captures the whole screen if no output is specified. exec(`scrot ${file}`, (err) => {
// To capture just the workspace, we can use grim with swaymsg to find the coordinates, if (err) return res.status(500).send('Screenshot failed');
// or simply rely on grim which by default captures everything. res.sendFile(file);
// To be safe and capture just the output for this workspace:
exec(`grim -o HDMI-A-${WORKSPACE} ${file}`, (err) => {
if (err) {
// Fallback if HDMI-A-X is not found (e.g. testing locally)
exec(`grim ${file}`, (err2) => {
if (err2) return res.status(500).send('Screenshot failed');
res.sendFile(file);
});
} else {
res.sendFile(file);
}
}); });
}); });
@@ -227,5 +244,5 @@ app.get('/screenshot', (req, res) => {
startChromium(); startChromium();
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Kiosk API running on port ${PORT}`); console.log(`Kiosk API running on port ${PORT} for monitor ${MONITOR}`);
}); });
+7 -4
View File
@@ -1,12 +1,15 @@
#!/bin/bash #!/bin/bash
# Wait for Wayland socket to be available # Wait for X11 socket to be available
echo "Waiting for Wayland display $WAYLAND_DISPLAY..." echo "Waiting for X11 display $DISPLAY..."
while [ ! -e "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; do while [ ! -S /tmp/.X11-unix/X0 ]; do
sleep 1 sleep 1
done done
echo "Wayland display found. Starting API server..." echo "X11 display found. Starting API server..."
# Disable screen blanking for this display as well just in case
xset s off -dpms || true
# Start the Node.js API server # Start the Node.js API server
exec node server.js exec node server.js