feat: give the classroom a desktop instead of a shell

The whole appliance UI is now configuration for a desktop Debian already
ships: a Plasma 6.3 session with a colour scheme, a look and feel package
that builds the bar, window rules, and KDE's own Kiosk restrictions.

Two things were measured rather than assumed, and both changed a decision.

Plasma restarts a crashed panel three times a minute and then gives up,
which is the failure a classroom has already had. Four lines of systemd
drop-in make it unconditional; the panel then survived eight SIGKILLs a
second apart, back each time in 2.4s.

Plasma draws a minimised task and a background task identically, so the
five states the room needs cannot all be shown. Rather than write a task
manager, the classroom removes minimise: clicking a launcher always means
show me this.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-10 08:35:12 -05:00
co-authored by Claude Opus 5
parent bfc7caf28f
commit 87b55761c6
51 changed files with 1352 additions and 36 deletions
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# Run a KWin script in the session under test and print what it logged.
#
# KWin's scripting interface is the only way to ask the compositor what it
# actually believes about a window — class, caption, desktop file, geometry,
# minimised, keep-above. Everything this experiment claims about window
# behaviour is measured through here rather than inferred from a screenshot.
set -euo pipefail
NAME="${LAB_NAME:-classroom-v2-lab}"
SCRIPT="${1:?usage: kwin-script FILE}"
ID="labscript$RANDOM"
# /tmp inside the lab is a tmpfs created after the container started, which
# `docker cp` cannot see, so the script is piped in instead.
docker exec -i "$NAME" sh -c "cat > /tmp/$ID.js && chown classroom /tmp/$ID.js" \
< "$SCRIPT"
run() {
docker exec -u classroom \
-e XDG_RUNTIME_DIR=/run/user/1000 \
-e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
-e HOME=/home/classroom \
"$NAME" "$@"
}
since=$(docker exec "$NAME" date '+%Y-%m-%d %H:%M:%S')
number=$(run qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.loadScript \
"/tmp/$ID.js" "$ID" 2>/dev/null | tr -d '\r')
run qdbus6 org.kde.KWin "/Scripting/Script$number" org.kde.kwin.Script.run >/dev/null 2>&1
sleep 1
run qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.unloadScript "$ID" >/dev/null 2>&1 || true
# KWin's print() reaches the journal, tagged by the wrapper process.
docker exec "$NAME" journalctl --since "$since" --no-pager -o cat 2>/dev/null \
| grep -a "LAB:" | sed 's/.*LAB://'