0.1.50 was taken to the church and the things a container cannot answer were answered on the hardware: boot, autologin, the panel after a reboot, panel and browser scaling, Wonder Ink, Parent Call opening in the corner and reaching the sanctuary with a typed name, readable and silent notifications, HDMI audio and the volume keys. The promotion is a fast-forward. The experiment was cut from the published stable source and never diverged from it, so master reaches this work without a merge and there is one history. One thing changed for the release. The channel rules turn around: classroom-v2 and hyprland are the obsolete channels now and a room on either comes home to stable, while canary — where the next development release goes — and any channel somebody named by hand stay where they were put. A fresh installation has always defaulted to stable. Nothing else moved. This is the 0.1.50 that was verified, with a version number and a channel rule. Co-Authored-By: Claude Opus 5 <[email protected]>
75 KiB
Classroom v2 architecture experiment
An open re-evaluation of the whole Ekklesia Classroom desktop stack, with reliability as the first requirement. Nothing here reaches a stable, canary or hyprland room, and nothing here changes the development host.
Base
| branch | experiment/classroom-v2 |
| base commit | aadb1b881977b0eb25e1ca10e060977bed31a48f |
| base subject | fix: keep the system print dialog in front of the window that rendered it |
VERSION at base |
0.1.44 |
That commit is the published stable build, verified rather than assumed:
repo/stable/manifest.json names ekklesia-classroom_0.1.44_amd64.deb with
sha256 7de568…81041, and the copy in repo/packages/ hashes to exactly
that. The branch is not built on experiment/hyprland; git ls-files on it
contains no Hyprland file.
The answer, in one paragraph
Every user-visible failure the church reported is a piece of desktop infrastructure the classroom wrote itself. The shell that disappeared had no supervisor. The minimised window with no feedback exists because wlroots has no minimise and the classroom invented one. The secondary window that fights the browser needed a 314-line arbiter because sway tiles. The unrecognisable browser icon was hand-drawn to match a monochrome set. Debian 13 already ships a desktop that solves all four, so the recommendation is Plasma 6.3 on the Debian the classroom already runs, configured rather than written, and it was built and tested here rather than argued for.
The development host, as found
Recorded because it bounds what could be tested.
| OS | Debian 13 trixie (13.5), kernel 6.12.90 |
| machine | already a VM — root is /dev/vda2, GPU is bochs-drm, CPU is QEMU Virtual CPU |
| nested virtualisation | none — /dev/kvm does not exist |
| libvirt / qemu / VirtualBox | not installed |
| containers | Docker 26.1.5, cgroup v2 |
| resources | 6 vCPU, 11 GiB RAM, 160 GiB free |
This session could not create a virtual machine, so the candidate desktop was given a systemd container instead. Nothing outside Docker was changed on the host: no packages installed, no configuration touched, no service altered. See Alternate operating systems for what still needs a real VM.
The lab
tools/lab/ builds a Debian 13 container with a full Plasma 6.3 desktop in
it, runs systemd as PID 1 so the session is supervised the way a real machine
supervises it, and nests the session inside a headless sway so that grim
can photograph it and a real pointer can be aimed at it.
To resume: the lab is left running, built from the package, with Parent
Call's test rig not started — so the classroom's broker settings are
off and nothing can reach anything. tools/lab/parent-call-rig up brings
the coordinator back, and it re-establishes and re-verifies the block on
the church's networks before it will start.
tools/lab/lab build # the image
tools/lab/from-package # a fresh machine, the .deb, and nothing else
tools/lab/cold-start 3 # three complete cold starts, from nothing
tools/lab/lab session # compositor + Plasma session
tools/lab/lab shot NAME # docs/screenshots/NAME.png
tools/lab/lab click X Y # a real pointer press
tools/lab/kwin-script FILE # ask KWin what it believes about a window
tools/lab/recovery-check # every recovery property, measured
tools/lab/parent-call-rig # the real coordinator, safely walled off
Every window measurement in this document came from kwin-script, not from
looking at a screenshot. Every click came through libinput.
Three lab-only deviations, recorded because they bound what was proven:
- Software rendering. llvmpipe throughout, with KWin nested in sway. This proves behaviour and layout, not performance on classroom hardware.
- No sound card and no optical drive. The audio policy was therefore tested as a pure function against the real room's reported hardware state rather than end to end, and DVD playback was not exercised.
- Video did not decode. VLC came up fullscreen at exactly 1920×1080 with the panel covered, which is the window-management result being tested, but the picture was black under software XWayland.
What was measured
Each of these changed a decision.
The panel gives up after three crashes
Plasma's own unit restarts a crashed plasmashell, but with
StartLimitBurst=3 inside sixty seconds. Measured:
| kill 1 | back in 0.3 s |
| kill 2 | back in 0.4 s |
| kill 3 | never came back |
That is the production failure the church already had, reproduced on stock
Plasma. A four-line systemd drop-in
(systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf) makes
the restart unconditional. Re-measured: eight consecutive SIGKILLs one
second apart, back every time in about 2.4 s, with the open application
window untouched.
This is the single most important line in the whole experiment, and it is a supported override rather than a supervisor of our own.
KWin already recovers, and takes nothing with it
kill -9 kwin_wayland twice: back in 0.1 s each time, via
kwin_wayland_wrapper, with the running application still on screen and its
Wayland connection intact.
A deleted panel rebuilds itself
plasma-org.kde.plasma.desktop-appletsrc was deleted and the shell
restarted. The classroom bar came back exactly as designed, from the layout
script in the look-and-feel package. So the recovery procedure for a damaged
desktop is one file removed and one service restarted — no reflash, and no
technician in the room.
Plasma cannot show all five application states
The brief asks for closed, running, active, minimised and behind. Measured by sampling the pixels of the indicator strip rather than by eye:
| state | indicator | icon |
|---|---|---|
| closed | none — panel background (26, 29, 32) |
full colour |
| running, behind | grey (42, 45, 48) |
full colour |
| running, active | teal (65, 127, 121) |
full colour |
| minimised | grey (42, 45, 48) — identical to behind |
full colour |
Three marks for four states. Rather than write a task manager that draws a fourth — which is exactly the kind of custom desktop code this experiment exists to remove — the classroom removes the state: clicking a launcher always means show me this, never hide it, and the window decoration has no minimise button. There is nothing on the idle desktop a teacher could want to see, so minimising in this room only ever loses the lesson.
A window minimised by some other route is still shown as running and comes back with one click, so it can never be lost even then.
A window rule cannot tell a browser from its popup
The first attempt maximised the browser with a KWin rule on google-chrome.
Measured: the window a lesson page opened was maximised too, because Chrome
gives both the same class — the very behaviour the current architecture
spends 314 lines preventing.
Chrome can tell them apart, so --start-maximized in the launcher does it.
Measured afterwards:
| main browser | [0,0] 1920×1004 — the whole desktop above the bar |
| window the page opened | [509,160] 902×683 — centred, the size the page asked for |
| main browser afterwards | unchanged |
The secondary window carries an ordinary title bar with a close button, so the dock's sticky Close button has nothing left to do.
Printing
A page's own Print button produced a native KDE print dialog through
xdg-desktop-portal-kde, transient=true on the browser, centred over it,
in the classroom's colours, offering only Elmer. Stacking is the window
manager's ordinary behaviour and needed no rule.
An immutable colour scheme cannot be toggled
Marking [General] immutable in /etc/xdg/kdeglobals froze the palette so
hard that classroom-theme light changed the running session and lost the
change at the next start. Immutability now covers the Kiosk restrictions and
nothing else. Measured both ways afterwards.
Two packages cannot own one file
dpkg -i refused the first package outright: plasma-workspace already
owns /etc/xdg/plasmanotifyrc, and kdeglobals, kwinrc and the rest are
the same story. The room's settings live in
/etc/ekklesia-classroom/xdg/ instead, with
/etc/environment.d/90-ekklesia-classroom.conf putting that directory in
front of /etc/xdg in XDG_CONFIG_DIRS. That is KConfig's own cascade
rather than a fight over a path, and it was checked at runtime:
systemctl --user show-environment carries it, and kreadconfig6 in the
session reads the classroom's values.
The postinst then failed on a chmod for a command that had been
removed. It walks what the package actually installed now.
VLC calls itself Vlc
KWin's window matching is case sensitive, and a lower-case substring rule matched nothing while reporting nothing. The rule is a regular expression now.
The architecture
| distribution | Debian 13 trixie — unchanged, so the .deb and OTA work survives |
| desktop | KDE Plasma 6.3.6 (Debian's own packages) |
| compositor | KWin 6.3.6, Wayland |
| panel | Plasma panel, one full-width bar, built by a look-and-feel layout script |
| task feedback | Icons-only Task Manager |
| notifications | Plasma's own |
| browser | Google Chrome, unchanged |
| Wonder Ink | Chrome app mode, its own profile |
| session start | SDDM autologin (recommended; not yet tested — see risks) |
| lockdown | KDE Kiosk action restrictions |
| audio | one small classroom service, event driven |
The bar
One continuous full-width bar, 76 px, the height the room already has.
- Left — Parent Call, drawn as a line mark in the accent rather than a colour tile, so it reads as a different kind of thing from the applications and is visibly not one of them.
- Centre — the applications: Wonder Ink, and the browser wearing Chrome's own icon, in colour. That is the direct answer to the teacher who asked whether the old monochrome glyph meant "the Internet".
- Right — network, volume, removable media, notifications, clock. Four applets placed directly on the bar rather than a system tray: the tray discovers whatever is installed and hides most of it behind an arrow, and in the lab it collected battery, brightness, clipboard, keyboard layout, camera, vaults and screen management on a machine with none of those.
The bar launches applications, shows their state, and shows a little status. It is not the Live View, not Parent Call, and not a shell framework.
Wonder Ink as an application
Wonder Ink chrome --app=https://app.wonderink.org/ --user-data-dir=…/wonderink
Browser chrome the room's own profile
Its window class is chrome-app.wonderink.org__-Default — measured on a
running session, and the launcher's StartupWMClass matches it exactly, so
the panel shows one button rather than a launcher and a window side by side.
The separate profile buys something the current single-profile design cannot:
if a teacher signs into Wonder Ink through Google, that Google session is in
the Wonder Ink profile and closing the window ends it, while the church
YouTube sign-in lives in the browser's profile and is untouched. The
documented conflict in browser.md — that clearing wonderink.org leaves
the next teacher one click from the previous teacher's account — does not
arise.
The ordinary browser therefore no longer opens Wonder Ink. Its home page is YouTube, because a second way in through that window would put a teacher's lesson session in the room's shared profile, which is the leak the split exists to prevent.
Windows
KWin's ordinary behaviour, with one rule.
- Main windows fill the desktop above the bar, edge to edge, because Chrome is told to start maximized.
- Windows a page opens float over the main one, centred, at the size the page asked for, with a title bar and a close button.
- Dialogs are transient on their parent and stack correctly with no help.
- Fullscreen video covers the whole display:
[0,0] 1920×1080, panel included, no rounding, no wallpaper. - One rule, and it is
noborderfor VLC.
The outer margin and the 10 px rounding were dropped. Chrome squares its own corners when maximised, so margin and rounding are the same decision, and expressing "the screen minus 16 px" needs a rule generated per resolution at every boot. That is glue in exchange for appearance, and the brief says reliability wins. Full-bleed above the bar is also what every other appliance does.
Theme
One set of design tokens in EkklesiaDark.colors; the light scheme is
generated from it by tools/build-light-scheme, so a colour can only be
changed in one place. Plasma applies either to the panel, the notifications,
the decorations and every dialog at once. classroom-theme light|dark|toggle
is the whole switch. Accent teal #4FB3A8, taken from the wallpaper.
Wallpaper
config/wallpapers/classroom-calm.png is a stand-in, generated by
tools/build-wallpaper in the sage/teal/blue/lavender palette with a river
leading into the picture and a quiet upper middle. The illustrated wallpaper
the church selected is not in this repository; when it arrives it replaces
that file and nothing else changes.
Lockdown
/etc/xdg/kdeglobals, [KDE Action Restrictions][$i]. Verified by clicking,
not by reading the file: right-clicking the wallpaper produced no menu,
right-clicking the bar produced no menu, and KRunner was not running and
could not be summoned. run_command, shell_access, logout,
action/lock_screen, action/kwin_rmb and plasma/plasmashell/unlockedDesktop
all read back false. There is no lock screen and no session restore.
Nothing was achieved by deleting a binary. Lifting a restriction for maintenance is one line and a session restart.
Audio
The one place custom code is still right, because no desktop expresses this
policy. lib/classroom_audio.py is a pure function so it can be tested
against the room that failed rather than against a machine:
- a port that reports itself unavailable is not a candidate;
- an analog port existing is not evidence that a cable is in it;
- an explicit
AUDIO_OUTPUT=hdmi|analogoverrides everything.
scripts/classroom-audio follows PipeWire's own event stream — no polling —
and on every change sets the card profile, sets the default sink, and
moves streams that are already playing. That last step is the fix for the
teacher who had to restart the browser after changing the output.
Twelve test cases in tests/test-classroom-audio.sh, the real room among
them. The service is supervised the same way the panel is; measured, it came
back from four consecutive kills.
DVD
How much of the DVD player survives this change was measured, not assumed, because the room's disc playback works and breaking it for tidiness would be a bad trade.
- Its loading cover and control bar are
gtk-layer-shellsurfaces. KWin implementszwlr_layer_shell_v1— the Plasma panel is one — and a probe overlay came up full-screen under it. So the DVD surfaces keep working andpython3-giandgir1.2-gtklayershell-0.1stay in the package. Removing them brokeclassroom-dvd toggleimmediately, which is how this was found. - Its stylesheet was the dock's. The dock is gone; the template survives as
config/overlay/style.css.in, renamed to say what still uses it. classroom-dvdcallsclassroom-window, which speaks sway's IPC. Measured with no sway running: it exits 0 and does nothing, so it degrades rather than hanging — but the DVD player's focus handling is not yet ported, and that is the largest piece of work this change leaves.- One behavioural difference: under sway the loading cover hid the dock; under KWin the panel draws above it, so the bar stays visible while a disc spins up. Fullscreen VLC still covers everything, which is the state that matters.
/usr/share/solid/actions/ekklesia-dvd.desktop with an
X-KDE-Solid-Predicate for a video DVD. Plasma's device notifier offers
"Play the DVD" while a disc is in the drive and withdraws it when the disc
leaves, so a room without a drive shows nothing and no room needs
DVD_ENABLED edited. Nothing polls, and classroom_optical's two-second
loop goes away. Not exercised — the lab has no optical drive.
Parent Call
The coordinator already existed and its contract is frozen. It was read
from /home/media/docker/parent-call/README.md and its source on the
control computer, not guessed, and nothing here changes it.
What the teacher does
Click the mark on the left of the bar. A small card appears in the middle of the screen, above whatever they were reading. It already knows the room. They type a child's name and press Call parent. The card says what happened, in the coordinator's own words, and closes itself four seconds after a call is on its way.
Four steps, one of which is typing a name. The room is never asked for: this machine knows which room it is, and offering a list is a chance to pick the wrong one.
The mark
Three were put on a real panel and judged at the size the bar draws them.
| tried | why it lost |
|---|---|
| a bell | sat inches from Plasma's own notification bell in the same bar |
call-outgoing, a handset with an arrow |
read as a telephone feature, not as fetching somebody |
| an adult, a child, and one speech bubble | kept |
Breeze has people and it has telephones and the two never meet, so the
kept mark is drawn rather than picked — on Breeze's 22-unit grid, with its
ColorScheme-Text class, so it recolours with the session and needs no
second file for the light theme. docs/screenshots/pc-icon-vs-notifications.png
is it beside the notification bell.
It is carried by Quick Launch, on the left, apart from the applications. Two other widgets were tried first and both failed in ways worth writing down:
- the Icon widget copies the desktop entry into the user's home and uses the copy, so an update can never change the icon or the command — it kept showing a bell out of a package that no longer contained one;
- a task manager reads the entry live, which fixes that, but a task manager also shows every running window, so the auxiliary corner filled up with the lesson's tasks.
The states a teacher sees
Every word comes back from the coordinator in the message field. The
classroom never composes one for a call, because the coordinator is the
only thing that knows what happened.
| coordinator status | on screen | what the card does |
|---|---|---|
submitted |
Sending | waits |
waiting_approval |
Waiting for approval | waits, amber |
sending |
Sending to sanctuary | waits |
sent_to_sanctuary |
Sent to sanctuary | green, closes after 4s |
rejected |
Not approved | red, offers Try again |
expired |
Expired before it was sent | red, offers Try again |
unavailable |
Parent Call is unavailable | red, no way to send |
error |
Could not reach the sanctuary | red, offers Try again |
Nothing says "displayed". A ProPresenter call that succeeded means the message was accepted, not that anyone saw it, and Sent to sanctuary is the strongest claim the room is entitled to make.
Before a name can be typed at all, the card reads the two retained topics
and refuses in four different ways: no broker (Cannot reach the church
network), no coordinator (The sanctuary system is not running), mode
off (Parent Call is unavailable, the coordinator's own wording), and
no retained mode at all (Parent Call has not been set up yet). A teacher
is never left believing a call went out.
How it is built
Quick Launch -> classroom-parent-call a separate process
|
+-- classroom_parent_call topics, payloads, states
+-- paho, one short-lived clean session
Not a Plasma applet. A broker library, a network timeout and a text entry have no business inside the only way a teacher has of getting anywhere. Measured: killing the card leaves the panel untouched, and clicking the mark again starts a new one.
Three properties the contract asks for and this keeps:
- the status topic is subscribed before the request is published, so an answer cannot arrive before anyone is listening;
- the session is clean and nothing is retained, so a call from last Sunday cannot replay into this one;
- listening stops the moment a status is terminal.
Bounded everywhere: 5s to reach the broker, 1.5s for retained state, 8s
for the coordinator's first answer, and 210s overall — its own
REQUEST_TTL is 180. Silence is reported as No answer from the
sanctuary, which is not the same as a refusal and is not shown as one.
Sixty tests cover the contract with no broker at all
(tests/test-classroom-parent-call.sh).
Testing it without touching the sanctuary
tools/lab/parent-call-rig runs the real coordinator — copied from
the control computer — against a broker on loopback and a ProPresenter
that is a mock, and it rejects the church's own networks from inside
the container before it will start. That guard is not a convention; the
rig exits if either address is still reachable. It was needed:
10.40.0.2:1883 is reachable from this development host.
Verified end to end on the packaged install: request accepted, queued,
approved by an operator, delivered as
Name=Emma, Room=Nursery, and Sent to sanctuary shown. Also off,
rejected, and the broker stopped.
DVD
It is an application now
DVD used to sit in the status row beside the clock, as though a disc were a machine reading. It is in the row with Wonder Ink and the browser, with Breeze's optical-disc mark, and Plasma gives it the same closed / running / active treatment as the others.
DVD_ENABLED is gone. classroom-desktop-layout asks the machine for
/dev/sr* and removes the launcher from the layout on a room that has no
drive. A computer that moves between rooms takes its drive with it, and
nobody remembers to edit a file when it does.
Disc presence is not shown on the launcher. Doing that needs either polling or rewriting the panel while it runs, and rewriting the panel while it runs was measured to destroy it. The launcher tracks the drive, not the disc; a room with a drive and no disc gets a notification saying so when it is pressed.
The panel is rebuilt from the package at every login
Plasma keeps whatever panel it first built. An update could therefore change the bar in the package and never change it in a room — which is exactly what happened to an icon that outlived the file it came from.
classroom-desktop-reset removes the panel configuration before
plasmashell starts, so the bar is built from the layout every time. The
desktop is stateless: there is nothing in a teacher's home an update
cannot reach, and a damaged panel is repaired by the next login. Nothing
is lost, because Kiosk policy means a teacher never had anything in that
file.
Editing the live configuration instead was tried first and destroyed the
panel — the shell owns that file and rewrites it, so an edit races with
it. That is why this runs Before=plasma-plasmashell.service.
The last of sway
The DVD player was the only thing still speaking a compositor the room no longer runs. It asked sway's tree whether VLC's window had really gone fullscreen, because VLC reports playing while its window is still the black startup one the loading cover exists to hide.
VLC's own status was measured as a replacement and is not one. It
reports <fullscreen>true</fullscreen> in the same 250ms poll as
<state>playing</state>, because the flag is the option it was started
with and not a report about its window. So the cover is held for a floor
(MINIMUM_COVER_SECONDS) and still capped by the deadline that was
already there, and the compositor is not asked anything at all.
Focus-or-launch went the same way: the session already takes an exclusive lock so a second one cannot start, and bringing an existing one to the front is the panel's job.
Gone with it: classroom-window, classroom_sway.py, and
classroom_chrome.py, which turned out to be reachable from nothing.
One mention of sway remains in the whole tree, and it is the upgrade
step in postinst that removes the sway-era login shell from a room
being upgraded. Nothing executes it, imports it, or asks it anything.
Still unproven, and it is the same gap as before: playback end to end.
The lab has a drive with no readable disc and no libdvdcss, so VLC exits
on --key-length=. The loading cover, the control bar and the session's
own lifecycle all run; the picture does not.
Audio: how to test it on the hardware
Nothing below can be done in the lab: the container has no sound card, so
the policy is tested as a function and applying it is not tested at all.
classroom-audio show prints what it can see and what it would choose,
and changes nothing — start there.
- Television on before the computer. Boot with the TV on and an
input selected. Expected: sound comes out of the TV with nothing
touched. Check with
classroom-audio show; the chosen port should be anhdmi-output-*. - Television off during boot, switched on afterwards. Boot with the TV off, wait for the desktop, then switch the TV on. Expected: within a couple of seconds the output moves to HDMI by itself. This is the case the room actually failed on, and the one worth watching.
- Browser already playing. Start a YouTube video, then switch the TV
on or change the output. Expected: the sound follows without
restarting Chrome. That is the whole point of moving existing
streams; if Chrome still has to be restarted, the policy is not doing
its job and
journalctl --user -u ekklesia-classroom-audiowill say how many streams it moved. - Unplug and replug HDMI while something is playing. Expected: falls back, then returns.
- VLC already playing, same as 3.
- Overrides.
AUDIO_OUTPUT=analogin/etc/ekklesia-classroom/device.conf, restart the user service, and confirm it obeys even with the TV on. Thenhdmi, then back toauto.
What to capture if something is wrong: classroom-audio show,
pactl list cards short, pactl list sinks short, and the service
journal. The decision is a pure function, so a bad choice can be
reproduced as a test case from what those print.
Custom code this deletes
Deleted on this branch across both milestones, and counted rather than estimated:
| lines | replaced by | |
|---|---|---|
scripts/classroom-dock |
754 | the Plasma panel |
scripts/classroom-chrome-windows |
314 | --start-maximized, and a window manager |
tests/test-classroom-chrome-windows.sh |
280 | — |
config/dock/style.css.in |
102 | the colour scheme |
scripts/classroom-notifications |
74 | Plasma notifications |
config/sway/config |
62 | kwinrc |
config/mako/config.in |
48 | plasmanotifyrc |
config/waybar/* |
55 | the Plasma panel |
config/bash_profile, getty autologin |
6 | SDDM |
config/icons/browser.svg |
— | Chrome's own icon |
scripts/classroom-window |
208 | the panel, and a lock the DVD session already took |
lib/classroom_sway.py |
127 | — |
lib/classroom_chrome.py |
66 | nothing: it was reachable from nowhere |
tests/test-classroom-window.sh |
214 | — |
config/icons/* (dock set) |
— | Breeze, and two marks the classroom draws |
| total | ≈ 2 310 |
Rewritten rather than deleted: classroom-browser (41 → 30 lines, it no
longer decides launch-or-focus-or-minimise), classroom-scale (sway output
scale → kscreen-doctor), classroom-notify (found the session by looking
for sway and refused to run without mako; now looks for the compositor and
the shell).
Nothing in the classroom speaks to a compositor any more.
classroom_icons.py,
classroom_style.py and classroom_optical.py remain, reachable only
from the DVD player and the MQTT notifier: the DVD overlay draws its own
layer-shell surfaces and still needs the icon and stylesheet helpers the
dock shared, and disc detection is still ours.
Added since: the audio policy and its tests, the theme switch, Parent Call and its contract module, the layout tailor, the panel reset, and configuration.
Kept because they are the classroom's own subject matter: the DVD session and CSS setup, printer setup, provisioning, MQTT, the updater, shutdown. Added: the audio policy and its test, the theme switch, and configuration.
The package was built and its contents checked: every /usr/local path any
classroom command names is in it.
Alternatives, and why they lost
Stay on sway, add Waybar. Cheapest migration, and it loses on the thing that matters. wlroots has no minimise, so the invented hidden workspace stays; sway tiles, so the 314-line window arbiter stays; there is no window-manager decoration, so the dock's sticky Close button stays; and Waybar is one more unsupervised process to lose. It removes none of the four reported problems.
Hyprland (experiment/hyprland). Already measured on this repository and
already carrying two findings against it: the legacy config silently matches
nothing, and a Lua config changes the IPC language so the classroom would be
adopting a brand-new format early. For critical production UI in a room
nobody attends on a Sunday morning, that is the wrong end of the maturity
curve.
GNOME. Task state, a bottom bar and application pinning all need extensions, and extensions break on GNOME updates. That is precisely the "several fragile independently-maintained components" the brief rules out.
Fedora KDE / Arch. Both would supply the same Plasma. Neither supplies
anything Debian 13 does not, and both would cost the entire .deb and OTA
investment. Arch's rolling updates are the wrong risk for an appliance that
must survive unattended. Changing distribution buys nothing here, which
is the most useful negative result in this document.
Firefox ESR / Chromium instead of Chrome. No concrete benefit. Firefox has no app mode, which is what makes Wonder Ink an application; Debian's Chromium ships no Widevine. Chrome is what Wonder Ink is known to work in.
Recovery, measured against the package
tools/lab/recovery-check, run after tools/lab/from-package, against
the lab exactly as the .deb built it:
| panel, 8 × SIGKILL one second apart | back every time, 2.2–2.5s |
| compositor, 2 × SIGKILL | back in 0.1s |
| audio policy, 3 × SIGKILL | active every time |
| Parent Call killed | panel untouched |
| browser killed | panel untouched |
| afterwards | panel, compositor, 10 applets, audio all standing |
The drop-in that makes the panel restart unconditional is in the package,
not only in a hand-edited session: /etc/systemd/user/plasma-plasmashell.service.d/.
Reliability results
| test | result |
|---|---|
| cold start, 3 identical runs | 16, 16, 17 s — panel, compositor and 11 applets every time |
plasmashell SIGKILL ×8 at 1 s |
back every time, ≈2.4 s |
plasmashell on stock settings |
dead on the third kill — fixed by the drop-in |
kwin_wayland SIGKILL ×2 |
back in 0.1 s, windows survived |
| audio service SIGKILL ×4 | active every time |
| panel configuration deleted | rebuilt itself from the layout script |
| right-click desktop / panel | no menu |
| KRunner | not running, not reachable |
| light ⇄ dark | persists both ways |
| memory, 3 samples over 6 minutes | flat: plasmashell 399 MB, KWin 141 MB under llvmpipe |
| package install on a clean machine | clean, and the session came up from it |
The package
tools/build-release.sh builds it, and it was installed on a fresh machine
and booted from rather than described. ekklesia-classroom_0.1.45 is 65
files: the desktop under /etc/ekklesia-classroom/xdg, a look and feel
package, two colour schemes, the launchers, the Solid action, two supervised
user services, and the classroom's own commands. Dependencies swap
sway, swaybg, waybar, mako-notifier, python3-gi, gir1.2-gtklayershell-0.1
for plasma-desktop, plasma-workspace, kwin-wayland, plasma-nm, plasma-pa, sddm, and it Conflicts with the welcome tour, the software centre and the
phone pairing daemon that plasma-desktop would otherwise recommend into a
classroom.
Checked mechanically: every /usr/local path any classroom command names is
in the package.
The OTA mechanism is untouched. This is an ordinary version on the ordinary
channel structure, and an experimental build would go out as
0.1.45~plasma1 on a channel of its own, exactly as the Hyprland milestone
did.
Screenshots
Twenty-nine, in docs/screenshots/, indexed in
screenshots/README.md — including the two that
record failures rather than successes.
Not done, and honest about it
- DVD end to end. The overlay surfaces and disc detection were checked; playback was not, and the focus helper is not ported. See the DVD player above.
- Real audio hardware. Policy tested as a function; no card to apply it to.
- Video decode and performance. Software rendering only.
- SDDM. Recommended over the current
bash_profileexec, because when the session ends SDDM starts another one instead of leaving a teacher at a shell prompt. Not yet configured or tested. - Packaging.
tools/build-release.shstill installs the sway-era file list. The desktop is installed bytools/lab/install-desktop, which is the shape the package needs but is not the package. - Parent Call and Live View. Placed in the architecture, not built. Live View's pin is KWin's Keep Above, which is already a title-bar button.
- UI scale.
classroom-scalenow askskscreen-doctorinstead of the compositor, and 1.25 was applied and read back correctly. The look of it could not be judged: a nested session shrinks its own canvas to the logical size instead of keeping the display's mode, so everything got smaller rather than larger. On a real output it does the opposite. Judge this one on hardware. - Network states. plasma-nm is in the bar and live — it correctly showed a problem state throughout, because Docker configures the container's interface before NetworkManager sees it and NM reports limited connectivity for it. Online, connecting and offline transitions need a machine whose network NetworkManager actually owns.
Live View, and whether this desktop can carry it
Not implemented. What the KDE architecture already provides for it:
- A small status widget on the idle desktop. A Plasma applet, or — preferring less custom desktop code — a notification when the sanctuary goes live plus a launcher in the bar.
- A preview that expands. A separate application window, not something embedded in the panel. The panel may launch it and show that something is live; it must not be the player.
- Pin above other applications. This is KWin's Keep Above, which is already a window state with a title-bar button, and a window rule can force it for the preview's class. Nothing has to be invented, which was the open question when this experiment started.
- Unpin returns it to normal stacking. The same button.
The likely media path (MediaMTX, WHEP) is a property of the player application and does not touch the desktop architecture.
The power button, and the way back in
A press does nothing
Two layers, and both are needed. While a graphical session is running,
PowerDevil takes logind's handle-power-key inhibitor and acts on the
button itself, so a logind drop-in on its own would be overruled.
| layer | file | what it says |
|---|---|---|
| logind | /etc/systemd/logind.conf.d/ekklesia-classroom.conf |
HandlePowerKey, HandleSuspendKey, HandleHibernateKey, HandleRebootKey and every lid switch set to ignore, long-press included |
| PowerDevil | /etc/ekklesia-classroom/xdg/powermanagementprofilesrc |
powerButtonAction=0 and lidAction=0 in all three profiles, groups marked [$i] so the session cannot put an action back |
logind covers the machine before the session starts, after it ends, and if PowerDevil is not running at all. PowerDevil covers it while a teacher is using it.
Measured in the lab: with the drop-in installed and logind reloaded,
logind reports ignore for the power, suspend, hibernate and lid keys —
it reported poweroff and suspend before — and the running graphical
session was not disturbed by the reload. The postinst therefore
reloads logind, so the button stops working at install time rather than
at the next boot.
Holding the button still cuts the power. That is firmware, below any operating system, and it is left alone on purpose: it is the only recovery a person standing at a wedged machine has.
Needs the hardware. Nothing in a container has a power button. What is proven is that both layers are configured, packaged, and read back the way they should. Pressing the button is a bench test.
One chord opens a terminal
Meta+Ctrl+Alt+Shift+K.
Normal administration is still SSH. This is for the case where somebody is standing at a machine and SSH is not available.
The chord is declared in the package — X-KDE-Shortcuts in
ekklesia-admin-terminal.desktop — rather than seeded into a teacher's
configuration, where an update could not reach it. KGlobalAccel read the
entry and registered a component for it; invoking that component's
_launch over D-Bus opened a working shell, which is how the whole path
was verified without a keyboard.
It needed a wrapper, for a reason worth recording. The classroom's Kiosk
policy sets shell_access=false and Konsole honours it: with the
classroom configuration in its cascade, Konsole starts and prints
Program to run not set. rather than opening a shell. That restriction
is worth keeping for everything else, so classroom-admin-terminal
lifts it for one process by leaving the classroom's configuration
directory out of that process's XDG_CONFIG_DIRS. Nothing is patched,
no binary is replaced, and the terminal therefore looks like stock KDE
rather than like the classroom — which is fair, because it is not part
of the classroom.
It grants no privilege. The classroom user has no sudo rule, and
anything needing root still needs the administrator's own account and
password. shell_access was always a barrier in the interface, never a
privilege boundary.
Konsole is a package dependency, so the route exists on every machine.
Its own entry is shadowed from /usr/local/share/applications with
NoDisplay=true, leaving Konsole's file exactly where its package put
it.
One thing found while testing, and kept as a second layer rather than
removed: Konsole ships X-KDE-Shortcuts=Ctrl+Alt+T, a chord a great
many people know. The shadow entry blanks it, but the registration comes
from Konsole's own file and survives that — so it is still registered.
It is also inert: Konsole's entry carries
X-KDE-AuthorizeAction=shell_access, and with the classroom's
shell_access=false the launch is refused. Measured: invoking that
component over D-Bus opened nothing.
Nothing is pinned to the panel. A task button appears only while a terminal is open, and closing it returns the bar to three launchers.
Needs the hardware. The chord itself has never been pressed — key injection cannot reach the nested session in this lab. What is verified is that the shortcut is registered from the packaged entry and that its action opens a shell.
Long-Escape to turn the television off: it should change
The gesture asked for was: hold Escape, silence for three seconds, then a five-second visible countdown, cancel on release. Deliberate, hard to trigger by accident, and a clear intent.
What was measured
- KGlobalAccel accepts a bare
Escape. A probe entry declaringX-KDE-Shortcuts=Escapewas registered and given its own component. So the binding is possible. - Press and release are both observable. The component's D-Bus
interface carries
globalShortcutPressedandglobalShortcutReleased. Hold duration and cancel-on-release are therefore expressible without any input daemon of our own. - KWin's scripting API has no key events at all —
registerShortcutand screen edges, and nothing that reports a key going up.
What was not measured, and why it decides the answer
Whether a registered global shortcut takes the key away from applications. Key injection cannot reach the nested session in this lab, so this could not be pressed.
The mechanism says it does: on Wayland, KWin matches global shortcuts in
an input filter that runs before the event reaches the focused surface,
and a match stops propagation. That is how Meta+Q never arrives at an
application. A bare Escape bound globally should therefore stop
reaching anything — no leaving a fullscreen video, no dismissing a Chrome
dialog, no closing the Parent Call card.
This is inference, not a measurement, and it is a thirty-second bench test: bind the probe entry above, press Escape in a fullscreen video, and see whether it leaves fullscreen.
The recommendation
Change the gesture. Even if Escape were forwarded as well as observed, the whole ceremony would rest on a global grab of the key an appliance uses most, and a countdown driven from the process holding that grab is an input daemon in all but name. That is the fragile thing the classroom keeps removing.
Two clean alternatives, both using mechanisms already proven here:
- Hold-to-confirm on screen. A television control where the volume applet is now: press and hold it, a countdown runs, releasing cancels. Press, hold and release inside an application's own surface are ordinary Qt and need no global input at all. This keeps every property the gesture was for — deliberate, visible countdown, cancel-on-release — and costs nothing in reliability.
- A modifier chord, the same mechanism as the maintenance terminal and proven to work in this session. Instant rather than ceremonial, but impossible to press by accident. Worth adding as a second route for somebody who cannot find the mouse.
If a keyboard hold is still wanted after the bench test, it should be on a key the classroom does not otherwise use — never Escape.
The television control service
Unchanged in shape from the previous checkpoint, and now with the gesture question answered:
panel control / chord presentation only
|
v
ekklesia-classroom-tv.service small, supervised, ours
|
+-- backend: CEC over the adapter
+-- interface: on, off, volume up, volume down, mute, input
- The keyboard and the panel request "TV off". They never speak CEC.
- The service decides how. CEC hardware can change without the desktop noticing.
- The same service will own automatic TV-on at session start, volume, mute and input, and it is why the computer's own volume applet is expected to leave the bar.
- Supervised and self-recovering, the same way the audio policy is.
No CEC code has been written and no protocol has been guessed. The adapters arrive today; the commands get measured before anything is built on them.
The upgrade that failed
The clean-install result was real and the migration was not. A classroom machine that had been through the sway build and then the Hyprland experiment took the 0.1.45 package and came up as stock Debian Plasma: kickoff, a pager, a full system tray, the Debian wallpaper, the old Waybar still across the top, mako still catching notifications — and then a lock screen, on an account that has no password. Somebody had to give the appliance account a password over SSH to get back in.
None of that was visible here, because the lab had been answering an easier question than the one a room asks.
Reproducing it
One line of the harness:
tools/lab/lab plasma-up: -e XDG_CONFIG_DIRS=/etc/ekklesia-classroom/xdg:/etc/xdg
The lab had been handing the session under test the very setting whose
delivery was the thing in doubt. Removing it and starting the session the
way a machine does reproduced the failure exactly: 23 applets, kickoff,
pager, system tray, Debian wallpaper — docs/screenshots/migration-before.png
is that screen, and it is the teacher's screen.
Root causes
1. The classroom's settings never reached the session. Everything the
room is — the panel layout, the lock screen that must never appear, the
idle behaviour, every Kiosk restriction — lives in
/etc/ekklesia-classroom/xdg, and reaches Plasma only if that directory
is in XDG_CONFIG_DIRS. It was set in /etc/environment.d, which
systemd's user manager reads. SDDM starts the session through PAM, and
PAM does not read /etc/environment.d. Debian's own
/etc/xdg/plasma-workspace/env/env.sh then set the variable from scratch,
because it was empty, and startplasma pushed its own environment back
into the user manager — so the value was not merely bypassed, it was
overwritten for every user service too. Measured in the session that came
up:
XDG_CONFIG_DIRS=/home/classroom/.config/kdedefaults:/etc/xdg:/usr/share/desktop-base/kf5-settings
LookAndFeelPackage = org.debian.desktop
Autolock = (unset, so on)
shell_access = (unset, so allowed)
The stock panel, the lock screen and the absence of every Kiosk restriction are all that one line.
2. Deleting the panel configuration made it worse. The desktop reset
removes plasma-org.kde.plasma.desktop-appletsrc before the shell starts
so that the bar always comes from the package. With the look and feel
unresolvable, Plasma rebuilt the Debian default layout instead. The
reset was correct and was pointed at the wrong answer.
3. Naming a colour scheme does not produce one. ColorScheme=EkklesiaDark
is a label; KConfig reads the actual colours from [Colors:*] groups in
kdeglobals, and nothing was putting them there except a technician having
run classroom-theme once by hand. The clean-install screenshots looked
right for a reason the package did not own. A machine with no user
profile got Breeze light — confirmed here the moment a profile was
cleared.
4. A stale user profile outranks the package. ~/.config/kdeglobals
sits ahead of every system directory, and ~/.config/kdedefaults ahead of
ours. A migrated machine carried a Breeze-light palette in both.
5. The old shell was still being started. waybar.service was
activated through graphical-session.target.wants — and, found only by
running the crash-recovery suite on a migrated machine, mako came back
through D-Bus: the panel was killed, something sent a notification while
nothing owned org.freedesktop.Notifications, and D-Bus started mako from
the file mako-notifier ships. Deactivating the systemd unit would not
have been enough.
And one more that had not bitten yet: device.conf still said
CHANNEL=hyprland, so the next release published on that channel would
have handed the room the Hyprland experiment back.
The fix
config/plasma/env/zz-ekklesia-classroom.sh |
new. /etc/xdg/plasma-workspace/env/ is sourced by startplasma for every session; the name sorts after Debian's env.sh, so it appends rather than replaces, and it is a no-op if the directory is already there. This is the root fix. |
config/plasma/xdg/kdeglobals |
LookAndFeelPackage[$i] — per-entry immutability, so no leftover profile and no stray write can answer that question differently. The palette stays writable, because classroom-theme may change it. |
tools/build-kdeglobals |
new. Composes the shipped kdeglobals from the session policy and EkklesiaDark.colors, so the room is dark because the package says so. One source of truth: it composes, it does not duplicate. |
scripts/classroom-desktop-reset |
asserts the cascade and repairs it for the shell about to start if it is wrong; removes another desktop's look and feel defaults and a foreign palette; keeps an Ekklesia one; clears a user power profile. |
scripts/classroom-migrate |
new. Stops, deactivates and masks the old shell's units, removes activation from all three directories it can live in, removes hand-written unit files that no Debian package owns, clears old autostart entries, removes the login shell that used to start sway, and migrates device.conf. Run from postinst on every install. |
config/dbus/fr.emersion.mako.service |
new. Shadows mako's D-Bus activation from /usr/local/share, where the session bus looks first. Exec=/bin/false, which is what a machine with no notification daemon does anyway. |
config/plasma/xdg/powermanagementprofilesrc |
profiles immutable at the profile level, not just on the button subgroups: PowerDevil loads an idle action when it finds a group for it, so a writable profile is one a leftover user file can add a suspend to. |
config/systemd/logind-ekklesia-classroom.conf |
IdleAction=ignore as well. |
config/sddm/ekklesia-classroom.conf |
uid range 1000–1999. The first migrated room's classroom account was uid 1001 and would not have appeared in the greeter at all. |
What device.conf means now
COMPOSITOR and HYPR_APPEARANCE are removed on upgrade. There is one
desktop stack and nothing left for them to choose; a stale value is an
instruction this package cannot carry out.
CHANNEL=hyprland becomes CHANNEL=classroom-v2. Not stable and not
canary: both are the sway line, and a release published there would
convert a v2 machine back. That channel is not published yet, so a
migrated machine takes no updates until it is — which is the honest state
of an unpublished experiment, and better than being quietly reverted.
Everything else in the file, the broker password included, is rewritten
line by line and byte-for-byte.
The tests that would have caught it
tools/lab/lab plasma-upno longer setsXDG_CONFIG_DIRS. The package has to put it there, the way it has to on a machine.tools/lab/lab reboot— a real restart of the machine under test: new PID 1, new user manager, every drop-in and generator read again. SDDM autologin genuinely runs here: it authenticates the classroom through PAM and launches the Plasma session, and then has no display hardware to draw on. Both scenarios now go through it.tools/lab/converged-check— 60 properties of a converged machine, asked of the running system and never of the repository. Both scenarios run the same list, because a fresh install and a migrated one have to be indistinguishable from a teacher's chair.tools/lab/dirty-upgrade— the Hyprland package actually installed, with sway, waybar and mako behind it; Waybar activated in all three directories; an autostart entry; a device.conf asking for a compositor that is gone; a Breeze-light profile in both places that outrank the package; a stock panel already built; and a user power profile that would suspend the room. Then the new package, a reboot, and the questions.tests/test-migration.sh— 41 source-level checks, one per reason the failure happened.
Results
| dirty upgrade → converged | 60 / 60 |
| installed a second time over itself | 60 / 60, device.conf unchanged |
| clean install → converged | 60 / 60 |
| panel killed eight times | back every time, 2.2–7.4s |
| compositor killed twice | 0.1–0.4s |
| audio policy killed three times | active every time |
| the room's light theme across a logout | survived |
| every test script | 11 / 11 |
docs/screenshots/migration-after.png and v2-from-package.png are the
migrated machine and the fresh one. They are the same desktop.
One regression this pass made and caught
The first version of the reset deleted ~/.config/kdedefaults
unconditionally. Plasma treats that directory as the record of which look
and feel is in force, so deleting it made Plasma re-apply the look and
feel at every login — which throws away the room's chosen palette.
Measured: classroom-theme light, log out, and the bar came back dark.
The rule is now whose the defaults are: the classroom's are kept, another
desktop's are removed.
The rooms themselves: 0.1.48
The first real classroom, Little Waders, took the Plasma build and then reported what a lab cannot: a disc player on a machine with no drive, a clock nobody could read, a browser at half the size of the panel around it, and Parent Call with no broker to call.
The broker every room shares
There is one, at 10.40.0.2, and every classroom uses it. It is a default
now rather than something to fill in room by room: a fresh machine is
pointed at it, and classroom-migrate brings an existing one to the same
place — switched on if it was off, addressed if it was blank, and left
exactly as it is if somebody deliberately pointed it elsewhere. The room
name, the username and the password are read by none of it and printed by
none of it, which tests/test-classroom-migrate-config.sh checks against
the real migration code rather than a copy of it.
The link restarts on always rather than on-failure, because it exits
cleanly when it finds no broker configured and a machine set up before
the church had one would otherwise never pick it up.
Parent Call now says which of the two problems a teacher has. "Cannot reach the church network" and "This room has no church network set up" send somebody to look at completely different things, and saying the first when the truth is the second costs an hour at a router.
A disc player on a machine with no drive
The detection was right and it ran too late. classroom-provision runs at
boot alongside the session rather than before it, and the panel is built
from the layout the moment a session starts: a room told about its own
hardware afterwards keeps the wrong bar until the next login.
ekklesia-classroom-layout.service runs it before the display manager
instead, a udev rule re-runs it when a drive is plugged in or pulled out,
and the login says so in the journal when the layout and the hardware
disagree. Measured in a mount namespace with no /dev/sr*: the launcher
is removed and the rebuilt panel has ten applets and no disc.
One scale, and everything that was not following it
UI_SCALE had been packaged, clamped, documented — and applied by an
autostart entry that only appeared in 0.1.47. Three things were still
outside it:
- The ceiling was 1.5. A 4K television wants 2.0, so a room asking for it silently got 1.5. It is 3.0 now, and the hidden display dialog offers up to it.
- The clock was pinned at 15 points, which is the same size at every scale. It is sized by the bar now, which follows the scale like everything else.
- Chrome was not told. On Wayland it did not follow the compositor's
output scale, so the browser was half the size of the panel around it.
Both launchers pass
--force-device-scale-factor, from the one command that owns the number.
Measured at 200% in the lab: the panel, the clock, the notification card, Chrome's tab strip and the page itself all doubled together.
The notification card
A Plasma style, which is the supported way and the reason no QML was replaced. A style is a set of pictures and anything the classroom does not provide comes from Breeze, so the worst this can do is look wrong — never fail to appear. Replacing the notification plasmoid's QML was the other route and a QML error there is a room with no notifications at all.
Web pages are blocked from notifying at the policy level, with no prompt: a permission dialog in a classroom is a dialog a child can agree to.
The volume display, and a shell package that costs the panel
The 0.1.47 build replaced Plasma's on-screen volume display with its own, through a shell package of its own. Plasma 6.3 does read the display from there and from nowhere else — the look and feel is not consulted, which was measured — so the mechanism was right.
With that shell package selected, plasmashell builds no panel at all. Not ours, not the stock one. Measured twice, with the 0.1.47 package's own files. The classroom deletes its panel configuration at every login on purpose, so every login was one step away from a television showing a wallpaper and nothing else.
So it is gone, and the volume display is Plasma's own. It is drawn at the
room's scale like everything else, which is what the television actually
needed. classroom-desktop-reset also strips a ShellPackage line out of
a profile written while the old build was installed.
The chime stays off, in plasmaparc, which is the file Plasma 6 actually
reads for it.
What the lab was allowed to reach
Pointing the package at the real broker had an immediate consequence: the lab connected to it, under a container id, as a room. Nothing was left behind — the broker held no retained status for it afterwards, which was checked — but the lab had no business being there.
tools/lab/lab up now blackholes both church networks before anything
starts, and refuses to continue if either is still reachable. The rig had
carried a guard for this since it was written and it had never once
worked: it used iptables, which is not installed in the lab image, and
it probed with /dev/tcp under sh, which is a bash feature and fails
under dash whatever the network is doing. Both were reported as success.
A morning in the rooms: 0.1.50
Four things a television found that a container had not.
"Parent Call has not been set up yet", on a room that was
The card came up checking with the sanctuary and then said the room had never been set up. Nothing about the room was wrong. Read from the church broker, read-only:
classroom/parent-call/coordinator {"online": true, ...}
classroom/parent-call/pending {...}
classroom/parent-call/mode — absent
The coordinator's own source says why. It publishes the retained mode
only in answer to an operator writing to …/mode/set, and on this
broker nobody ever has. The classroom waited 1.5 seconds for a retained
message that will never exist, and then told the teacher the room was
unconfigured.
So a missing mode is now read as what it is: the coordinator's own default, which the sanctuary end knows and this end does not. The room offers the call and lets the coordinator answer. If the true mode is off, the coordinator refuses in its own words through the status topic, which the card already shows — a wrong no costs a teacher the call, a wrong yes costs a round trip and tells the truth at the end of it.
The distinction the card has to keep is untouched. A broker that is not configured at all still says so, a broker that cannot be reached still says that, and a coordinator that is down still names the sanctuary. "Undecided" is now a third answer, with no message, which is why the card shows nothing at all until the retained topics have had their chance.
Proved on a rig whose broker was made to match the church's exactly —
coordinator and pending retained, mode deleted. The card came up ready,
and a call went through to the sanctuary mock: Ruth Alvarez, Nursery.
The card opens where its button is
Centred, on a classroom television, is a long way from the button in the bottom-left corner. A Wayland client cannot place its own window, so the card is anchored rather than positioned: layer shell, bottom and left edges, margins in logical pixels. The compositor does the arithmetic, so it is correct at 1920×1080 at 100% and at 3840×2160 at 200% with no coordinates written down anywhere. Both were photographed.
The classroom has drawn the disc player's cover this way under KWin for months, so the mechanism is not new here. It is optional: if the library is missing the card still opens, centred, exactly as before.
The one thing the lab cannot answer is whether a teacher can type into it. The entry takes focus and shows a caret, which is KWin giving the layer surface the keyboard. Actually typing has never been testable in this lab — the tool that tries closes the card — so the first bench test on this build is a name typed into Parent Call with a real keyboard.
Notifications for a room, not a desk
Everything Plasma draws is measured in grid units, and a grid unit is the height of the general font. There is no setting that makes a notification larger on its own, so the font is the lever — and it is the supported one, which matters after the shell package that cost the panel.
Measured at 100%: Breeze's 10 point gives a card about 350×110 with a title a volunteer at the back cannot read; 15 point gives about 410×145 with a title that carries. Text, icon, padding and buttons all move together because they are all grid units. The bar does not change: its height is fixed and the clock sizes itself to it.
At 200% the display scale doubles all of it again. No chime, unchanged:
AudioFeedback is off in plasmaparc and the applet's own setting is
off in the layout.
The room does not introduce itself
"Ekklesia Classroom" is gone from everything a teacher sees. The
notification card is from Classroom, the disc player's notifications
are from DVD, the maintenance terminal is a Maintenance terminal.
Nineteen visible names are checked by the test suite now, and the product
name stays where it belongs: package names, units, paths and topics.
And the lab, again
Four rooms named after container ids are sitting on the church broker as
retained status messages. The fence was applied when each container
started and lost the moment it was restarted, because routes do not
survive that — and lab reboot is in every scenario. It re-fences now
and refuses to continue if either church network answers. The four
retained topics are the user's to clear; the commands are in the report.
Stable: 0.2.0
The experiment is over. 0.1.50 was taken to the church and everything a container could not answer was answered on the hardware: Plasma and KWin boot, autologin works, the panel comes back after a reboot, the panel and Chrome are the right size, Wonder Ink runs, Parent Call opens in the corner, accepts a typed name and reaches the sanctuary, notifications are readable and silent, HDMI audio and the volume keys work, and the room looks the way it was meant to.
So the Plasma classroom is the stable line, and this branch's work is on master. The promotion was a fast-forward: the experiment was cut from the published stable source and never diverged from it, so there is one history and no merge.
One thing changed for the promotion and nothing else did. The channel
rules turned around: classroom-v2 and hyprland are the obsolete
channels now and a room on either comes home to stable, while canary
— the next development release's channel — and any channel named by hand
are left exactly where somebody put them. A fresh installation has always
defaulted to stable and still does.
Publishing followed the existing path: the package goes up through a
channel publish, and update_stable.sh moves the manifest afterwards,
manifest last.
Unresolved risks
In the order I would worry about them.
- DVD playback has never run end to end on this desktop. The port off sway is done and nothing asks a compositor anything, but the lab has a drive with no readable disc and no libdvdcss, so VLC exits before a picture. A room that uses DVDs should not take this build until a real disc has played, the cover has dropped at the right moment, and eject has returned cleanly to the desktop.
- The loading cover is now timed, not observed. It used to wait for
the window manager to confirm VLC was fullscreen. It waits for a floor
instead, because VLC's own fullscreen flag was measured to be the
option it was given rather than a report about its window. On real
hardware the disc is far slower than the floor, so this should not
show — but if the cover ever drops onto a black VLC window, this is
why, and the fix is to raise
MINIMUM_COVER_SECONDS. - Parent Call has never had a real key pressed into it. Keyboard
injection cannot reach the nested session in this lab at all — the
same limit that stopped it reaching Chrome — and the one tool that
tries disturbs the session enough to close the card. The name was
typed through a lab-only
--childargument. Type a name on the bench before a Sunday. - SDDM has never put a picture on a screen. More is known than before: in the lab it really does autologin, authenticating the classroom through PAM and launching the Plasma session — that is now one of the checks. What it cannot do in a container is find a display to draw on, so everything from the compositor's first frame onwards is still a bench test. It remains the first thing to check on a VM or a bench machine.
- No real audio hardware has been through the policy. The decision is
well tested; applying it is not. A wrong
pactl set-card-profileon real hardware is a silent room. - Plasma is a much larger surface than sway. More code, more services, more that can regress in a Debian point release. The mitigations are that all of it is supervised, that Debian 13 is frozen apart from security updates, and that the OTA channel structure already lets a build be held back. But it is a real trade and it should be named.
- Performance is unmeasured on classroom hardware. Everything here ran on llvmpipe, which says nothing useful about an old office machine with Intel graphics. Plasma with compositing is heavier than sway.
- Chrome remains a third-party repository. Unchanged from today, but still the one part of the stack Debian does not update.
- The wallpaper is a stand-in. The real illustration has not been seen against this palette, and is to be supplied separately.
- The disc launcher tracks the drive, not the disc. A room with a drive shows the DVD button whether or not anything is in it.
- The power button has never been pressed. Both layers are configured, packaged, and read back correctly, but nothing in a container has a power button.
- The maintenance chord has never been typed. The shortcut is
registered from the packaged entry and its action opens a shell —
verified over D-Bus — but key injection cannot reach the nested
session, so
Meta+Ctrl+Alt+Shift+Kitself is unpressed. - Idle behaviour is configured and unobserved. No suspend, no dim, no blank and no lock are all now in the package, immutable, and read back correctly from a migrated session. Nothing in a container goes idle, so none of it has been watched not happening. Leave a bench machine alone for an hour.
- The v2 channel does not exist yet. A migrated machine is moved to
CHANNEL=classroom-v2so that a stable or Hyprland release cannot take it back, and nothing is published there. Until it is, a v2 machine takes no updates at all. That is deliberate; it is also a thing to remember before leaving one in a room. - Nothing in this pass has been on the classroom machines. The development host has no route to Little Waders — its SSH refuses this machine's key, and the control computer cannot reach it either — so every result above is the lab's. The package is built and the deploy commands are written down; the room itself is unverified.
- The experiment's channel is now the obsolete one. It carried the rooms while this was an experiment and 0.2.0 brings them home to stable. It is kept, pointed at 0.2.0 so that a machine still on it takes the release that moves it, and the 0.1.50 package stays hosted as the rollback.
- Typing into the anchored Parent Call card was verified at the church, not here. Layer shell gives it the keyboard — the entry takes focus and shows a caret — but keyboard injection has never worked in this lab, so the first thing to do on the bench is type a name into it. If it cannot be typed into, removing gtk-layer-shell from the package restores the centred card with no other change.
- Ctrl+Alt+T is still registered. Konsole's own entry declares it
and the registration survives being shadowed. It is inert, because
Konsole refuses to open a shell under the classroom's
shell_access=false— measured — but it rests on that one restriction rather than on the chord not existing.
Would I trust it on a classroom computer, unattended, for a Sunday?
For a room that does not use DVDs: yes, after the checks below. The reasoning is not that Plasma is nicer. It is that the four failures the church actually reported are all failures of code we wrote, three of them are gone because the code is gone, and the fourth — the panel disappearing — went from reproducible on the third crash to survived eight kills and is now a supervised system service rather than a process started by a compositor and forgotten.
For a room that uses DVDs: not yet. Finish the focus port and play a real disc first.
Before any room:
- Boot a bench machine from the package and confirm SDDM autologins, and that killing the session brings it back rather than a shell.
- Put a television on HDMI, switch it on after the computer, and confirm sound arrives without touching anything, with the browser already open. The full procedure is under Audio: how to test it on the hardware.
- Press Parent Call and type a name with a real keyboard. Then do it with the coordinator's mode off, and with the broker stopped.
- Press the power button. Nothing should happen. Then hold it, and confirm the machine still dies — that is the recovery path and it must stay.
- Type Meta+Ctrl+Alt+Shift+K. A terminal should open. Then try Ctrl+Alt+T and confirm nothing does.
- Print to Elmer.
- Play a DVD, if that room uses them: cover, picture, controls, eject.
- Leave it running from Friday to Sunday.
Alternate operating systems
None is required. The recommendation stays on Debian 13, which the classroom already runs and already packages for.
If the church later wants Fedora KDE or Arch compared on real hardware, that needs a VM this session could not create:
Recommended next candidate: none. Debian 13 + Plasma 6.3 was actually tested, in a container, on this branch. A disposable VM is needed only for what a container cannot show: 4 vCPU, 8 GiB RAM, 60 GiB disk, a real GPU or working virtio-gpu, an HDMI-capable audio device, and an optical drive — to test DVD playback, audio switching against real hardware, SDDM autologin, display hotplug, and performance on classroom-class hardware.
The existing Debian classroom test VM should be kept.
Checkpoint log
- Branch opened at
aadb1b8; host recorded as unable to create a VM. - Lab built; Plasma 6.3.6 running software-rendered at 1920×1080.
- Panel supervision measured, failure reproduced, drop-in written, re-measured.
- Classroom desktop written: colours, layout, window rules, Kiosk, launchers.
- Task states measured; minimise removed as a route.
- Secondary windows, print dialog, notifications, fullscreen, themes, lockdown all verified on a running session.
- Audio policy written with the failing room as a test.
- Cold starts repeated: eleven, identical.
- Package built, and the file conflict and the stale
postinstit exposed both fixed. tools/lab/from-packagerun: fresh machine,.deb, session up, both applications launched, one button each.- Parent Call built against the coordinator's own frozen contract, and proven end to end on a rig that rejects the church's networks before it starts.
- Parent Call's mark replaced twice and judged on a real panel.
- The last of sway removed: the DVD player asks no compositor
anything, and
classroom-window,classroom_swayandclassroom_chromeare gone. - DVD moved out of the status row into the applications, and the
launcher now follows the machine's drive rather than
DVD_ENABLED. - The panel is rebuilt from the package at every login, so an update can reach a room that has already run.
- Recovery re-measured against the packaged build, not a hand-edited session.
- The power button disabled at both layers, packaged, and read back from logind after a reload that did not disturb the session.
- A maintenance terminal behind one obscure chord, declared in the package, hidden from every application list, granting no privilege.
- Long-Escape investigated and recommended against, with the two clean alternatives written down.
- The migration failure found on a real room and fixed in the package: the settings directory now reaches the session the way Plasma delivers it, the look and feel cannot be answered for by a stale profile, the palette is in the package rather than in somebody's home directory, and the old shell is stopped, deactivated, masked and — the one that was only found by killing the panel — unable to come back through D-Bus.
- The lab stopped flattering the package: it no longer sets the session's configuration path by hand, it restarts the machine, and it asks a fresh install and a machine upgraded from the Hyprland experiment the same sixty questions. Both answer them identically.
- 0.1.47's work brought into the repository, where it had only ever existed in a build directory: the hidden display dialog behind Meta+Ctrl+Alt+Shift+D, its authenticated writer, the polkit action, the autostart entry that applies the scale, and the church's own wallpaper.
- The broker given to every room, the disc player decided before the session starts, the clock and the browser brought under the one scale, the notification card given a Plasma style of its own, and the shell package that cost the panel taken back out — 0.1.48.
Next, in order:
- Deploy 0.1.48 to Little Waders and run the list. Everything in The rooms themselves is lab work: the development host cannot reach the classroom machines at all. The deploy and rollback commands are in the report that accompanies this build, and the checks that matter are the panel after a reboot, the clock and the browser at the room's scale, Parent Call with a typed name, the broker, HDMI audio and the volume keys.
- The thirty-second Escape test, before anything is built on the
gesture: bind a probe entry to
Escape, press it in a fullscreen video, and see whether the video leaves fullscreen. That settles the one thing this pass could not measure. - On the bench, tonight. Type a name into Parent Call with a real keyboard. Switch a television on after the computer and listen. Play a disc. These three are the only things left that a container cannot answer, and each of them is a Sunday-morning path. Add the power button and the maintenance chord to that list.
- Publish the v2 channel.
classroom-migratealready moves a migrated room ontoCHANNEL=classroom-v2, and nothing serves that manifest yet, so those machines are taking no updates at all. This is now the first packaging job rather than a tidy-up. - Take the upgraded VM through the same list by hand:
tools/lab/converged-checkis the shape of it, and the things a container cannot answer are the lock screen never appearing after an hour alone, and the machine still being there on Monday. - Bench-test SDDM on hardware: autologin all the way to a picture, and that ending the session brings the classroom back rather than a shell.
- When the CEC adapters have been exercised, replace the computer volume applet with a television control, behind the service boundary in Television control, and where CEC will fit.
- Live View, when the rest is quiet.