Pressing DVD now enters DVD mode rather than opening VLC. VLC is still the backend, because its dvdnav module is what decodes disc menus and nothing else on Debian does that reliably, but nothing a person can see or press belongs to it any more. classroom-dvd-session owns the whole thing. It puts a full-screen black layer-shell surface up the moment the button is pressed, starts VLC behind it, and drops it once playback is actually on screen — so the harsh cut from wallpaper to VLC's black window is gone, and no partial VLC window or reserved dock strip is ever visible. A deadline drops the surface regardless, because DVD mode must never leave a black screen with nothing behind it. The controls are a classroom bar on the overlay layer, the one layer sway draws above a fullscreen window. It slides up when the pointer reaches the bottom of the screen and hides again shortly after it leaves. It is drawn from the dock's own stylesheet at the room's UI scale, so it is the same surface, the same radius and the same white icons. While hidden it only accepts the pointer in a thin strip along the bottom edge, so a click on a disc menu goes to the disc. The bar reaches VLC through an HTTP interface on loopback behind a password generated per session, sending hotkey *actions* by name. That is what makes the next part possible. All 108 hotkeys VLC 3.0.23 defines are unset, then Space, Esc and the arrow keys plus Enter are bound. Crop, aspect ratio, subtitles, audio tracks and deinterlacing are no longer reachable by pressing a key at random, and the arrow keys no longer change VLC's volume — the television owns volume, and CEC will own it properly later. Esc quits instead of leaving fullscreen. There is nothing to leave, and leaving it was what stranded people on a black VLC window. Close, Eject and Esc all ask VLC to quit and escalate to SIGTERM then SIGKILL if it does not; when VLC exits by any route, including a crash, the session ends and the classroom desktop comes back. Verified against VLC 3.0.23 on Trixie: every argument accepted, every control action accepted over the control interface, a wrong password refused, and quit exiting cleanly. The surfaces were rendered under a headless sway and screenshotted to check the bar against the dock. Co-Authored-By: Claude Opus 5 <[email protected]>
162 lines
4.0 KiB
Python
Executable File
162 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""Classroom DVD control.
|
|
|
|
Discs are never played automatically. Inserting a disc only produces a
|
|
notification and changes the dock icon; playback always starts from a
|
|
deliberate press of the DVD button.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
|
sys.path.insert(
|
|
0,
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib"),
|
|
)
|
|
|
|
import classroom_optical as optical # noqa: E402
|
|
from classroom_vlc import PLAYER # noqa: E402
|
|
|
|
WINDOW_TOOL = "/usr/local/bin/classroom-window"
|
|
SESSION_TOOL = "/usr/local/bin/classroom-dvd-session"
|
|
|
|
|
|
def notify(title, message, urgency="normal"):
|
|
try:
|
|
subprocess.Popen(
|
|
[
|
|
"notify-send",
|
|
"--app-name=Ekklesia Classroom",
|
|
f"--urgency={urgency}",
|
|
"--",
|
|
title,
|
|
message,
|
|
],
|
|
start_new_session=True,
|
|
)
|
|
except OSError:
|
|
print(f"{title}: {message}", file=sys.stderr)
|
|
|
|
|
|
def play(device):
|
|
"""Hand the disc to DVD mode through the normal window toggle.
|
|
|
|
classroom-dvd-session owns the player from here: it puts a black
|
|
surface up immediately, starts VLC behind it, and provides the
|
|
classroom's own controls. Going through classroom-window keeps the
|
|
dock button behaving like every other one — focus if it is already
|
|
up, rather than starting a second session.
|
|
"""
|
|
if shutil.which(PLAYER) is None:
|
|
notify(
|
|
"DVD",
|
|
"The DVD player is not installed on this classroom computer.",
|
|
)
|
|
return 1
|
|
|
|
subprocess.run(
|
|
[
|
|
WINDOW_TOOL,
|
|
"toggle",
|
|
"--match",
|
|
"(?i)vlc",
|
|
"--",
|
|
SESSION_TOOL,
|
|
]
|
|
)
|
|
|
|
return 0
|
|
|
|
|
|
def action_toggle(force_play=False):
|
|
device = optical.default_device()
|
|
|
|
if device is None:
|
|
notify("DVD", "No DVD drive is connected to this classroom computer.")
|
|
return 1
|
|
|
|
status = optical.drive_status(device)
|
|
|
|
if status == optical.CDS_DISC_OK:
|
|
return play(device)
|
|
|
|
if status == optical.CDS_DRIVE_NOT_READY:
|
|
notify("DVD", "Reading the disc. Please try again in a moment.")
|
|
return 1
|
|
|
|
if status == optical.CDS_TRAY_OPEN:
|
|
notify("DVD", "The disc tray is open. Insert a disc to get started.")
|
|
return 1
|
|
|
|
if force_play:
|
|
return play(device)
|
|
|
|
notify("DVD", "No disc is in the drive. Insert a disc to get started.")
|
|
return 1
|
|
|
|
|
|
def action_status():
|
|
device = optical.default_device()
|
|
|
|
if device is None:
|
|
print("drive: none")
|
|
return 1
|
|
|
|
status = optical.drive_status(device)
|
|
|
|
print(f"drive: {device}")
|
|
print(f"status: {optical.describe(status)}")
|
|
print(f"player: {shutil.which(PLAYER) or 'not installed'}")
|
|
|
|
if status == optical.CDS_DISC_OK:
|
|
print(f"disc: {optical.disc_status(device)}")
|
|
return 0
|
|
|
|
return 1
|
|
|
|
|
|
def action_eject():
|
|
device = optical.default_device()
|
|
|
|
if device is None:
|
|
notify("DVD", "No DVD drive is connected to this classroom computer.")
|
|
return 1
|
|
|
|
if not optical.eject(device):
|
|
notify("DVD", "The disc could not be ejected. It may still be playing.")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Classroom DVD control.")
|
|
subparsers = parser.add_subparsers(dest="action", required=True)
|
|
|
|
subparsers.add_parser("toggle", help="Dock button behaviour")
|
|
subparsers.add_parser("play", help="Start playback even if the drive is unsure")
|
|
subparsers.add_parser("status", help="Report drive and disc state")
|
|
subparsers.add_parser("eject", help="Open the disc tray")
|
|
|
|
arguments = parser.parse_args()
|
|
|
|
if arguments.action == "toggle":
|
|
return action_toggle()
|
|
|
|
if arguments.action == "play":
|
|
return action_toggle(force_play=True)
|
|
|
|
if arguments.action == "status":
|
|
return action_status()
|
|
|
|
return action_eject()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|