The DVD player was the only thing still speaking a compositor the room no longer runs. It asked sway's tree whether VLC's window had really gone fullscreen, because VLC reports playing while its window is still the black startup one the loading cover exists to hide. VLC's own status was measured as a replacement and is not one: it reports fullscreen true in the same 250ms poll as playing, because the flag is the option it was started with and not a report about its window. So the cover is held for a floor and capped by the deadline that was already there, and the compositor is not asked anything. Focus-or-launch went the same way. The session already takes an exclusive lock, so a second one cannot start, and bringing an existing one to the front is the panel's job. classroom-window, classroom_sway and classroom_chrome are gone with it — the last of them was reachable from nothing at all. DVD also moves out of the status row, where it sat beside the clock as though a disc were a machine reading. It is an application: it is in the row with Wonder Ink and the browser, and classroom-provision removes it from the layout on a machine with no optical drive, which retires DVD_ENABLED. A computer that moves between rooms takes its drive with it; nobody remembers to edit a file when it does. The panel is rebuilt from the package at every login now. Plasma keeps whatever panel it first built, so an update could change the bar in the package and never change it in a room — measured, with an icon that outlived the file it came from. Editing the live panel configuration was tried instead and destroyed the panel, because the shell owns that file. Removing it before the shell starts is safe and makes the desktop stateless. Co-Authored-By: Claude Opus 5 <[email protected]>
185 lines
6.1 KiB
Bash
Executable File
185 lines
6.1 KiB
Bash
Executable File
#!/bin/bash
|
|
set -uo pipefail
|
|
|
|
# Checks the VLC launch policy: every hotkey unset, only the classroom
|
|
# ones bound, and the control interface wired to this session only.
|
|
#
|
|
# tests/test-classroom-dvd-session.sh
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
PYTHONPATH="$ROOT/lib" python3 - "$ROOT" <<'PY'
|
|
import sys
|
|
|
|
sys.path.insert(0, sys.argv[1] + "/lib")
|
|
|
|
import classroom_vlc as vlc
|
|
|
|
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
|
|
|
|
|
|
command = vlc.player_command("/dev/sr0", 41234, "s3cret")
|
|
|
|
print("1. every VLC hotkey is unset")
|
|
|
|
check("VLC 3.0.23 defines 108 hotkeys", len(vlc.ALL_HOTKEYS) == 108,
|
|
f"got {len(vlc.ALL_HOTKEYS)}")
|
|
check("no duplicates in the hotkey list",
|
|
len(set(vlc.ALL_HOTKEYS)) == len(vlc.ALL_HOTKEYS))
|
|
|
|
unset = [a for a in command if a.startswith("--key-") and a.endswith("=")]
|
|
check("one unset per hotkey", len(unset) == len(vlc.ALL_HOTKEYS),
|
|
f"{len(unset)} unsets for {len(vlc.ALL_HOTKEYS)} hotkeys")
|
|
|
|
# The specific complaints from hardware testing.
|
|
for name in ("crop", "aspect-ratio", "vol-up", "vol-down", "vol-mute",
|
|
"subtitle-track", "audio-track", "deinterlace",
|
|
"toggle-fullscreen", "leave-fullscreen"):
|
|
check(f"{name} is unset", f"--key-{name}=" in command)
|
|
|
|
print()
|
|
print("2. only the classroom keys are bound")
|
|
|
|
bound = {a.split("=", 1)[0][len("--key-"):]: a.split("=", 1)[1]
|
|
for a in command
|
|
if a.startswith("--key-") and not a.endswith("=")}
|
|
|
|
check("bound set is exactly the classroom set",
|
|
bound == vlc.CLASSROOM_HOTKEYS, f"got {bound}")
|
|
check("Space is play/pause", bound.get("play-pause") == "Space")
|
|
check("Esc quits rather than leaving fullscreen",
|
|
bound.get("quit") == "Esc")
|
|
check("arrow keys are disc navigation, not volume",
|
|
bound.get("nav-up") == "Up" and bound.get("nav-down") == "Down")
|
|
check("Enter activates a menu item", bound.get("nav-activate") == "Enter")
|
|
check("nothing bound to volume",
|
|
not any(k.startswith("vol-") for k in bound))
|
|
check("no fullscreen toggle: DVD mode is the whole screen",
|
|
"toggle-fullscreen" not in bound and "leave-fullscreen" not in bound)
|
|
|
|
print()
|
|
print("3. unsets come before the bindings, so the bindings win")
|
|
|
|
first_binding = min(i for i, a in enumerate(command)
|
|
if a.startswith("--key-") and not a.endswith("="))
|
|
last_unset = max(i for i, a in enumerate(command) if a.endswith("=")
|
|
and a.startswith("--key-"))
|
|
check("every unset precedes every binding", last_unset < first_binding,
|
|
f"last unset {last_unset}, first binding {first_binding}")
|
|
|
|
print()
|
|
print("4. VLC's own interface is switched off")
|
|
|
|
for option in ("--qt-minimal-view", "--no-qt-fs-controller", "--no-qt-bgcone",
|
|
"--no-qt-system-tray", "--no-video-title-show", "--fullscreen",
|
|
"--qt-dark-palette"):
|
|
check(f"{option} passed", option in command)
|
|
|
|
print()
|
|
print("5. the control interface belongs to this session alone")
|
|
|
|
check("bound to loopback only",
|
|
command[command.index("--http-host") + 1] == "127.0.0.1")
|
|
check("uses the port it was given",
|
|
command[command.index("--http-port") + 1] == "41234")
|
|
check("uses the password it was given",
|
|
command[command.index("--http-password") + 1] == "s3cret")
|
|
check("passwords are not reused",
|
|
vlc.control_password() != vlc.control_password())
|
|
check("passwords are long enough to matter",
|
|
len(vlc.control_password()) >= 24)
|
|
|
|
print()
|
|
print("6. the disc is the last argument")
|
|
|
|
check("dvd:// selects dvdnav for menus", command[-1] == "dvd:///dev/sr0")
|
|
|
|
print()
|
|
print("7. the loading surface holds until the player is ready")
|
|
|
|
import re # noqa: E402
|
|
|
|
|
|
class FakeControl:
|
|
"""Stands in for VLC's HTTP interface."""
|
|
|
|
def __init__(self, body):
|
|
self.body = body
|
|
|
|
def field(self, pattern):
|
|
found = re.search(pattern, self.body)
|
|
return found.group(1) if found else None
|
|
|
|
def wants_fullscreen(self):
|
|
return self.field(r"<fullscreen>(\w+)</fullscreen>") == "true"
|
|
|
|
|
|
source = open(sys.argv[1] + "/scripts/classroom-dvd-session").read()
|
|
|
|
check("the session no longer asks a window manager anything",
|
|
"classroom_sway" not in source and "swaymsg" not in source)
|
|
check("and holds the cover for a floor instead",
|
|
"MINIMUM_COVER_SECONDS" in source)
|
|
check("VLC's own flag is named for what it is, not for the window",
|
|
"wants_fullscreen" in source and "player_is_fullscreen" not in source)
|
|
|
|
ready = None
|
|
|
|
try:
|
|
import importlib.util
|
|
from importlib.machinery import SourceFileLoader
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
gi.require_version("GtkLayerShell", "0.1")
|
|
|
|
loader = SourceFileLoader(
|
|
"dvdsession", sys.argv[1] + "/scripts/classroom-dvd-session")
|
|
spec = importlib.util.spec_from_loader("dvdsession", loader)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules["dvdsession"] = module
|
|
loader.exec_module(module)
|
|
ready = module.DvdSession.player_is_ready
|
|
except Exception as error: # noqa: BLE001
|
|
print(f" skip GTK unavailable here ({type(error).__name__})")
|
|
|
|
if ready is not None:
|
|
floor = module.MINIMUM_COVER_SECONDS
|
|
|
|
class Session:
|
|
def __init__(self, body, elapsed):
|
|
self.control = FakeControl(body)
|
|
self.elapsed = elapsed
|
|
|
|
FULL = "<state>playing</state><fullscreen>true</fullscreen>"
|
|
WINDOWED = "<state>playing</state><fullscreen>false</fullscreen>"
|
|
|
|
check("a player that is not set fullscreen is never ready",
|
|
ready(Session(WINDOWED, floor + 5)) is False)
|
|
check("a fullscreen player is not ready before the floor",
|
|
ready(Session(FULL, floor / 2)) is False)
|
|
check("and is ready after it",
|
|
ready(Session(FULL, floor + 0.1)) is True)
|
|
check("a silent VLC is not ready",
|
|
ready(Session("", floor + 5)) is False)
|
|
check("the startup watcher still explains what it waits for",
|
|
"fullscreen" in module.DvdSession.watch_startup.__doc__)
|
|
|
|
print()
|
|
print(f"passed: {passed} failed: {failed}")
|
|
sys.exit(1 if failed else 0)
|
|
PY
|