River Kids came up silent on stable 0.2.2. The decision this policy makes
was right and was never asked for.
classroom-audio applied its policy once at startup and then only when a
PipeWire event arrived. pactl talks to PipeWire's PulseAudio server, and
the unit was ordered after pipewire and wireplumber but not after that.
At boot it therefore raced the server and lost: the single startup apply
failed with "Connection refused", the subscription attached cleanly a few
seconds later, and from that moment the daemon waited for an event that
had already happened. systemd said active, the subscription was live, and
the policy had never run at all.
That is why the card was found on output:analog-stereo+input:analog-stereo
with its HDMI port available. That profile is WirePlumber's own pick, not
one this policy would ever produce, which is the evidence it never ran.
Reproduced in the lab against real PipeWire 1.4.2 and WirePlumber 0.5.8 by
starting the daemon with pipewire-pulse stopped and then starting it: the
old daemon sits idle forever, the new one converges.
The shape is now: wait for the server, apply, subscribe, apply again the
moment the subscription attaches, and coalesce each burst of events into
one decision. Applying on attach is the fix for the race. While the room
has not got the output it would prefer it also looks again on a backing
off timer capped at a minute, so a television switched on hours later is
picked up whether or not ALSA's jack change ever reaches us as an event.
Once the room has what it wants it stops looking entirely, and a machine
with no digital output never looks at all.
Also here, all of it found by measuring the real server rather than
reading the code:
* sink lookup matched on fields a sink does not have. Two of its three
conditions could never be true; the one that worked returned whichever
sink of the card came first rather than the one for the chosen port.
* volume and mute are no longer touched. The old code unmuted and forced
70% on every output change, so every television power cycle reset a
volume the teacher had set and could unmute an output muted on
purpose. WirePlumber already remembers volume per device. A machine
this policy has never configured still gets 70% once.
* the profile chosen keeps the card's input, which is the difference
between output:hdmi-stereo and the output:hdmi-stereo+input:analog-stereo
an administrator reaches for by hand.
* a card profile is only set when it is not already in place, and only
so many times, so a sink that never appears cannot become a switching
loop.
* a machine with no sound server at all says so once and backs off,
rather than filling the journal. pactl subscribe's own stderr is
dropped because saying it is this daemon's job, and a subscription is
only treated as healthy once it has lasted, since pactl starts
successfully and then fails to connect.
* the fixtures described a machine nobody has. Real pactl puts no
"direction" on a port at all and says "availability unknown" rather
than nothing, so a test could not have caught this.
Existing streams are moved explicitly. Measured both ways on the real
stack: changing the default sink does move a stream that never asked for
a particular sink, but a stream pinned to a device stays where it is with
no policy running, and this moves it back without the application being
restarted.
tests/test-classroom-audio-daemon.sh drives the real daemon through the
boot race, a television switched on with and without an event announcing
it, a power cycle, a set that never comes on, and a room already correct.
It uses a pactl mock because no loopback or dummy sound driver has an HDMI
port, so the one thing a lab cannot prove is that the real jack change
arrives; docs/testing.md carries the physical procedure for that and says
not to take a passing build as evidence of it.
Co-Authored-By: Claude Opus 5 <[email protected]>
15 KiB
Classroom test checklist
Run against the exact artifact published to canary, on the real endpoint,
before promoting to stable with ./update_stable.sh.
Boot
- Cold boot reaches the classroom shell with no terminal or window junk
- Reboot does the same
- Wallpaper appears
- Dock appears, centred
- Clock appears, bottom right
Provisioning
journalctl -u ekklesia-classroom-provisionshows a clean run- Google Chrome is installed on a machine that never had it
- A second boot does not reinstall anything
- A machine with no network still boots to a working shell, and provisioning succeeds at a later boot once it is online
Networking
nmtuiis installed andNetworkManager.serviceis enabled- An Ethernet interface declared in
/etc/network/interfacesremains managed by ifupdown and keeps its existing address sudo nmtuican save and activate a Wi-Fi connection- The saved Wi-Fi connection reconnects after reboot
- A machine with no Wi-Fi adapter reaches the classroom normally without an online-wait delay or NetworkManager failure
- Ethernet-only operation is unchanged
Printing
cups.serviceis enabled andlpstat -rreports that the scheduler runslpinfo -mcontains the generic color PCL 6/PCL XL driver documented in printing.mdsudo classroom-printer-setupcreates onlyElmer, enables it, and makes it the system default- Provisioning creates Elmer automatically at
socket://10.10.0.155:9100with descriptionElmer lpoptions -p Elmer -lshows*NormalforPrintoutModeand*ColorforColorModel- An upgrade removes the old
Classroom_Colorqueue - Repeating provisioning reports Elmer already configured and does not rewrite the queue
- Chrome opens the system print dialog and shows only Elmer
lpstat -eprints onlyElmer; neither the Brother label printer nor a discovered duplicate Ricoh appearsavahi-daemon.serviceandavahi-daemon.socketare masked- The CUPS test page prints in color on the Ricoh MP C3502
- With the Ricoh powered off, the classroom still boots normally and a job simply remains queued
Secondary Chrome windows
- The first Chrome top-level remains maximised, tiled and borderless
- A second
google-chrometop-level floats instead of splitting the main browser - It is centred at roughly three quarters of the logical display
- Its red Close button is visible and mouse-accessible
- Close removes only the secondary and reveals the untouched main browser
- Changing either page title does not change the main/secondary roles
- A third Chrome top-level also floats and Close targets the newest one
- Chrome minimise/restore and the dock Browser button still behave normally
UI scale
UI_SCALE sets sway's output scale, so it scales the whole session.
Change it, then sudo systemctl restart getty@tty1.
UI_SCALE=1.0is identical to a session with no scalingswaymsg -t get_outputsshows the mode still 1920x1080 at every scale, with only the logical size changing- At 1.25 and 1.5, Chrome grows: tabs, omnibox, toolbar icons, menus and page content
- Chrome's buttons are still easy to hit with the mouse
- Dock, clock, notifications and the DVD bar grow by the same amount — once, not twice
- Dock icons stay crisp at 1.25 and 1.5 on a normal-density monitor, not just from across the room: compare an icon against the clock text beside it
- DVD control bar icons are crisp at the same scales
UI_SCALE=0.8shrinks everything together- A nonsense value (
UI_SCALE=banana) still gives a working shell - An extreme value (
UI_SCALE=9) is clamped, not obeyed journalctlor the sway log showsclassroom-scale: session scale N
Reserved bottom shell (regression)
The bottom strip is one full-width layer-shell surface with a 76px exclusive zone. Dock and clock live inside it. Do not split them back into separate layer-shell windows.
- A maximised Chrome window stops above the bottom strip
- The clock does not float over Chrome
- Dock stays centred, clock stays bottom right
- Wallpaper shows through the unused parts of the strip
- Every dock button still responds to clicks
- A fullscreen application covers the whole display, dock included
Browser
- First Browser click launches Chrome
- Chrome opens on Wonder Ink (https://app.wonderink.org/)
- Clicking Browser while Chrome is focused minimises it
- Clicking Browser again restores it maximised, not floating or draggable
- Clicking Browser while Chrome is running but unfocused focuses it
Super+Mminimises the focused windowSuper+Shift+Mrestores the most recently minimised window- Normal navigation to any other site works
- The Home button returns to Wonder Ink
- Fullscreen YouTube video uses the whole display
- Leaving fullscreen restores the correct geometry above the dock
- The main Chrome window has no sway title bar — it starts at Chrome's own tab strip, and Chrome's tabs and address bar are still there
- Chrome still maximises and minimises as before
- After a reboot, no "Restore pages? Chrome didn't shut down correctly" bubble
- Chrome does not offer to save passwords
- Chrome shows no way to sign into the browser itself
- No "Add profile" and no Guest entry in the profile menu
- No profile picker at startup
- Wonder Ink signs in normally during a session
- After closing Chrome fully and reopening, Wonder Ink is signed out (see browser.md — this is the unverified one)
- YouTube is still signed into the church account after that
chrome://policylists PasswordManagerEnabled as an applied managed policy- Website logins still persist across a reboot
- Clicking Browser when Chrome is closed shows a throbber on the dock button until the window appears
- Clicking Browser when Chrome is already open feels immediate, with no throbber
- The pointer shows a busy cursor while it is over the dock
- No first-run, default-browser or sign-in nag appears
chrome://policylists the Ekklesia policies as applied, with no "unknown policy" entries- Chrome does not start by itself at boot
- After a reboot, cookies and site logins are still there
- After an OTA update, cookies and site logins are still there
Known limitation: Chrome's own minimise button in its title bar does
nothing. Sway ignores the Wayland minimise request and gives no event to
hook. Minimising is the dock's Browser button or Super+M.
Notifications
classroom-notify-testrenders with the classroom styling: dark translucent, rounded, white text, top rightclassroom-notify-test --critical "Parent Call" "Room 3 needs a parent."renders in the critical style and does not time out- Notifications appear over a fullscreen video
Over SSH from an administrator account, run it with sudo:
sudo classroom-notify-test --dry-run # show the session it resolved
sudo classroom-notify-test # send into the classroom desktop
The classroom desktop belongs to the classroom user, and mako sits on that user's D-Bus session. A plain notify-send from an SSH session lands in the administrator's own session, where nothing owns org.freedesktop.Notifications, so D-Bus tries to start a second mako with no display and the call times out with NoReply. The script finds the running sway process for the classroom user and delivers into its session instead. It never starts mako.
sudo classroom-notify-testfrom an SSH session displays on the classroom screensudo classroom-notify-test --dry-runreports the resolved XDG_RUNTIME_DIR, bus address and Wayland display- Running it as an unprivileged admin account explains it needs sudo
tests/test-classroom-notify-test.shpasses (also run by the build)
Audio
The room prefers the television. AUDIO_OUTPUT in device.conf decides:
auto (prefer a connected HDMI or DisplayPort set, fall back to the
jack), hdmi, or analog. A machine with no digital output at all — a
laptop, when one arrives — is correct on auto without any change here.
Two test suites cover this, and both run in the build:
tests/test-classroom-audio.sh— the decision itself, against the shapes real pactl emitstests/test-classroom-audio-daemon.sh— the real daemon driven through every situation a room gets into, against a pactl that answers the way PipeWire does
On a machine:
classroom-audio showlists the outputs, what it chose, the profile it would set and whether it issettledjournalctl --user -u ekklesia-classroom-audioshows oneoutput is ...line per real change and nothing in between- Leaving the room alone for an hour adds no further lines and no measurable CPU time
The television switched on after the computer
This is the scenario that silenced River Kids on 0.2.2 and the one no lab can produce: ALSA reports a television through the HDMI port's availability, and no loopback or dummy sound driver has such a port. The suites above play it through a mock, and the mock is faithful to the real server as far as it was measured — but a mock cannot prove the real ALSA jack change arrives. Do not take a passing build as evidence that this works. It has to be done at a machine, once, per release:
- Switch the television off at the set, not the remote.
- Reboot the computer and let it reach the classroom desktop.
- Leave it at least two minutes, so any startup settling is over.
journalctl --user -u ekklesia-classroom-audio— expect oneoutput is ...line. In a room with nothing in the analog jack it names the HDMI port even now, which is right: there is nowhere better for the sound to go, and the set is then already correct when it comes on.- Open Wonder Ink and Chrome and start something playing.
- Switch the television on.
- Sound should arrive within about a minute, in both applications, without closing or reopening either one.
wpctl statusshould show the HDMI sink as default, andclassroom-audio showshould saysettled=True.- Switch the television off and on again. Sound should come back.
- Check the volume is where the teacher left it, and that an output muted on purpose is still muted.
If step 7 fails, the jack change is not reaching PipeWire at all; capture
pactl subscribe and classroom-audio show with the set off and then on,
which is what distinguishes that from a policy fault.
Messaging
- With
MQTT_ENABLED=false,journalctl -u ekklesia-classroom-mqttshows one line and the service is inactive — and the room is completely normal - With a broker configured, the service connects and
mosquitto_sub -t 'classroom/status/#' -vshows the room online - A published notification appears on the classroom screen in the classroom styling
urgency: criticalrenders in the critical style- Killing the broker leaves the room entirely usable
- The service reconnects on its own when the broker returns
- Rebooting with the broker down boots normally to the dock
- A retained notification left on the broker does not appear at boot
DVD
With DVD_ENABLED=false:
- No DVD button in the dock
With DVD_ENABLED=true:
- Drive unplugged: button present, icon held back, friendly notification on click
- Drive present, no disc: friendly notification on click, nothing crashes
- Inserting a disc produces one DVD Ready notification
- The DVD icon comes up to full strength while a disc is in
- Nothing plays until the button is pressed
- Clicking plays the disc
- Menus work
- Audio works
- Chapter navigation works
- Fullscreen works
Super+Mminimises the player, the DVD button restores it- Closing the player works
- Ejecting is detected, the icon goes back
- Reinserting is detected again
- Commercial CSS disc plays without anyone having run codec setup by hand
Sanctuary LIVE capability
With LIVE_ENABLED=false:
- No LIVE button or spacing hole appears in the dock
- No live-specific service or process starts
With LIVE_ENABLED=true:
- LIVE appears after DVD when DVD is enabled, and after Browser otherwise
- Clicking it shows the existing placeholder notification
- No live playback is attempted
DVD mode
- Pressing DVD covers the whole display immediately, dock and clock included — no wallpaper visible anywhere, at any point
- A spinner and "Loading DVD…" show while the disc spins up
- No partial VLC window is ever visible against the wallpaper
- The loading surface stays up until the disc menu is actually showing — VLC's tiled black startup window is never visible
- Video or the disc menu replaces the loading surface when ready
- A disc that never opens still gives the desktop back (20s)
- No VLC chrome anywhere: no menu bar, no fullscreen controller, no cone, no sway title bar
- Wallpaper is never visible around VLC
- Moving the mouse to the bottom slides the classroom bar up
- It hides again shortly after the mouse leaves it
- While hidden, clicking a disc menu item near the bottom still reaches the menu
- Play/pause toggles and the icon follows the actual state
- Previous and next chapter work
- The menu button returns to the disc menu
- Space pauses; arrow keys and Enter navigate the disc menu
- Arrow keys do not change volume, and no key reaches crop, aspect ratio, subtitles, audio tracks or deinterlacing
- Esc leaves DVD mode cleanly, with no black window left behind
- Close leaves DVD mode cleanly
- Eject leaves DVD mode and opens the tray
- Killing VLC by hand also returns to the classroom desktop
- Pressing DVD twice quickly does not start two sessions
- The bar is sized by UI_SCALE like the dock
OTA
./update_canary.shpublishes package first, manifest last- The endpoint on the canary channel picks the update up
- SHA-256 verification passes
- Install succeeds and the endpoint reboots
- Everything above still works after the update
- Upgrade from 0.1.36 preserves every existing
device.confvalue LIVE_ENABLED=falseis appended exactly once on upgrade and remains single after reinstalltools/validate-trixie-release.shpasses against the 0.1.37 package and the 0.1.36 package