#!/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