feat: an external-only display mode, chosen rather than inferred

The women's Bible study laptop is a fixed appliance: AC power, battery
out, lid shut, one television on HDMI. Its own panel should be off.

DISPLAY_MODE=external-only says so, and it is a setting rather than
something inferred from having a lid, because another laptop may want its
own screen or both. Nothing reads DEVICE_TYPE to decide it.

The built-in panel is identified by kscreen's output TYPE — 7 is Panel —
rather than by connector name. Matching "eDP-1" would be matching one
machine's spelling: eDP, LVDS and DSI are all internal, numbered
differently per driver and renamed between kernel versions. The names are
kept only as a fallback for a driver that reports no type, which some
virtual devices do.

One rule overrides the setting: never leave the machine with no screen. A
laptop asked for external-only whose television is off keeps its own
panel and says so in the journal. A machine nobody can see is one nobody
can fix standing in front of it, and it corrects itself the next time
this runs with the set on. Only the outputs that are not already right
are touched, so a television power cycle is not a stream of mode sets.

Applied at login from an autostart entry named to sort before the scale
one, so the scale lands on the displays this leaves enabled.

The lid itself needed no new code — logind ignores every lid event,
PowerDevil's lidAction is 0 in all three profiles with
triggerLidActionWhenExternalMonitorPresent=false, the profiles are
immutable and the classroom account cannot reach suspend through polkit.
That was all already true for towers. What was missing was a test saying
so, and there is one now, because it is the property the laptop depends
on and nothing would have noticed it regressing.

KNOWN LIMITATION, and it is written into docs/testing.md rather than left
to be discovered: the mode is applied at login, not continuously. If KWin
re-enables the internal panel when the television is power cycled, it
stays enabled until the next login. The lab has no lid and no HDMI
hotplug, so this cannot be settled here; the physical procedure says to
check it specifically, because the answer decides whether the follow-up
needs an event-driven applier like the audio policy's.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-20 21:17:06 -05:00
co-authored by Claude Opus 5
parent 886f8a70d2
commit 52301084a5
7 changed files with 472 additions and 1 deletions
+76
View File
@@ -230,6 +230,82 @@ 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.
## Device features
Every optional application is its own setting in device.conf, and
`DEVICE_TYPE` decides none of them. A laptop can take children's church
and a tower can be a Bible study.
```
DEVICE_TYPE=desktop|laptop what the hardware IS
WONDERINK_ENABLED=true|false
DVD_ENABLED=true|false a drive is still required as well
PARENT_CALL_ENABLED=true|false
NETWORK_PREFERENCE=wired|wifi
DISPLAY_MODE=all|external-only
```
All default to the classroom's existing behaviour, so an upgrade changes
nothing until somebody edits the file. `classroom-desktop-layout` writes
the answers into the packaged layout; the bar is rebuilt from it at the
next login.
- [ ] `tests/test-classroom-desktop-layout.sh` passes (also run by the build)
- [ ] After changing a setting, `classroom-desktop-layout` then a
logout/login shows the new bar
- [ ] A disabled application leaves no gap: the remaining launchers close up
- [ ] The launcher order is the same on two machines with the same
features enabled
- [ ] Dragging a launcher does not move it
- [ ] Right-clicking a launcher offers no Pin, Unpin, Configure or panel
edit entry
- [ ] The order survives launching and closing applications, logout/login
and a reboot
- [ ] The bar is right at 1080p/100% and 4K/200%
## The Bible study laptop
A laptop installed as a fixed appliance: AC power, battery out, lid shut,
one television on HDMI, Wi-Fi.
```
DEVICE_TYPE=laptop
DISPLAY_MODE=external-only
NETWORK_PREFERENCE=wifi
WONDERINK_ENABLED=false
PARENT_CALL_ENABLED=false
DVD_ENABLED=false
```
Nothing about `DEVICE_TYPE=laptop` turns those applications off; they are
off because those lines say so.
### Physical verification — a real lid and a real television
The lab has no lid and no HDMI hotplug, so none of this is proven by a
passing build. Do it once at the machine:
1. With the lid **open** and the television on, check
`classroom-display-mode` reports the panel switched off and the
television on, and that only the television is lit.
2. Close the lid. The machine must keep running: the television stays on,
audio keeps playing, SSH stays up. `systemctl is-system-running`
still answers, and `loginctl` shows the session active.
3. Leave it closed for ten minutes. It must not suspend, hibernate, log
out or lock. `journalctl -b | grep -i -E "suspend|hibernate|lid"`
should show nothing acting.
4. With the lid still closed, switch the television **off** and on again.
The picture must come back. **Known limitation of 0.2.4:** the display
mode is applied at login, not continuously, so if KWin re-enables the
internal panel on that power cycle it stays enabled until the next
login. Check whether it does, and report it — that decides whether the
follow-up needs an event-driven applier like the audio policy's.
5. Unplug and replug HDMI, lid closed. Same check.
6. Reboot with the lid closed and the television on. It must come back to
the classroom desktop on the television.
7. Confirm Wi-Fi reconnects on its own, with nobody typing a password.
8. Confirm the HDMI audio policy still works (see Audio, above).
## Messaging
- [ ] With `MQTT_ENABLED=false`, `journalctl -u ekklesia-classroom-mqtt`