diff --git a/.agents/MEMORY.md b/.agents/MEMORY.md index 3001683..9eb7ac9 100644 --- a/.agents/MEMORY.md +++ b/.agents/MEMORY.md @@ -34,9 +34,18 @@ loopback; current phase is Phase 7. - **systemd autostart**: `systemd/screencast-receiver.service` is a template (`__SC_RECEIVER_BIN__`/`__SC_RECEIVER_USER__`); the install script substitutes and enables it (opt out: `SC_RECEIVER_SERVICE=0`), - adding the run user to video/render/input for headless KMSDRM. The unit - gets a controlling tty (StandardInput=tty, tty1) because SDL's KMSDRM - backend needs one for VT handling. + adding the run user to video/render/input for headless KMSDRM. +- **systemd tty trap (hit on the real Pi, reproduced locally)**: with + StandardInput=tty + TTYPath=tty1, the service NEVER started — systemd + blocks PRE-EXEC in acquire_terminal() waiting for a tty that the console + session/getty already owns. Symptoms: status shows the main PID as + "(screencast)" with Tasks:1 and ~40ms CPU, silent journal, no mDNS. + Diagnosed by gdb-attaching the stuck process (acquire_terminal backtrace) + after reproducing with a local transient unit. Fix: the service owns a + dedicated free VT (tty7) + tolerant `ExecStartPre=-/usr/bin/chvt 7`; + tty1 keeps the console. Also: `systemctl status` Tasks counts THREADS + (a healthy receiver shows ~15), and "Console Autologin" advice was + WRONG (it made the hang deterministic) — removed from all docs. - **Fullscreen headless rendering**: under the KMSDRM video driver (no window manager) the renderer goes fullscreen automatically; --fullscreen forces it on desktops. Aspect is preserved via diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md index adfde88..8f22923 100644 --- a/docs/RUNBOOK.md +++ b/docs/RUNBOOK.md @@ -96,9 +96,13 @@ The receiver does not need X11, Wayland, or any window manager: SDL3's KMSDRM backend renders straight to the kernel display pipeline. This is the recommended setup for small boards (Raspberry Pi OS **Lite**): -1. `sudo raspi-config` → System Options → Boot → **Console Autologin**. -2. No desktop may be enabled — a running compositor holds the DRM master +1. No desktop may be enabled — a running compositor holds the DRM master and the receiver cannot take it (the two modes are mutually exclusive). + No console autologin is needed either. +2. The service renders on its **own VT (tty7)** and switches to it at + boot: `Ctrl+Alt+F1` returns to the console login, `Ctrl+Alt+F7` back + to the screencast. (A service must not target tty1: acquiring a tty + that the console session or getty owns blocks the start forever.) 3. Disable console blanking: append `consoleblank=0` to `/boot/firmware/cmdline.txt` and reboot. diff --git a/scripts/install-receiver.sh b/scripts/install-receiver.sh index 04e2927..0dd9b63 100755 --- a/scripts/install-receiver.sh +++ b/scripts/install-receiver.sh @@ -184,8 +184,10 @@ The receiver is installed and starts automatically at boot. Notes for small boards (e.g. Pi Zero 2 W): - No window manager is required: the receiver renders directly to the kernel display pipeline (SDL KMSDRM). Use Raspberry Pi OS Lite with - Console Autologin (raspi-config) and no desktop enabled — a running - compositor would own the display and the receiver could not start. + NO desktop enabled — a running compositor would own the display and + the receiver could not start. No console autologin is needed either. + - The service owns tty7 and switches to it at boot (Ctrl+Alt+F1 returns + to the console login, Ctrl+Alt+F7 to the screencast). - Disable console blanking so the picture never goes dark: add consoleblank=0 to /boot/firmware/cmdline.txt and reboot. - Decoding is software H.264; expect smooth playback for small streams diff --git a/systemd/screencast-receiver.service b/systemd/screencast-receiver.service index 2e9675f..c055912 100644 --- a/systemd/screencast-receiver.service +++ b/systemd/screencast-receiver.service @@ -26,12 +26,16 @@ StartLimitBurst=10 [Service] Type=simple User=__SC_RECEIVER_USER__ +# SDL's KMSDRM backend wants a tty. The service gets its OWN virtual +# terminal: systemd must acquire the controlling terminal before exec, and +# acquiring one that another session owns (tty1: getty, console autologin, +# or a desktop) blocks the start FOREVER in a pre-exec placeholder process +# (systemctl status shows the main PID as "(screencast)"). tty7 is free on +# stock systems; tty1 keeps the console login. +ExecStartPre=-/usr/bin/chvt 7 ExecStart=__SC_RECEIVER_BIN__ --receive -# SDL's KMSDRM backend (headless console) expects a controlling terminal -# for its VT handling; system services have none by default. Harmless in -# desktop mode, where the process never touches the tty. StandardInput=tty -TTYPath=/dev/tty1 +TTYPath=/dev/tty7 # SIGTERM (systemctl stop) shuts the receiver down gracefully, which # withdraws its mDNS announcement. Never SIGKILL it; the systemd default # KillMode escalates only after the graceful timeout. @@ -54,6 +58,8 @@ WantedBy=multi-user.target # to the kernel via KMSDRM): # * Raspberry Pi OS Lite, no desktop enabled (a running compositor would # hold the DRM master and the receiver could not take it) -# * raspi-config → System Options → Boot → Console Autologin +# * no console autologin needed: the service owns tty7 and switches to it +# at boot; Ctrl+Alt+F1 returns to the console login, Ctrl+Alt+F7 to the +# screencast # * disable console blanking: add consoleblank=0 to # /boot/firmware/cmdline.txt \ No newline at end of file