test: the wallpaper repair, on Tiny Pebbles' own file, in the lab and on a real install
Unit: Tiny Pebbles' file (desktop 10, panel 1), a healthy River Kids file left byte for byte, a late desktop beside a good one, idempotence, the refusal under a running shell. Lab: Plasma makes a bare desktop itself through a new activity, and the screen is read, not the file. Container gate: the installed package's fallback is the package, and its repair fixes the room's file and leaves the panel alone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7b4ac39cd2
commit
9fc86f9d4c
Executable
+408
@@ -0,0 +1,408 @@
|
||||
#!/bin/bash
|
||||
# Every desktop shows the classroom's wallpaper, whatever it is numbered.
|
||||
#
|
||||
# Tiny Pebbles came through the 0.2.2 upgrade with its desktop as
|
||||
# containment 10 and no wallpaper configured on it, and showed Plasma's own
|
||||
# purple one. These run the real repair against that file, against a
|
||||
# machine that was already right, and against the shapes in between, and
|
||||
# then check that every place naming the wallpaper names the same one.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
python3 - "$ROOT" <<'PY'
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
loader = SourceFileLoader("wallpaper", str(root / "scripts/classroom-desktop-wallpaper"))
|
||||
spec = importlib.util.spec_from_loader("wallpaper", loader)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
loader.exec_module(module)
|
||||
|
||||
passed = failed = 0
|
||||
|
||||
|
||||
def check(label, condition, detail=""):
|
||||
global passed, failed
|
||||
if condition:
|
||||
print(f" ok {label}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" FAIL {label}")
|
||||
if detail:
|
||||
print(f" {detail}")
|
||||
failed += 1
|
||||
|
||||
|
||||
IMAGE = "Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png"
|
||||
|
||||
|
||||
def group_texts(text):
|
||||
"""{group name: its lines}, for saying what did and did not change."""
|
||||
lines = text.splitlines(keepends=True)
|
||||
return {
|
||||
name: "".join(lines[header:end]).rstrip("\n")
|
||||
for name, header, end in module.groups(lines)
|
||||
}
|
||||
|
||||
|
||||
def containment_ids(text):
|
||||
return sorted(re.findall(r"^\[Containments\]\[(\d+)\]$", text, re.M))
|
||||
|
||||
|
||||
# A panel built with no screen present takes id 1 and its applets 2 to 9,
|
||||
# which is how Tiny Pebbles' desktop came to be 10. Its containment is as
|
||||
# it was read off the machine.
|
||||
PANEL_1 = """[ActionPlugins][0]
|
||||
MiddleButton;NoModifier=org.kde.paste
|
||||
RightButton;NoModifier=org.kde.contextmenu
|
||||
|
||||
[ActionPlugins][1]
|
||||
RightButton;NoModifier=org.kde.contextmenu
|
||||
|
||||
[Containments][1]
|
||||
activityId=
|
||||
formfactor=2
|
||||
immutability=1
|
||||
lastScreen=0
|
||||
location=4
|
||||
plugin=org.kde.panel
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][1][Applets][2]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.quicklaunch
|
||||
|
||||
[Containments][1][Applets][2][Configuration][General]
|
||||
launcherUrls=file:///usr/share/applications/ekklesia-parent-call.desktop
|
||||
|
||||
[Containments][1][Applets][3]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.panelspacer
|
||||
|
||||
[Containments][1][Applets][4]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.icontasks
|
||||
|
||||
[Containments][1][Applets][4][Configuration][General]
|
||||
launchers=applications:ekklesia-wonderink.desktop,applications:google-chrome.desktop,applications:ekklesia-dvd.desktop
|
||||
sortingStrategy=1
|
||||
|
||||
[Containments][1][Applets][5]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.panelspacer
|
||||
|
||||
[Containments][1][Applets][6]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.networkmanagement
|
||||
|
||||
[Containments][1][Applets][7]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.volume
|
||||
|
||||
[Containments][1][Applets][8]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.notifications
|
||||
|
||||
[Containments][1][Applets][9]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.digitalclock
|
||||
|
||||
[Containments][1][General]
|
||||
AppletOrder=2;3;4;5;6;7;8;9
|
||||
"""
|
||||
|
||||
TINY_PEBBLES = PANEL_1 + """
|
||||
[Containments][10]
|
||||
activityId=62d3d55d-bd31-4fc8-9a7b-a5b9b2b99b60
|
||||
formfactor=0
|
||||
immutability=1
|
||||
lastScreen=0
|
||||
location=0
|
||||
plugin=org.kde.plasma.folder
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][10][General]
|
||||
positions={"1920x1080":[]}
|
||||
|
||||
[ScreenMapping]
|
||||
itemsOnDisabledScreens=
|
||||
"""
|
||||
|
||||
# The healthy machine, as the layout builds it with a screen present and
|
||||
# as the lab's own session wrote it: desktop 1, panel 2, applets 3 to 10.
|
||||
# The clock is applet 10 — the number that is Tiny Pebbles' desktop.
|
||||
RIVER_KIDS = """[ActionPlugins][0]
|
||||
MiddleButton;NoModifier=org.kde.paste
|
||||
RightButton;NoModifier=org.kde.contextmenu
|
||||
|
||||
[Containments][1]
|
||||
ItemGeometries-1920x1080=
|
||||
ItemGeometriesHorizontal=
|
||||
activityId=a9db02f6-926f-4650-b05f-de3a9f0b73fe
|
||||
formfactor=0
|
||||
immutability=1
|
||||
lastScreen=0
|
||||
location=0
|
||||
plugin=org.kde.plasma.folder
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][1][Wallpaper][org.kde.image][General]
|
||||
FillMode=2
|
||||
Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png
|
||||
SlidePaths=
|
||||
|
||||
[Containments][2]
|
||||
activityId=
|
||||
formfactor=2
|
||||
immutability=1
|
||||
lastScreen=0
|
||||
location=4
|
||||
plugin=org.kde.panel
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][2][Applets][10]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.digitalclock
|
||||
|
||||
[Containments][2][Applets][10][Configuration][Appearance]
|
||||
showDate=true
|
||||
|
||||
[Containments][2][Applets][3]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.quicklaunch
|
||||
|
||||
[Containments][2][Applets][5]
|
||||
immutability=1
|
||||
plugin=org.kde.plasma.icontasks
|
||||
|
||||
[Containments][2][General]
|
||||
AppletOrder=3;4;5;6;7;8;9;10
|
||||
|
||||
[ScreenMapping]
|
||||
itemsOnDisabledScreens=
|
||||
"""
|
||||
|
||||
# What the lab produced when Plasma made a desktop mid-session: the good
|
||||
# one kept its picture, and the new one, 11, had none.
|
||||
LATE_DESKTOP = RIVER_KIDS.replace("[ScreenMapping]", """[Containments][11]
|
||||
activityId=f992593c-2347-4e8d-be82-f80d02e71158
|
||||
formfactor=0
|
||||
immutability=1
|
||||
lastScreen=0
|
||||
location=0
|
||||
plugin=org.kde.plasma.folder
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][11][General]
|
||||
positions={"1920x1080":[]}
|
||||
|
||||
[ScreenMapping]""")
|
||||
|
||||
|
||||
print("1. Tiny Pebbles: a desktop numbered 10, with no wallpaper at all")
|
||||
check("the desktop is found by what it is, not its number",
|
||||
module.desktops(TINY_PEBBLES.splitlines(True)) == ["10"],
|
||||
str(module.desktops(TINY_PEBBLES.splitlines(True))))
|
||||
after, changes = module.converge(TINY_PEBBLES)
|
||||
groups_before, groups_after = group_texts(TINY_PEBBLES), group_texts(after)
|
||||
wallpaper = groups_after.get("[Containments][10][Wallpaper][org.kde.image][General]", "")
|
||||
check("it gets the classroom's image", IMAGE in wallpaper, wallpaper)
|
||||
check("scaled and cropped, as the layout writes it", "FillMode=2" in wallpaper)
|
||||
check("and it says what it did", changes == ["desktop 10: had no wallpaper configured"],
|
||||
str(changes))
|
||||
check("the wallpaper plugin is still the image one",
|
||||
"wallpaperplugin=org.kde.image" in groups_after["[Containments][10]"])
|
||||
check("one group added and nothing else changed",
|
||||
set(groups_after) - set(groups_before)
|
||||
== {"[Containments][10][Wallpaper][org.kde.image][General]"}
|
||||
and all(groups_after[name] == text for name, text in groups_before.items()))
|
||||
check("every containment keeps its number", containment_ids(after) == ["1", "10"])
|
||||
check("the panel is still containment 1, applets 2 to 9, in order",
|
||||
groups_after["[Containments][1][General]"].endswith("AppletOrder=2;3;4;5;6;7;8;9")
|
||||
and groups_after["[Containments][1]"] == groups_before["[Containments][1]"])
|
||||
check("the new group sits with its own containment",
|
||||
after.index("[Containments][10][General]")
|
||||
< after.index("[Containments][10][Wallpaper]")
|
||||
< after.index("[ScreenMapping]"))
|
||||
again, changes_again = module.converge(after)
|
||||
check("a second run changes nothing", again == after and changes_again == [], str(changes_again))
|
||||
|
||||
|
||||
print("2. River Kids: already right")
|
||||
after, changes = module.converge(RIVER_KIDS)
|
||||
check("the file comes back byte for byte", after == RIVER_KIDS)
|
||||
check("and nothing is reported as changed", changes == [], str(changes))
|
||||
check("applet 10 on the panel is not mistaken for a desktop",
|
||||
module.desktops(RIVER_KIDS.splitlines(True)) == ["1"])
|
||||
|
||||
|
||||
print("3. two desktops, one bare, as the lab made them")
|
||||
after, changes = module.converge(LATE_DESKTOP)
|
||||
groups_before, groups_after = group_texts(LATE_DESKTOP), group_texts(after)
|
||||
check("only the bare one is written",
|
||||
changes == ["desktop 11: had no wallpaper configured"], str(changes))
|
||||
check("the good one is untouched",
|
||||
groups_after["[Containments][1][Wallpaper][org.kde.image][General]"]
|
||||
== groups_before["[Containments][1][Wallpaper][org.kde.image][General]"])
|
||||
check("and the panel, numbered 2, is untouched",
|
||||
all(groups_after[name] == text for name, text in groups_before.items()
|
||||
if name and name.startswith("[Containments][2]")))
|
||||
check("the new one has the image",
|
||||
IMAGE in groups_after.get("[Containments][11][Wallpaper][org.kde.image][General]", ""))
|
||||
|
||||
|
||||
print("4. a desktop some other profile left with another picture")
|
||||
OTHER = """[Containments][4]
|
||||
formfactor=0
|
||||
location=0
|
||||
plugin=org.kde.desktopcontainment
|
||||
wallpaperplugin=org.kde.color
|
||||
|
||||
[Containments][4][Wallpaper][org.kde.image][General]
|
||||
Image[$e]=file:///usr/share/wallpapers/Next/contents/images/1920x1080.png
|
||||
SlidePaths=/usr/share/wallpapers/
|
||||
|
||||
[Containments][7]
|
||||
formfactor=2
|
||||
location=4
|
||||
plugin=org.kde.plasma.private.systemtray
|
||||
|
||||
[Containments][9]
|
||||
formfactor=2
|
||||
location=4
|
||||
plugin=org.kde.panel
|
||||
"""
|
||||
after, changes = module.converge(OTHER)
|
||||
groups_after = group_texts(after)
|
||||
check("the plain Desktop containment counts as a desktop",
|
||||
module.desktops(OTHER.splitlines(True)) == ["4"])
|
||||
check("its plugin becomes the image one",
|
||||
"wallpaperplugin=org.kde.image" in groups_after["[Containments][4]"])
|
||||
wallpaper = groups_after["[Containments][4][Wallpaper][org.kde.image][General]"]
|
||||
check("its image becomes the classroom's, keeping the key's flags",
|
||||
"Image[$e]=file:///usr/share/backgrounds/ekklesia/classroom-calm.png" in wallpaper,
|
||||
wallpaper)
|
||||
check("a missing key is added to the existing group", "FillMode=2" in wallpaper)
|
||||
check("a key the classroom does not set is left alone",
|
||||
"SlidePaths=/usr/share/wallpapers/" in wallpaper)
|
||||
check("a system tray is not a desktop",
|
||||
groups_after["[Containments][7]"] == group_texts(OTHER)["[Containments][7]"])
|
||||
check("nor is a panel",
|
||||
groups_after["[Containments][9]"] == group_texts(OTHER)["[Containments][9]"])
|
||||
check("and it settles", module.converge(after) == (after, []))
|
||||
|
||||
|
||||
print("5. the command, on files")
|
||||
work = Path(tempfile.mkdtemp())
|
||||
|
||||
|
||||
def run(*args):
|
||||
return module.main([str(arg) for arg in args])
|
||||
|
||||
|
||||
target = work / "tiny-pebbles"
|
||||
target.write_text(TINY_PEBBLES)
|
||||
os.chmod(target, 0o600)
|
||||
check("--check says it is wrong", run("--check", target) == 1)
|
||||
check("and changes nothing", target.read_text() == TINY_PEBBLES)
|
||||
check("the repair succeeds", run(target) == 0)
|
||||
check("the file is repaired", IMAGE in target.read_text())
|
||||
check("the file keeps its permissions", (target.stat().st_mode & 0o777) == 0o600)
|
||||
check("no temporary file is left behind", sorted(p.name for p in work.iterdir()) == ["tiny-pebbles"])
|
||||
stamp = (target.stat().st_ino, target.stat().st_mtime_ns)
|
||||
check("a second run succeeds", run(target) == 0)
|
||||
check("and does not write the file at all",
|
||||
(target.stat().st_ino, target.stat().st_mtime_ns) == stamp)
|
||||
check("--check then says it is right", run("--check", target) == 0)
|
||||
|
||||
healthy = work / "river-kids"
|
||||
healthy.write_text(RIVER_KIDS)
|
||||
stamp = (healthy.stat().st_ino, healthy.stat().st_mtime_ns)
|
||||
check("a machine that is right is left alone", run(healthy) == 0
|
||||
and (healthy.stat().st_ino, healthy.stat().st_mtime_ns) == stamp)
|
||||
|
||||
check("no file at all is not an error", run(work / "absent") == 0)
|
||||
check("and none is created", not (work / "absent").exists())
|
||||
|
||||
empty = work / "no-desktop"
|
||||
empty.write_text(PANEL_1)
|
||||
check("a file with only a panel is not an error", run(empty) == 0)
|
||||
check("and is not changed", empty.read_text() == PANEL_1)
|
||||
|
||||
|
||||
print("6. never underneath a running shell")
|
||||
config_home = work / "config"
|
||||
config_home.mkdir()
|
||||
live = config_home / module.APPLETSRC
|
||||
live.write_text(TINY_PEBBLES)
|
||||
os.environ["XDG_CONFIG_HOME"] = str(config_home)
|
||||
check("the default file is the one in XDG_CONFIG_HOME", module.default_path() == str(live))
|
||||
|
||||
proc = work / "proc"
|
||||
for pid, name in (("1", "systemd"), ("812", "kwin_wayland"), ("self", "python3")):
|
||||
(proc / pid).mkdir(parents=True)
|
||||
(proc / pid / "comm").write_text(name + "\n")
|
||||
check("no plasmashell in the process table is no shell",
|
||||
module.shell_running(str(proc)) is False)
|
||||
(proc / "1044").mkdir()
|
||||
(proc / "1044" / "comm").write_text("plasmashell\n")
|
||||
check("this user's plasmashell is found", module.shell_running(str(proc)) is True)
|
||||
check("and the real process table can be read",
|
||||
module.shell_running() in (True, False))
|
||||
|
||||
module.shell_running = lambda: True
|
||||
check("with plasmashell running it refuses", run() == 1)
|
||||
check("and the shell's file is untouched", live.read_text() == TINY_PEBBLES)
|
||||
check("--check still answers while it runs", run("--check") == 1)
|
||||
|
||||
module.shell_running = lambda: False
|
||||
check("with the shell stopped it repairs", run() == 0 and IMAGE in live.read_text())
|
||||
|
||||
|
||||
print("7. one wallpaper, named the same everywhere")
|
||||
layout = (root / "config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js").read_text()
|
||||
defaults = (root / "config/plasma/lookandfeel/contents/defaults").read_text()
|
||||
build = (root / "tools/build-release.sh").read_text()
|
||||
dropin = (root / "systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf").read_text()
|
||||
package = root / "config/wallpapers/EkklesiaClassroom"
|
||||
metadata = json.loads((package / "metadata.json").read_text())
|
||||
|
||||
check("the layout and the repair name the same file",
|
||||
f'var WALLPAPER = "{module.WALLPAPER}";' in layout)
|
||||
check("and the same fill mode", 'desktop.writeConfig("FillMode", 2);' in layout
|
||||
and ("FillMode", "2") in module.WALLPAPER_KEYS)
|
||||
check("the packaged file is where they say", f'"$STAGE{module.WALLPAPER}"' in build)
|
||||
|
||||
default = re.search(r"^\[Wallpaper\]\nImage=(.*)$", defaults, re.M)
|
||||
check("the look and feel's default is a package name, not a path",
|
||||
default is not None and "/" not in default.group(1),
|
||||
default.group(1) if default else "no [Wallpaper] Image")
|
||||
check("the package it names is the classroom's",
|
||||
default is not None and default.group(1) == metadata["KPlugin"]["Id"] == package.name)
|
||||
check("the build installs that package's metadata",
|
||||
f'"$ROOT/config/wallpapers/{package.name}/metadata.json"' in build
|
||||
and f'WALLPAPER_PACKAGE="$STAGE/usr/share/wallpapers/{package.name}"' in build)
|
||||
check("its image is a link to the one file, not a copy",
|
||||
f"ln -s {module.WALLPAPER}" in build)
|
||||
|
||||
check("the repair is packaged",
|
||||
'"$STAGE/usr/local/bin/classroom-desktop-wallpaper"' in build)
|
||||
check("it runs before every shell start",
|
||||
"ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper" in dropin)
|
||||
check("and a failure in it cannot stop the shell",
|
||||
not re.search(r"^ExecStartPre=/", dropin, re.M))
|
||||
check("the layout still writes the wallpaper for a fresh desktop",
|
||||
'desktop.writeConfig("Image", "file://" + WALLPAPER);' in layout)
|
||||
|
||||
print()
|
||||
print(f"{passed} passed, {failed} failed")
|
||||
sys.exit(1 if failed else 0)
|
||||
PY
|
||||
@@ -182,6 +182,12 @@ check "no discover or system settings" 0 \
|
||||
"$(in_lab "grep -cE 'discover.desktop|systemsettings.desktop' $APPLETS")"
|
||||
check "the packaged wallpaper" 1 \
|
||||
"$(in_lab "grep -c 'Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png' $APPLETS")"
|
||||
# And the one a desktop gets when nothing wrote it, which is what Tiny
|
||||
# Pebbles showed: a package name, and the package there to be found.
|
||||
check "a desktop nobody configured defaults to it" EkklesiaClassroom \
|
||||
"$(in_lab 'sed -n "/^\[Wallpaper\]/,/^\[/s/^Image=//p" /usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults')"
|
||||
check "and that package is the same picture" /usr/share/backgrounds/ekklesia/classroom-calm.png \
|
||||
"$(in_lab 'readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png')"
|
||||
|
||||
section "it will not strand a teacher"
|
||||
check "no automatic lock" false "$(session_config --file kscreenlockerrc --group Daemon --key Autolock)"
|
||||
|
||||
Executable
+238
@@ -0,0 +1,238 @@
|
||||
#!/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 ]]
|
||||
@@ -499,6 +499,71 @@ if [[ -n "$previous" ]]; then
|
||||
grep -q 'var PRESENTATIONS = false;' \
|
||||
/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
|
||||
|
||||
# TINY PEBBLES, 0.2.2 -> 0.2.6.4: THE DESKTOP WITH NO WALLPAPER.
|
||||
#
|
||||
# The room's desktop was containment 10, made by Plasma after the
|
||||
# layout had run, with nothing configured on it; it showed Plasma's
|
||||
# own wallpaper. What a desktop gets when nothing wrote it is the look
|
||||
# and feel's default, and that has to be a package Plasma can find.
|
||||
lnf=/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults
|
||||
[[ "$(sed -n '/^\[Wallpaper\]/,/^\[/s/^Image=//p' "$lnf")" == EkklesiaClassroom ]]
|
||||
grep -q '"Id": "EkklesiaClassroom"' /usr/share/wallpapers/EkklesiaClassroom/metadata.json
|
||||
[[ "$(readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png)" \
|
||||
== /usr/share/backgrounds/ekklesia/classroom-calm.png ]]
|
||||
grep -qx 'ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper' \
|
||||
/etc/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf
|
||||
|
||||
# And the repair the package installed, on that room's own file: the
|
||||
# panel built with no screen took 1 and its applets 2 to 9.
|
||||
wallpaper_home=$(mktemp -d)
|
||||
cat > "$wallpaper_home/plasma-org.kde.plasma.desktop-appletsrc" <<'APPLETS'
|
||||
[Containments][1]
|
||||
formfactor=2
|
||||
location=4
|
||||
plugin=org.kde.panel
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][1][Applets][2]
|
||||
plugin=org.kde.plasma.quicklaunch
|
||||
|
||||
[Containments][1][Applets][9]
|
||||
plugin=org.kde.plasma.digitalclock
|
||||
|
||||
[Containments][1][General]
|
||||
AppletOrder=2;3;4;5;6;7;8;9
|
||||
|
||||
[Containments][10]
|
||||
activityId=62d3d55d-bd31-4fc8-9a7b-a5b9b2b99b60
|
||||
formfactor=0
|
||||
immutability=1
|
||||
lastScreen=0
|
||||
location=0
|
||||
plugin=org.kde.plasma.folder
|
||||
wallpaperplugin=org.kde.image
|
||||
|
||||
[Containments][10][General]
|
||||
positions={"1920x1080":[]}
|
||||
APPLETS
|
||||
applets="$wallpaper_home/plasma-org.kde.plasma.desktop-appletsrc"
|
||||
panel_before=$(sed -n '/^\[Containments\]\[1\]/,/^$/p' "$applets")
|
||||
|
||||
! XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper --check >/dev/null
|
||||
XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper \
|
||||
| grep -q 'configured on desktop 10'
|
||||
sed -n '/^\[Containments\]\[10\]\[Wallpaper\]\[org.kde.image\]\[General\]$/,/^$/p' "$applets" \
|
||||
| grep -qx 'Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png'
|
||||
[[ "$(sed -n '/^\[Containments\]\[1\]/,/^$/p' "$applets")" == "$panel_before" ]]
|
||||
[[ "$(grep -c '^\[Containments\]\[[0-9]*\]$' "$applets")" -eq 2 ]]
|
||||
|
||||
# Twice changes nothing, and a room that was already right is not
|
||||
# written at all.
|
||||
repaired=$(sha256sum < "$applets")
|
||||
XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper \
|
||||
| grep -q 'the classroom wallpaper already'
|
||||
[[ "$(sha256sum < "$applets")" == "$repaired" ]]
|
||||
XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper --check >/dev/null
|
||||
rm -rf "$wallpaper_home"
|
||||
|
||||
# And a room that was carried through the experiment on its own
|
||||
# channel comes home, because that channel is the obsolete one now.
|
||||
sed -i 's/^CHANNEL=.*/CHANNEL=classroom-v2/' /etc/ekklesia-classroom/device.conf
|
||||
|
||||
Reference in New Issue
Block a user