diff --git a/docs/classroom-v2-experiment.md b/docs/classroom-v2-experiment.md index 741d851..1db0570 100644 --- a/docs/classroom-v2-experiment.md +++ b/docs/classroom-v2-experiment.md @@ -15,21 +15,25 @@ or hyprland room, and nothing here changes the development host. 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 +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. -## What the experiment must not do +## The answer, in one paragraph -* No change to the development host's OS, packages, partitions or bootloader. -* No in-place distribution upgrade or migration. -* No touching stable, canary, or a real classroom machine. -* Alternate operating systems get a disposable environment or a written VM - specification, never the host. +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 can be tested here. +Recorded because it bounds what could be tested. | | | | --- | --- | @@ -37,43 +41,404 @@ Recorded because it bounds what can be tested here. | 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, `/dev/uinput` present | +| containers | Docker 26.1.5, cgroup v2 | | resources | 6 vCPU, 11 GiB RAM, 160 GiB free | -**Consequence: this session cannot create a virtual machine.** There is no -KVM and no hypervisor. Every alternate OS or desktop that needs real -hardware-level isolation has to be either tested in a container or written up -as a VM specification for someone with a hypervisor to run. The previous -Hyprland milestone hit the same wall and used containers; that lab worked, and -this experiment extends it rather than inventing a new one. +**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. -## Hypothesis being tested +## The lab -> The classroom's reliability problems — a shell that vanished in production, -> no minimised-window feedback, secondary windows fighting the main browser, -> and a growing pile of compositor glue — are all symptoms of having written a -> desktop environment by hand. A mature integrated desktop already solves each -> one, and Debian 13 already ships one. +`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. -`apt-cache policy` inside a throwaway `debian:trixie` container, on the same -archive the classroom already tracks: +``` +tools/lab/lab build # the image +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 +``` -| package | Debian 13 version | +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: + +| | | | --- | --- | -| plasma-workspace | 4:6.3.6-2 | -| plasma-desktop | 4:6.3.6-1 | -| kwin-wayland | 4:6.3.6-1 | -| pipewire / wireplumber | 1.4.2 / 0.5.8 | -| vlc | 3.0.23 | -| cups | 2.4.10 | -| chromium | 152.0.7977.82 | -| firefox-esr | 140.15.0esr | +| kill 1 | back in 0.3 s | +| kill 2 | back in 0.4 s | +| kill 3 | **never came back** | -So a full Plasma 6.3 desktop is available **without leaving Debian**, which -means the entire `.deb`/apt OTA investment survives the change. That is the -first thing that has to be checked, because if a better desktop required -Fedora or Arch, this experiment would have to stop at a VM specification. +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 `SIGKILL`s one +second apart, back every time in about 2.4 s**, with the open application +window untouched. -## Status +This is the single most important line in the whole experiment, and it is a +supported override rather than a supervisor of our own. -See the checkpoint log at the end of this document. +### 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. + +### 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 `noborder` for 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|analog` overrides 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 + +`/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. + +## Custom code this deletes + +| | lines | replaced by | +| --- | --- | --- | +| `classroom-dock` | 754 | the Plasma panel | +| `classroom-chrome-windows` | 314 | `--start-maximized`, and a window manager | +| `classroom-window` | 208 | the task manager | +| `classroom-notify` | 214 | Plasma notifications | +| `classroom-notifications` | 74 | — | +| `classroom-scale` | 57 | Plasma display scaling | +| `classroom_sway.py` | 127 | — | +| `classroom_icons.py` | 95 | the icon theme | +| `classroom_style.py` | 36 | the colour scheme | +| `classroom_optical.py` | 125 | Solid | +| sway, waybar, mako configs | — | — | +| `config/bash_profile` | 2 | SDDM | +| **total** | **≈ 2 000** | | + +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. + +## 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. + +## 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 | + +## Screenshots + +In `docs/screenshots/`. + +`v2-idle-desktop` the idle desktop · `v2-light-bar` / `v2-dark-bar` the same +bar in both themes · `state-1-closed` closed · `state-3-behind` active beside +running · `state-4-minimized` · `state-5-restored` · `v2-testpage` a +maximised browser · `v2-secondary-window` a window a page opened, floating · +`v2-print-dialog` · `v2-notification-crop` · `v2-fullscreen` · `recover-panel` +the bar rebuilt from nothing · `lock-desktop-rmb`, `lock-panel-rmb`, +`lock-krunner` the lockdown. + +## Not done, and honest about it + +* **DVD end to end.** No drive in the lab. +* **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_profile` `exec`, 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.sh` still installs the sway-era file + list. The desktop is installed by `tools/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.** Plasma's own display scaling replaces `classroom-scale`; not + exercised at 1.25 or 1.5. + +## 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. + +**Next**: SDDM autologin; move the file list in `tools/build-release.sh` to +the Plasma layout and build a `0.1.45~plasma1` package on its own channel; +Plasma display scaling at 1.25 and 1.5; then a real VM for DVD, audio and +performance. diff --git a/docs/screenshots/recover-panel.png b/docs/screenshots/recover-panel.png new file mode 100644 index 0000000..d1c0246 Binary files /dev/null and b/docs/screenshots/recover-panel.png differ diff --git a/tools/lab/Dockerfile.plasma b/tools/lab/Dockerfile.plasma index c9c84ae..1f7ff03 100644 --- a/tools/lab/Dockerfile.plasma +++ b/tools/lab/Dockerfile.plasma @@ -70,3 +70,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pipewire pipewire-pulse wireplumber pulseaudio-utils \ libnotify-bin \ && rm -rf /var/lib/apt/lists/* + +# seatd, so wlroots can open input devices without a login session. The lab +# compositor needs a seat before it can offer the session under test a +# pointer to be clicked with. +RUN apt-get update && apt-get install -y --no-install-recommends seatd \ + && rm -rf /var/lib/apt/lists/* diff --git a/tools/lab/cold-start b/tools/lab/cold-start new file mode 100755 index 0000000..c92fc2c --- /dev/null +++ b/tools/lab/cold-start @@ -0,0 +1,38 @@ +#!/bin/bash +# One cold start of the classroom, end to end: a fresh container, a fresh +# user profile, the desktop installed the way the package would install it, +# and the session brought up from nothing. Repeat it to see whether the +# room comes back the same way every time. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LAB="$ROOT/tools/lab/lab" +ROUNDS="${1:-1}" + +for round in $(seq 1 "$ROUNDS"); do + started=$(date +%s) + "$LAB" up >/dev/null + docker exec classroom-v2-lab /srv/classroom/tools/lab/install-desktop >/dev/null + docker exec classroom-v2-lab sh -c ' + install -Dm644 /srv/classroom/config/chrome/policies/managed/ekklesia-classroom.json \ + /etc/opt/chrome/policies/managed/ekklesia-classroom.json + install -Dm755 /srv/classroom/scripts/classroom-audio /usr/local/bin/classroom-audio + install -Dm755 /srv/classroom/scripts/classroom-theme /usr/local/bin/classroom-theme + install -Dm644 /srv/classroom/lib/classroom_audio.py \ + /usr/local/lib/ekklesia-classroom/classroom_audio.py + install -Dm644 /srv/classroom/lib/classroom_config.py \ + /usr/local/lib/ekklesia-classroom/classroom_config.py + install -Dm644 /srv/classroom/systemd/user/ekklesia-classroom-audio.service \ + /etc/systemd/user/ekklesia-classroom-audio.service' + "$LAB" session >/dev/null + sleep 6 + + panel=$(docker exec classroom-v2-lab pgrep -c -x plasmashell || echo 0) + kwin=$(docker exec classroom-v2-lab pgrep -c -x kwin_wayland || echo 0) + applets=$(docker exec classroom-v2-lab sh -c \ + 'grep -c "^plugin=" /home/classroom/.config/plasma-org.kde.plasma.desktop-appletsrc 2>/dev/null' \ + || echo 0) + + printf 'round %s: %ss plasmashell=%s kwin=%s applets=%s\n' \ + "$round" "$(( $(date +%s) - started ))" "$panel" "$kwin" "$applets" +done