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]>
460 lines
21 KiB
Markdown
460 lines
21 KiB
Markdown
# 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.
|
||
|
||
```
|
||
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
|
||
```
|
||
|
||
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 `SIGKILL`s 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.
|
||
|
||
### 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
|
||
|
||
Deleted on this branch, 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 |
|
||
| **total** | **1 695** | |
|
||
|
||
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).
|
||
|
||
**Still present, and still sway-shaped.** `classroom-window`,
|
||
`classroom_sway.py`, `classroom_icons.py`, `classroom_style.py` and
|
||
`classroom_optical.py` are all reachable only from the DVD player and the
|
||
MQTT notifier, and they were deliberately not rewritten — see *the DVD
|
||
player* under unresolved risks. Another ~590 lines go when that is done.
|
||
|
||
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.
|
||
|
||
## 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.
|