#!/bin/bash # Does every desktop show the classroom's wallpaper — including one Plasma # makes by itself after the layout has run? # # Tiny Pebbles came through the 0.2.2 upgrade with its desktop numbered 10 # and nothing configured on it, and showed Plasma's own purple wallpaper. # The layout only reaches the desktops that exist while it runs, and a # room whose updater reboots it at three in the morning with the # television off has none then. The lab cannot switch a television on, so # it makes Plasma create a desktop the other way it does, through a new # activity: the same code in the shell, and measured to give the same # bare containment. # # tools/lab/wallpaper-check against the running lab session # # Each answer is read off the screen, not out of a file: a configuration # can say anything, and the question is what the room sees. The sky at the # top of the classroom's picture is pale blue; Plasma's is purple. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" LAB="$ROOT/tools/lab/lab" NAME="${LAB_NAME:-classroom-v2-lab}" APPLETS=/home/classroom/.config/plasma-org.kde.plasma.desktop-appletsrc PASS=0 FAIL=0 check() { if [[ "$2" == "$3" ]]; then printf ' \033[32mPASS\033[0m %-58s %s\n' "$1" "$3" PASS=$((PASS + 1)) else printf ' \033[31mFAIL\033[0m %-58s got [%s] want [%s]\n' "$1" "$3" "$2" FAIL=$((FAIL + 1)) fi } section() { printf '\n%s\n' "$1"; } in_lab() { docker exec "$NAME" bash -c "$1" 2>/dev/null; } # The same, with this script's input handed through — for a program on a # here-document. Without -i docker gives it nothing to read, and it runs # on nothing and says nothing. in_lab_fed() { docker exec -i "$NAME" bash -c "$1"; } user() { "$LAB" run "$@" 2>/dev/null; } activities() { user busctl --user call org.kde.ActivityManager /ActivityManager/Activities \ org.kde.ActivityManager.Activities "$@" } # What is on the screen at the top middle, where the classroom's picture # is sky and Plasma's is purple: "classroom", "plasma", or the colour. screen() { "$LAB" outer grim -t ppm - 2>/dev/null | python3 -c ' import sys data = sys.stdin.buffer.read() fields, at = [], 0 while len(fields) < 4: while data[at:at + 1].isspace(): at += 1 end = at while not data[end:end + 1].isspace(): end += 1 fields.append(data[at:end]) at = end width, height = int(fields[1]), int(fields[2]) pixels = data[at + 1:] total = [0, 0, 0] count = 0 for y in range(20, 140, 4): for x in range(width * 2 // 5, width * 3 // 5, 8): offset = (y * width + x) * 3 for channel in range(3): total[channel] += pixels[offset + channel] count += 1 r, g, b = (value / count for value in total) if b > r + 25 and g > r + 10: print("classroom") elif r > g + 25 and b > g + 25: print("plasma") else: print(f"rgb({r:.0f},{g:.0f},{b:.0f})") ' } # The shell writes its file a few seconds after a change, not at once. settle() { sleep "${1:-35}"; } # The shell's file, read as groups rather than grepped, so nothing here # can pick a line out of the wrong containment. # # applets panels each panel's number, its applets' numbers and plugins # and their order. Not the panel's text: the shell adds # keys of its own to applets as soon as it runs (popup # sizes, the volume's migration), and that is Plasma, # not the repair. tests/ checks the repair's own # output byte for byte. # applets desktops the desktop containments as "id:image" or "id:none", # found by plugin and never by number applets() { in_lab_fed "python3 - $1 $APPLETS" <<'PY' import re, sys mode, path = sys.argv[1], sys.argv[2] groups, name = {}, None for line in open(path).read().splitlines(): if line.startswith("[") and line.endswith("]"): name = line groups[name] = [] elif name and line: groups[name].append(line) def top(cid): return groups.get(f"[Containments][{cid}]", []) ids = [m.group(1) for g in groups for m in [re.match(r"^\[Containments\]\[(\d+)\]$", g)] if m] if mode == "panels": for c in ids: if "plugin=org.kde.panel" not in top(c): continue applets = sorted( (int(m.group(1)), line[len("plugin=org.kde.plasma."):]) for g in groups for m in [re.match(r"^\[Containments\]\[%s\]\[Applets\]\[(\d+)\]$" % c, g)] if m for line in groups[g] if line.startswith("plugin=") ) order = [l[len("AppletOrder="):] for l in groups.get(f"[Containments][{c}][General]", []) if l.startswith("AppletOrder=")] print(f"panel {c}:", " ".join(f"{n}={name}" for n, name in applets), "order", *order) else: image = "Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png" out = [] for c in ids: if {"plugin=org.kde.plasma.folder", "plugin=org.kde.desktopcontainment"} & set(top(c)): wallpaper = groups.get(f"[Containments][{c}][Wallpaper][org.kde.image][General]", []) out.append(f"{c}:{'image' if image in wallpaper else 'none'}") print(" ".join(out)) PY } panel_groups() { applets panels; } desktops() { applets desktops; } last_repair() { user journalctl --user -u plasma-plasmashell.service --no-pager -o cat \ | grep '^classroom-desktop-wallpaper:' | tail -1 } restart_shell() { user systemctl --user restart plasma-plasmashell.service for _ in $(seq 1 30); do in_lab 'pgrep -x plasmashell >/dev/null' && break sleep 1 done sleep 10 } section "the package" check "the look and feel's default names a package" EkklesiaClassroom \ "$(in_lab 'sed -n "/^\[Wallpaper\]/,/^\[/s/^Image=//p" /usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults')" check "which is installed, and is the classroom's picture" /usr/share/backgrounds/ekklesia/classroom-calm.png \ "$(in_lab 'readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png')" check "the repair runs before every shell start" 1 \ "$(in_lab 'grep -c "^ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper" /etc/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf')" section "the session as it came up" check "the room shows the classroom's picture" classroom "$(screen)" original=$(activities CurrentActivity | cut -d'"' -f2) section "a desktop Plasma makes by itself (Tiny Pebbles' television)" panels_before=$(panel_groups) late=$(activities AddActivity s "Late screen" | cut -d'"' -f2) activities SetCurrentActivity s "$late" >/dev/null settle now=$(desktops) check "Plasma made a second desktop" 2 "$(wc -w <<< "$now")" check "and wrote no wallpaper into it, as on Tiny Pebbles" 1 "$(grep -o ':none' <<< "$now" | wc -l)" check "the room still shows the classroom's picture" classroom "$(screen)" section "the shell restarted, without a login" restart_shell check "the repair wrote it in" "classroom-desktop-wallpaper: the wallpaper is now configured on desktop $(tr ' ' '\n' <<< "$now" | cut -d: -f1 | paste -sd, | sed 's/,/, /g')" \ "$(last_repair)" check "every desktop now names the picture" 0 "$(desktops | grep -o ':none' | wc -l)" check "the panel keeps its numbers, applets and order" "$panels_before" "$(panel_groups)" check "the room shows the classroom's picture" classroom "$(screen)" restart_shell check "a second restart finds nothing to do" \ "classroom-desktop-wallpaper: desktop $(desktops | tr ' ' '\n' | cut -d: -f1 | paste -sd, | sed 's/,/, /g'): the classroom wallpaper already" \ "$(last_repair)" check "and still does" "$panels_before" "$(panel_groups)" activities SetCurrentActivity s "$original" >/dev/null activities RemoveActivity s "$late" >/dev/null sleep 3 section "Tiny Pebbles' own file: desktop 10, panel 1, no wallpaper" user systemctl --user stop plasma-plasmashell.service in_lab_fed "python3 - '$original' $APPLETS" <<'PY' import sys activity, path = sys.argv[1], sys.argv[2] applets = ["quicklaunch", "panelspacer", "icontasks", "panelspacer", "networkmanagement", "volume", "notifications", "digitalclock"] out = ["[Containments][1]", "activityId=", "formfactor=2", "immutability=1", "lastScreen=0", "location=4", "plugin=org.kde.panel", "wallpaperplugin=org.kde.image", ""] for number, applet in enumerate(applets, start=2): out += [f"[Containments][1][Applets][{number}]", "immutability=1", f"plugin=org.kde.plasma.{applet}", ""] out += ["[Containments][1][Applets][4][Configuration][General]", "launchers=applications:ekklesia-wonderink.desktop,applications:google-chrome.desktop,applications:ekklesia-dvd.desktop", "sortingStrategy=1", "", "[Containments][1][General]", "AppletOrder=2;3;4;5;6;7;8;9", "", "[Containments][10]", f"activityId={activity}", "formfactor=0", "immutability=1", "lastScreen=0", "location=0", "plugin=org.kde.plasma.folder", "wallpaperplugin=org.kde.image", "", "[Containments][10][General]", 'positions={"1920x1080":[]}', ""] open(path, "w").write("\n".join(out)) PY in_lab "chown classroom:classroom $APPLETS" panels_before=$(panel_groups) user systemctl --user start plasma-plasmashell.service sleep 12 check "the repair found desktop 10 and wrote it" \ "classroom-desktop-wallpaper: the wallpaper is now configured on desktop 10" "$(last_repair)" check "desktop 10 names the picture" "10:image" "$(desktops)" check "the panel is still containment 1, applets 2 to 9, in order" "$panels_before" "$(panel_groups)" check "the room shows the classroom's picture" classroom "$(screen)" section "and at the next login" "$LAB" plasma-down >/dev/null "$LAB" plasma-up >/dev/null settle 10 check "the reset left the repair nothing to do" \ "classroom-desktop-wallpaper: no desktop configuration yet; the layout writes the wallpaper" \ "$(last_repair)" check "the layout rebuilt one desktop with the picture" 1 "$(desktops | grep -o ':image' | wc -l)" check "the room shows the classroom's picture" classroom "$(screen)" printf '\n%s passed, %s failed\n' "$PASS" "$FAIL" [[ "$FAIL" -eq 0 ]]