Tell me briefly what it's about. I'll reply by e-mail.
A projector is easy to start. Making a small installation pleasant to operate takes a little more thought. I wanted the routine to be simple: switch on, let the picture and soundtrack loop, then press one obvious button when the show is over.
The result combines a Raspberry Pi, an Acer projector, and a souvenir with a useful second life: my DEF CON 29 badge. One of its illuminated keys becomes the shutdown button. Red means “stop the Pi.”

The Pi runs 64-bit Debian (aarch64) without a desktop environment. It still has a display attached: HDMI carries the video to the projector. The badge connects over USB and supplies the human-machine interface, or HMI.
There are three jobs:
The badge controls the Pi, not the projector’s power. The projector still uses its own controls.
The examples use a playback account named video, with movie.mov and audio.wav in /home/video/video/. Substitute your own paths. Install the tools on the Pi:
sudo apt install mpv evtest cron
Start by getting one video to play from the Pi’s local console. The script below selects mpv’s DRM/EGL backend with --vo=gpu --gpu-context=drm; the installed build, graphics driver, and playback user’s device permissions must support it. mpv --gpu-context=help and mpv --audio-device=help list the available choices.
Save this as /home/video/video.sh:
#!/usr/bin/env bash
set -euo pipefail
media_dir=/home/video/video
movie="$media_dir/movie.mov"
mpv --no-video --input-terminal=no --loop-file=inf \
"$media_dir/audio.wav" &
audio_pid=$!
trap 'kill "$audio_pid" 2>/dev/null || true' EXIT
mpv --vo=gpu --gpu-context=drm \
--no-audio --input-terminal=no --loop-playlist=inf \
'--{' --video-pan-x=-0.7 "$movie" '--}' \
'--{' --video-pan-x=0.7 "$movie" '--}'
The audio runs in the background; the exit trap stops it when the video player finishes. The two processes loop independently, so this suits a separate ambient soundtrack. For sound that must stay synchronized with the picture, play the video’s audio in the same mpv process instead.
The quoted --{ and --} delimit mpv’s per-file options. The same movie plays at one position, then the other, before the playlist repeats. This is a change between playthroughs, not a smooth animated pan. Offsets are fractions of the scaled video size; 0.7 is substantial and can move much of the picture off screen. Adjust it to the actual projection. The mpv manual explains the playback options and output backends.
For this setup, the badge needs to send an S key event as a USB keyboard, with the chosen button lit red. The LED makes the control easy to find; the keyboard event is what Linux acts on.
I configured the key through the badge’s built-in serial menu. This setup did not need custom firmware. Connect the badge with a USB-C data cable, open its serial interface in a terminal, and use the menu to configure the red key to send S. The serial menu is for configuration; the resulting key press reaches the Pi through the badge’s USB keyboard interface. MK Factor’s badge repository provides the firmware and hardware sources for anyone who wants to look further.
Before connecting the key to shutdown, inspect what it sends:
ls -l /dev/input/by-id/
sudo evtest
Select the badge’s keyboard device from the list and press the intended button. Look for an event containing:
(EV_KEY), code 31 (KEY_S), value 1
1 means a key press; 0 is release and 2 is autorepeat. KEY_S identifies a key, not an uppercase text character, so Shift is not required. These values are defined by the Linux input protocol.
Use the badge’s matching /dev/input/by-id/...-event-kbd symlink for the listener. A name such as /dev/input/event0 can refer to another device after a reboot. If the badge has several input interfaces, choose the one where the test actually reports KEY_S.
Save the following as shutdown-on-s.sh. It takes the device path explicitly, waits for that path to appear, and keeps the keyboard grabbed while it runs.
#!/usr/bin/env bash
set -euo pipefail
badge_device=${1:?Pass the badge keyboard path from /dev/input/by-id/}
while [[ ! -e "$badge_device" ]]; do
sleep 1
done
LC_ALL=C stdbuf -oL evtest --grab "$badge_device" |
awk '
!requested && /\(EV_KEY\)/ && /\(KEY_S\)/ && /value 1$/ {
requested = (system("systemctl poweroff") == 0)
if (!requested)
print "Power-off request failed" > "/dev/stderr"
}
'
evtest --grab keeps the badge’s input from also reaching a console or another application. stdbuf makes the event stream line-buffered. After a successful shutdown request, the requested flag prevents another request; a failure is logged and a later press can retry.
Run this listener as root so it can read the input device and request shutdown without an interactive password prompt. Install a root-owned copy:
sudo install -o root -g root -m 0755 shutdown-on-s.sh /usr/local/sbin/shutdown-on-s
For a first end-to-end input test, replace system("systemctl poweroff") in a temporary copy with system("printf 'S pressed\\n'"). That lets you check the button without shutting down the Pi. The real systemctl poweroff command asks systemd to shut down the operating system; it does not switch off the projector.
The original setup used cron’s @reboot entries. Keep playback under the video account and the input listener under root.
In the video user’s crontab (crontab -e while logged in as that user):
@reboot /bin/bash /home/video/video.sh >>/home/video/video.log 2>&1
In root’s crontab (sudo crontab -e), replace usb-YOUR_BADGE-event-kbd with the actual symlink discovered above:
@reboot /usr/local/sbin/shutdown-on-s /dev/input/by-id/usb-YOUR_BADGE-event-kbd >>/var/log/projector-shutdown.log 2>&1
@reboot runs when cron starts. It does not wait for graphics or audio to be ready, create a desktop session, or grant device access. Verify playback under the intended account first, then check both logs after a cold boot. If the display backend needs an active console session, a cron entry alone will not supply it.
For a longer-running installation, I would give playback and the listener separate systemd services, with restart behavior and explicit device setup. Cron starts a process once; it will not recover a failed player or reconnect a listener after the badge is unplugged.
Other useful additions would be a short audio fade before shutdown and a badge LED that confirms the Pi has received the request. Those are separate features: the red key currently marks the control, rather than reporting the operating system’s state.
The part I like most is the interaction. A conference badge gets a new purpose, and ending the show becomes a physical action: press the red key and let the Pi shut down cleanly.