1d9da3bca0f8816ea93087f155931fd9839b3421
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
55f4c183c7 |
feat: Presentations, for a teacher who downloaded tonight's session
PRESENTATIONS_ENABLED, off by default, independent of everything else. A teacher downloads a session in ordinary Chrome and it is on the Presentations screen a few seconds later, ready for the television. No file manager, no import button, no Impress. WHY NOTHING LAUNCHES A DOCUMENT APPLICATION. The brief was to use existing presentation software, so the existing software was tried first, on Debian 13, before any of this was designed. `soffice --show` does start a real fullscreen slideshow — and needs XDG_SESSION_TYPE plus a VCL plugin or it dies with "Failed to open display", shows a splash, opened a "Tip of the Day: 1/225" dialog over the slides, and leaves the Impress editor when the show ends. `okular --presentation` opens THREE windows: the presentation, the ordinary document window behind it, and an information dialog. zathura is not a presenter. pdfpc is the closest and is another dependency with a presenter-console model. Every one of them leaves an application on screen that can open any file on the machine, and none of them knows there is a library to go back to. So LibreOffice converts once, headless, at import, with no window on any screen; poppler renders the pages; the classroom shows the pictures. Measured: 0.34s for a ten-slide PPTX, 0.03s for its ten thumbnails, 0.10s for one slide at 1920px. That is not a new renderer — it is the same engine Impress would have used live — and it means Escape is ours, presenting is instant, and a PDF exported from Google Slides skips the conversion entirely and keeps the appearance Google gave it. THE THREE THINGS KEPT APART. The library thumbnail, the starting slide and the retention clock are independent, because the first slide of a Bible study session is a generic opening shared by every session and the one that names it is usually the second. Quick Look chooses the picture; it does not change where the slideshow starts and it does not count as having presented anything. mark_presented() is called when a slide is actually drawn, not when Present is pressed: an intention is not a presentation, and a viewer that failed to start must not buy another thirty days. IMPORTING. The teacher's download is copied, never moved, renamed or deleted. A file with a .crdownload beside it is still arriving. Wonder Ink's own folder is not looked at. Two downloads of the same name are two presentations, the second titled (2) — found in the lab, where a PDF and a PPTX of one session both came out with the same name because the import being named was excluded from the taken-titles set BY TITLE, and a set holds one copy of a repeated string. Metadata is written saying "importing" before any work, so a reboot mid-conversion leaves a directory the next startup sweeps and imports again. A file that cannot be converted gets a card saying so, offering only Remove, with the teacher's download untouched. Retention: 180 days never presented, 30 days from the last presentation, every presentation starting the thirty over. The cleanup never touches a presentation being shown, never leaves the library directory, and never looks at Downloads. The dock order is unchanged: Presentations is appended after the disc player, so Wonder Ink, the browser and the player keep the places teachers have been reaching for. There is a test for exactly that. classroom-wonderink now asks feature() rather than carrying its own copy of the default, so the panel and the command cannot disagree. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
27cd64a668 |
fix: the room asks for its output again, not only once at boot
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]>
|
||
|
|
23dff0b7b4 |
feat: close the chord that let a teacher hibernate the room
Ctrl+Alt+Delete opened Plasma's session screen and a teacher hibernated a classroom computer from it. A hibernated room cannot be recovered by anybody standing in front of it, which on a Sunday morning is everybody. logout=false was already set and did not stop it. That restriction governs Plasma's own actions — what plasmashell draws in a menu or an applet. The chord is a KGlobalAccel shortcut registered by ksmserver and it asks D-Bus for the greeter without consulting a Plasma action at all. A restriction on a menu cannot see a key. Three layers, because each one alone has a way past it. The key. kglobalshortcutsrc unbinds every ksmserver session action and PowerDevil's Sleep, Hibernate, PowerDown and PowerOff, each entry marked immutable. It is deliberately not in the xdg directory with the other settings: kglobalacceld opens that file as a SimpleConfig, so it reads one file in the teacher's home and no system directory at all. Measured — with it in the cascade, kreadconfig6 reported the chord unbound while the shortcut server went on answering Ctrl+Alt+Del. classroom-desktop-reset copies the package's copy into place before the shortcut server starts, the same way the panel is copied out of the package at every login. The policy. A polkit rule refuses login1.suspend* and .hibernate* for the classroom user and for nobody else. This is the layer that matters: a key can be unbound and anything reaching logind can still ask. Refusing here rather than masking sleep.target keeps a technician's own account working, and is also what tells Plasma to stop drawing the button — CanSuspend and CanHibernate come back no. Hybrid sleep and suspend-then-hibernate have no action of their own in systemd 257; both are authorised through hibernate, so denying it denies all three. The greeter. Its D-Bus activation is shadowed with /bin/false, the same mechanism already used for the old notification daemon. Layer one removes the caller, this removes the callee. Measured in the lab, from the package, after a reboot: the escape reproduced first with the shadow removed — the greeter really does start — and then refused, exited 1, with it back. The chord reports nothing bound. systemctl suspend as the classroom user is denied. The maintenance chord and the display chord both still answer, by key code, after the shortcut file is replaced wholesale. A root process may still suspend, hibernate, reboot and power off. 182 checks pass across a logout and back. Screen locking stays disabled, the power button still does nothing, and reboot and shut down are not newly denied to anybody. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
34fd02e08f |
feat: give every room the broker, one scale, and its own panel back
Little Waders took the Plasma build and reported what a container cannot. A disc player on a machine with no optical drive, a clock nobody at the back of the room could read, a browser at half the size of the panel around it, and Parent Call with no broker to call. The broker is the same for every classroom, so it is a default now and not something to fill in room by room. classroom-migrate brings an existing room to the same place without touching its name, its username or its password, and Parent Call distinguishes a broker it cannot reach from one that was never set up: those send somebody to look at completely different things. The disc player's detection was right and ran too late — alongside the session rather than before it, and the panel is built from the layout the moment a session starts. It runs before the display manager now, and again from udev when a drive is plugged in. UI_SCALE had a ceiling of 1.5, which cannot express a 4K television; a clock pinned at 15 points, which is the same size at every scale; and a browser that was never told the number at all. All three are fixed and the room doubles together at 200%. This also brings 0.1.47's work into the repository, where it had only ever existed in a build directory under /tmp: the hidden display dialog, its authenticated writer, the polkit action, and the church's own wallpaper. One piece of it is deliberately left out. The custom volume display needed a shell package of our own, and with one selected plasmashell builds no panel at all — measured twice, with that build's own files, on a classroom that deletes its panel configuration at every login by design. The lab connected to the real church broker the moment the package defaulted to it. Nothing was left behind, which was checked, but the guard that should have stopped it had never worked: it used iptables, which the lab image does not have, and probed with /dev/tcp under dash, where that always fails. Both reported success. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
d2d30d6b0c |
feat: take the last of sway out of the classroom
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 in the same 250ms poll as playing, 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 and capped by the deadline that was already there, and the compositor is not asked anything. 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. classroom-window, classroom_sway and classroom_chrome are gone with it — the last of them was reachable from nothing at all. DVD also moves out of the status row, where it sat beside the clock as though a disc were a machine reading. It is an application: it is in the row with Wonder Ink and the browser, and classroom-provision removes it from the layout on a machine with no optical drive, which retires DVD_ENABLED. A computer that moves between rooms takes its drive with it; nobody remembers to edit a file when it does. The panel is rebuilt from the package at every login now. Plasma keeps whatever panel it first built, so an update could change the bar in the package and never change it in a room — measured, with an icon that outlived the file it came from. Editing the live panel configuration was tried instead and destroyed the panel, because the shell owns that file. Removing it before the shell starts is safe and makes the desktop stateless. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
4c3f19d599 |
build: keep the DVD and MQTT helpers, and count the deletion honestly
Building the package showed what reading the tree did not: classroom-dvd still calls classroom-window, classroom-mqtt still calls classroom-notify, and the DVD overlay still imports the icon and stylesheet helpers the dock shared. They go back in, and the write-up now counts what was actually removed rather than what could be. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
2ff9d47be9 |
feat: decide the classroom's audio output from what is plugged in
The room that prompted this had a television on HDMI with working PCM audio and nothing in the analog jack, and WirePlumber chose analog. A teacher who corrected it by hand then had to restart the browser before any sound came out. The policy is a pure function so it can be tested against that room rather than against a machine: an unavailable port is not a candidate, an analog port merely existing is not evidence of a cable, and an explicit AUDIO_OUTPUT overrides the lot. Twelve cases, the real room among them. The daemon follows PipeWire's own event stream rather than polling, and moves streams that are already playing, which is the part that stops a teacher having to close and reopen the browser. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
87b55761c6 |
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]> |
||
|
|
a1d8150d7c | Run provisioning after package install | ||
|
|
dfd42f6fdf |
feat: give the rooms a line to the church broker
The first piece of turning these machines into connected endpoints. classroom-mqtt is one small service per room that subscribes, validates what arrives, and hands the work to commands that already exist. Nothing in the room depends on it. A broker that is down, a network that is missing, a machine with no broker configured at all — each is a service sitting quietly, not a classroom that fails. Nothing waits for it at boot and the shipped default is off, so every existing classroom upgrades into this release with messaging disabled until a broker exists. The room is the hostname, which is already how these machines are named. Topics carry no product name because the broker is the church's and these are just rooms: classroom/notify/all, classroom/notify/<room>, and a retained classroom/status/<room> so anyone asking later can be told which rooms are up without waiting for them to say so again. Notifications are events and are never retained; status is state and is. Stale messages cannot come back, which is the property that matters most and has two halves. The session is clean, so a broker never hands over a backlog from while the room was off. And retained notifications are refused on arrival, because otherwise one would replay into every room at every reboot forever. The honest limit is written down: MQTT only flags a retained message when it is replayed to a new subscription, so a retained publish delivered live is indistinguishable from an ordinary one and is shown once — which is right, since it is live at that moment, and the replay is the actual hazard. Delivery reuses the session-aware sender rather than a second copy of it. classroom-notify-test grew into the real thing, so it is now classroom-notify with the sample sender a wrapper over it, and its 36 tests came along. The broker password lives in device.conf, so that file stops being world readable: 0640 root:classroom, which everything that reads it already satisfies. Transport logic is kept free of any broker library so it can be tested without one — 40 checks on room identity, the topic contract and which payloads become notifications. Separately verified against a real mosquitto: connect, subscribe, retained status, per-room and all-room delivery, urgency, duplicate and expired and malformed messages ignored, the broker killed with the daemon surviving, reconnect, and a stale retained notification left on the broker not reaching the screen when a room boots. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
255072ac94 |
feat: settle Chrome's window, crash bubble and password prompts
Three things a volunteer should never have to see. Sway was drawing its own title bar above Chrome's tab strip, which already carries the window controls. Verified on a real session: Chrome is an xdg_shell client with app_id "google-chrome" and sway was giving it border "normal". A scoped rule removes the compositor decoration only — Chrome keeps its tabs and address bar, stays a normal window, and still maximises and minimises. A class rule covers an XWayland fallback. Nothing else changes: the only other window in this classroom is VLC, which is already borderless, so no broader rule is warranted. "Restore pages? Chrome didn't shut down correctly" is suppressed with --hide-crash-restore-bubble. Note the name. The switch this was going to use, --disable-session-crashed-bubble, is not present in Chrome 152 at all — checked against the strings of the installed binary — so it would have silently done nothing. That hides the prompt; the profile was still being marked as crashed, which is the untidy half. Chrome's browser process shuts down cleanly on SIGTERM, so a shutdown unit now gives it one and waits up to eight seconds before the session is torn out from under it. Best effort by construction: a shutdown that will not finish is far worse than a crash flag. Password prompts go off with the PasswordManagerEnabled policy, on the path the classroom already manages. Existing profile and session data are untouched, and website logins are unaffected: Wonder Ink and YouTube stay signed in. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
a3dba6e8fb |
feat: provision Chrome and DVD codecs without a manual step
A fresh classroom needed two things installed by hand on every box: Google Chrome, which is not in Debian, and the DVD codecs, which Debian cannot ship. Both now happen on their own. ekklesia-classroom-provision.service runs after every boot and does the work that needs the network and the apt lock, neither of which a package postinst can have. Each step returns immediately if it is already done, so a provisioned machine costs nothing and a machine that was offline the first time gets another go at the next boot. Nothing there is allowed to fail the boot: a classroom with no DVD codecs is still a working classroom, and it says so in the log rather than silently. The postinst enables the service but deliberately does not start it. Every OTA update reboots the moment the install finishes, and an apt run killed by that reboot would leave dpkg half-configured. Chrome comes from Google's own repository rather than a one-off .deb, so it keeps getting security updates. Google's package migrates whatever we write into a deb822 file of its own, so the repository is looked for across every source file instead of only the one we would write; otherwise a machine that had Chrome removed would end up with two copies of the same repository. The DVD codec helper previously assumed deb822 sources and did nothing on a machine using a classic /etc/apt/sources.list, which is what the hardware actually runs. It now handles both formats, and only ever adds a missing component to an existing Debian entry — so it cannot produce a duplicate repository or a duplicate component however many times it runs, and third-party repositories like Chrome's are never touched. It also answers libdvd-pkg's debconf questions, so nothing blocks on a prompt in a service with no terminal. Tested against fixture APT trees for classic sources.list, deb822 .sources, contrib already enabled, repeated runs, third-party repositories and the awkward real lines: bracketed options, indentation, trailing comments and flat repositories. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
5d7781c352 | chore: checkpoint classroom 0.1.5 stable | ||
|
|
b1ff7a31ce | feat: establish classroom 0.1.0 baseline |