#!/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"(\w+)") == "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 = "playingtrue" WINDOWED = "playingfalse" 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