diff --git a/config/sddm/ekklesia-classroom.conf b/config/sddm/ekklesia-classroom.conf new file mode 100644 index 0000000..1516fde --- /dev/null +++ b/config/sddm/ekklesia-classroom.conf @@ -0,0 +1,24 @@ +# The classroom logs itself in, and logs itself back in. +# +# The old session started from ~/.bash_profile on an autologin getty, which +# has one bad property: when the session ends for any reason, the teacher is +# looking at a shell prompt on a machine that is supposed to have no +# terminal. A display manager ends that. Relogin means the room comes +# straight back to the classroom instead of to anything else. +# +# SDDM is never seen. There is one user, no password, and no chooser. + +[Autologin] +User=classroom +Session=plasma +Relogin=true + +[General] +DisplayServer=wayland +# No user switching, no second session. +InputMethod= + +[Users] +MaximumUid=1000 +MinimumUid=1000 +HideShells=/usr/sbin/nologin,/bin/false diff --git a/docs/screenshots/net-online.png b/docs/screenshots/net-online.png new file mode 100644 index 0000000..5110071 Binary files /dev/null and b/docs/screenshots/net-online.png differ diff --git a/docs/screenshots/v2-dark-bar.png b/docs/screenshots/v2-dark-bar.png index 67e97bb..8b354d1 100644 Binary files a/docs/screenshots/v2-dark-bar.png and b/docs/screenshots/v2-dark-bar.png differ diff --git a/docs/screenshots/v2-idle-desktop.png b/docs/screenshots/v2-idle-desktop.png index 0935b5e..8d2c7a2 100644 Binary files a/docs/screenshots/v2-idle-desktop.png and b/docs/screenshots/v2-idle-desktop.png differ diff --git a/docs/screenshots/v2-scale-125.png b/docs/screenshots/v2-scale-125.png new file mode 100644 index 0000000..a1e331b Binary files /dev/null and b/docs/screenshots/v2-scale-125.png differ diff --git a/packaging/debian/control.template b/packaging/debian/control.template index 4205f98..d17946f 100644 --- a/packaging/debian/control.template +++ b/packaging/debian/control.template @@ -4,7 +4,16 @@ Section: misc Priority: optional Architecture: amd64 Maintainer: Ekklesia -Depends: sway, swaybg, waybar, mako-notifier, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, cups, cups-client, cups-filters, foomatic-db-compressed-ppds, python3-gi, python3-gi-cairo, gir1.2-gtk-3.0, gir1.2-gtklayershell-0.1, adwaita-icon-theme, libnotify-bin, curl, gnupg, ca-certificates, python3-paho-mqtt +Depends: plasma-desktop, plasma-workspace, kwin-wayland, plasma-nm, plasma-pa, sddm, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, pulseaudio-utils, cups, cups-client, cups-filters, foomatic-db-compressed-ppds, libnotify-bin, curl, gnupg, ca-certificates, python3-paho-mqtt Recommends: vlc +Conflicts: plasma-welcome, plasma-discover, kdeconnect, kup-backup Description: Ekklesia managed classroom environment - A minimal Wayland classroom environment for Ekklesia-managed church computers. + A classroom appliance built on Debian's own Plasma desktop. The room's + panel, window behaviour, notifications, palette and lockdown are + configuration for that desktop rather than a shell of our own; what is + left here is the church's own subject matter — Wonder Ink, the browser, + the DVD player, the printer, the audio policy and the updater. + . + The Conflicts line is deliberate. A welcome tour, a software centre and + a phone pairing daemon are all things plasma-desktop recommends and a + room a teacher walks into should not have. diff --git a/scripts/classroom-scale b/scripts/classroom-scale index 971bd66..f82d3d5 100755 --- a/scripts/classroom-scale +++ b/scripts/classroom-scale @@ -1,20 +1,14 @@ #!/usr/bin/env python3 -"""Apply the room's UI scale to the whole classroom session. +"""Make everything in the room bigger or smaller, once. -UI_SCALE is a property of the television, not of one program, so it is -applied where it reaches everything: sway's output scale. Chrome's tabs -and omnibox, its menus, VLC, and the classroom's own dock and -notifications are then all measured in the same scaled logical pixels. - -The display mode is untouched — the panel stays at its native -resolution. Only the logical coordinate space shrinks, which is what -makes everything drawn in it larger. - -Run from the sway config before anything else starts, so surfaces come -up at the right size rather than being resized underneath themselves. +UI_SCALE is the display's scale factor, so it scales the browser, VLC, the +bar and every dialog together rather than each of them separately. Plasma +owns display configuration, so this asks Plasma's own tool to do it — which +is the whole of what replaced the compositor-specific version. """ +import json import os import subprocess import sys @@ -28,27 +22,50 @@ sys.path.insert( from classroom_config import read_device_config, ui_scale # noqa: E402 -def main(): - scale = ui_scale(read_device_config()) +def outputs(): + """Every connected display, by name. + The JSON form rather than the human one: kscreen-doctor's plain output + is full of terminal colour codes, and a regular expression over it + silently matched nothing. + """ try: - result = subprocess.run( - ["swaymsg", "output", "*", "scale", f"{scale:g}"], - capture_output=True, - text=True, - ) - except OSError as error: + listing = subprocess.run( + ["kscreen-doctor", "-j"], + capture_output=True, text=True, timeout=20, check=False, + ).stdout + found = json.loads(listing or "{}").get("outputs") or [] + except (OSError, subprocess.SubprocessError, ValueError) as error: print(f"classroom-scale: {error}", file=sys.stderr) - return 1 + return [] - if result.returncode != 0: - print( - f"classroom-scale: {result.stderr.strip() or 'sway refused the scale'}", - file=sys.stderr, + return [ + output["name"] for output in found + if output.get("name") and output.get("connected", True) + ] + + +def main(): + # classroom_config clamps a nonsense or out-of-range value rather than + # obeying it, so a mistyped UI_SCALE cannot leave a room unusable. + scale = ui_scale(read_device_config()) + found = outputs() + + if not found: + print("classroom-scale: no outputs", file=sys.stderr) + return 0 + + for output in found: + result = subprocess.run( + ["kscreen-doctor", f"output.{output}.scale.{scale}"], + capture_output=True, text=True, timeout=20, check=False, ) - return 1 - print(f"classroom-scale: session scale {scale:g}") + if result.returncode == 0: + print(f"classroom-scale: {output} scale {scale}") + else: + print(f"classroom-scale: {output}: {result.stderr.strip()}", + file=sys.stderr) return 0 diff --git a/tools/build-release.sh b/tools/build-release.sh index f28db58..b35a27c 100755 --- a/tools/build-release.sh +++ b/tools/build-release.sh @@ -79,31 +79,15 @@ install -m755 \ "$ROOT/packaging/debian/postinst" \ "$STAGE/DEBIAN/postinst" -# Classroom UI configuration. -install -Dm644 \ - "$ROOT/config/sway/config" \ - "$STAGE/etc/ekklesia-classroom/sway/config" -install -Dm644 \ - "$ROOT/config/waybar/config.jsonc" \ - "$STAGE/etc/ekklesia-classroom/waybar/config.jsonc" -install -Dm644 \ - "$ROOT/config/waybar/style.css" \ - "$STAGE/etc/ekklesia-classroom/waybar/style.css" # Classroom commands. install -Dm755 \ "$ROOT/scripts/classroom-browser" \ "$STAGE/usr/local/bin/classroom-browser" -install -Dm755 \ - "$ROOT/scripts/classroom-chrome-windows" \ - "$STAGE/usr/local/bin/classroom-chrome-windows" -install -Dm755 \ - "$ROOT/scripts/classroom-window" \ - "$STAGE/usr/local/bin/classroom-window" install -Dm755 \ "$ROOT/scripts/classroom-dvd" \ @@ -117,21 +101,12 @@ install -Dm755 \ "$ROOT/scripts/classroom-dvd-css-setup" \ "$STAGE/usr/local/bin/classroom-dvd-css-setup" -install -Dm755 \ - "$ROOT/scripts/classroom-notify" \ - "$STAGE/usr/local/bin/classroom-notify" -install -Dm755 \ - "$ROOT/scripts/classroom-notify-test" \ - "$STAGE/usr/local/bin/classroom-notify-test" install -Dm755 \ "$ROOT/scripts/classroom-mqtt" \ "$STAGE/usr/local/bin/classroom-mqtt" -install -Dm755 \ - "$ROOT/scripts/classroom-notifications" \ - "$STAGE/usr/local/bin/classroom-notifications" install -Dm755 \ "$ROOT/scripts/classroom-scale" \ @@ -162,47 +137,24 @@ install -Dm644 \ "$ROOT/lib/classroom_chrome.py" \ "$STAGE/usr/local/lib/ekklesia-classroom/classroom_chrome.py" -install -Dm644 \ - "$ROOT/lib/classroom_icons.py" \ - "$STAGE/usr/local/lib/ekklesia-classroom/classroom_icons.py" install -Dm644 \ "$ROOT/lib/classroom_mqtt.py" \ "$STAGE/usr/local/lib/ekklesia-classroom/classroom_mqtt.py" -install -Dm644 \ - "$ROOT/lib/classroom_style.py" \ - "$STAGE/usr/local/lib/ekklesia-classroom/classroom_style.py" -install -Dm644 \ - "$ROOT/lib/classroom_sway.py" \ - "$STAGE/usr/local/lib/ekklesia-classroom/classroom_sway.py" install -Dm644 \ "$ROOT/lib/classroom_vlc.py" \ "$STAGE/usr/local/lib/ekklesia-classroom/classroom_vlc.py" -install -Dm644 \ - "$ROOT/lib/classroom_optical.py" \ - "$STAGE/usr/local/lib/ekklesia-classroom/classroom_optical.py" install -Dm644 \ - "$ROOT/config/wallpapers/classroom-default.png" \ - "$STAGE/usr/share/backgrounds/ekklesia/classroom-default.png" + "$ROOT/config/wallpapers/classroom-calm.png" \ + "$STAGE/usr/share/backgrounds/ekklesia/classroom-calm.png" -install -Dm755 \ - "$ROOT/scripts/classroom-dock" \ - "$STAGE/usr/local/bin/classroom-dock" -# Stylesheet and notification templates. Both carry measurement tokens -# that are resolved from UI_SCALE when the session starts. -install -Dm644 \ - "$ROOT/config/dock/style.css.in" \ - "$STAGE/etc/ekklesia-classroom/dock/style.css.in" -install -Dm644 \ - "$ROOT/config/mako/config.in" \ - "$STAGE/etc/ekklesia-classroom/mako/config.in" # Chrome enterprise policy: suppress first-run and sign-in nags, and pin # Home to Wonder Ink. Nothing here restricts where teachers can browse. @@ -220,8 +172,8 @@ install -Dm755 \ # System configuration. install -Dm644 \ - "$ROOT/systemd/autologin/autologin.conf" \ - "$STAGE/etc/systemd/system/getty@tty1.service.d/autologin.conf" + "$ROOT/config/sddm/ekklesia-classroom.conf" \ + "$STAGE/etc/sddm.conf.d/ekklesia-classroom.conf" install -Dm644 \ "$ROOT/systemd/updater/ekklesia-classroom-update.service" \ @@ -243,21 +195,93 @@ install -Dm644 \ "$ROOT/systemd/mqtt/ekklesia-classroom-mqtt.service" \ "$STAGE/etc/systemd/system/ekklesia-classroom-mqtt.service" -install -Dm644 \ - "$ROOT/config/icons/dvd.svg" \ - "$STAGE/etc/ekklesia-classroom/icons/dvd.svg" + + + + +# The classroom desktop. +# +# Not a shell, and not a daemon: a Plasma session described in files Plasma +# already reads. The panel, the window behaviour, the notifications, the +# lockdown and the palette are all in here, which is why there is no longer +# a dock, a window helper, a notification wrapper or a compositor config in +# the list above. + +for setting in kdeglobals kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \ + krunnerrc plasmanotifyrc; do + install -Dm644 \ + "$ROOT/config/plasma/xdg/${setting}" \ + "$STAGE/etc/xdg/${setting}" +done + +for scheme in EkklesiaDark EkklesiaLight; do + install -Dm644 \ + "$ROOT/config/plasma/colors/${scheme}.colors" \ + "$STAGE/usr/share/color-schemes/${scheme}.colors" +done + +LOOK="$STAGE/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop" install -Dm644 \ - "$ROOT/config/icons/live.svg" \ - "$STAGE/etc/ekklesia-classroom/icons/live.svg" + "$ROOT/config/plasma/lookandfeel/metadata.json" \ + "$LOOK/metadata.json" install -Dm644 \ - "$ROOT/config/icons/live-off.svg" \ - "$STAGE/etc/ekklesia-classroom/icons/live-off.svg" + "$ROOT/config/plasma/lookandfeel/contents/defaults" \ + "$LOOK/contents/defaults" install -Dm644 \ - "$ROOT/config/icons/browser.svg" \ - "$STAGE/etc/ekklesia-classroom/icons/browser.svg" + "$ROOT/config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js" \ + "$LOOK/contents/layouts/org.kde.plasma.desktop-layout.js" + +for entry in "$ROOT"/config/plasma/applications/*.desktop; do + install -Dm644 "$entry" \ + "$STAGE/usr/share/applications/$(basename "$entry")" +done + +# Shadows Chrome's own entry. XDG_DATA_DIRS searches /usr/local/share first, +# so the panel matches a Chrome window to this launcher instead of to +# Chrome's, and the room gets one button rather than two. +for entry in "$ROOT"/config/plasma/applications-local/*.desktop; do + install -Dm644 "$entry" \ + "$STAGE/usr/local/share/applications/$(basename "$entry")" +done + +install -Dm644 \ + "$ROOT/config/plasma/solid/ekklesia-dvd.desktop" \ + "$STAGE/usr/share/solid/actions/ekklesia-dvd.desktop" + +for icon in ekklesia-wonderink ekklesia-parent-call; do + install -Dm644 \ + "$ROOT/config/icons/${icon}.svg" \ + "$STAGE/usr/share/icons/hicolor/scalable/apps/${icon}.svg" +done + +# The panel is the only way a teacher has of getting anywhere. Plasma gives +# up restarting it after three crashes in a minute; this does not. +install -Dm644 \ + "$ROOT/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf" \ + "$STAGE/etc/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf" + +install -Dm644 \ + "$ROOT/systemd/user/ekklesia-classroom-audio.service" \ + "$STAGE/etc/systemd/user/ekklesia-classroom-audio.service" + +install -Dm755 \ + "$ROOT/scripts/classroom-wonderink" \ + "$STAGE/usr/local/bin/classroom-wonderink" + +install -Dm755 \ + "$ROOT/scripts/classroom-audio" \ + "$STAGE/usr/local/bin/classroom-audio" + +install -Dm755 \ + "$ROOT/scripts/classroom-theme" \ + "$STAGE/usr/local/bin/classroom-theme" + +install -Dm644 \ + "$ROOT/lib/classroom_audio.py" \ + "$STAGE/usr/local/lib/ekklesia-classroom/classroom_audio.py" # DVD mode control bar. for icon in media-play media-pause chapter-prev chapter-next \ diff --git a/tools/lab/Dockerfile.plasma b/tools/lab/Dockerfile.plasma index 1f7ff03..9306ff4 100644 --- a/tools/lab/Dockerfile.plasma +++ b/tools/lab/Dockerfile.plasma @@ -76,3 +76,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # pointer to be clicked with. RUN apt-get update && apt-get install -y --no-install-recommends seatd \ && rm -rf /var/lib/apt/lists/* + +# What a classroom would not install. +# +# plasma-desktop recommends a welcome tour, a software centre, a phone +# pairing daemon and a backup tool. None of them belong in a room a teacher +# walks into, and a classroom package would simply not pull them in; the lab +# removes them so it is testing the same desktop. +RUN DEBIAN_FRONTEND=noninteractive apt-get purge -y \ + plasma-welcome plasma-discover plasma-discover-common \ + plasma-discover-backend-fwupd kdeconnect kup-backup \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/*