#!/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 PLAYER = "vlc" WINDOW_TOOL = "/usr/local/bin/classroom-window" 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 player_command(device): """VLC, dressed down to look like an appliance rather than a desktop app. All of these are documented VLC 3.x options, verified against the 3.0.23 build in Debian Trixie. No skin is involved: skins replace the interface wholesale and break DVD menu input, which is the one thing that has to keep working. Note that --no-qt-updates-notif, which earlier builds passed, is not an option in Debian's VLC and made it refuse to start at all. """ return [ PLAYER, # Fullscreen first: the disc opens straight onto the TV. "--fullscreen", # No menu bar or extended panels, and a dark window instead of # the default light grey one. "--qt-minimal-view", "--qt-dark-palette", # No orange cone, and no album-art panel, behind the video. "--no-qt-bgcone", # Nothing that behaves like a desktop application. "--no-qt-system-tray", "--no-qt-name-in-title", "--no-qt-video-autoresize", "--qt-notification=0", # Never offer to resume, and never accumulate a list of the # discs a classroom has played. "--qt-continue=0", "--no-qt-recentplay", # No first-run privacy dialog, no title banner over the video. "--no-qt-privacy-ask", "--no-video-title-show", # dvd:// selects VLC's dvdnav module, which is what provides the # disc menus. The Qt interface is kept for exactly that reason: # --intf dummy would be cleaner still but takes the keyboard and # mouse handling that menu navigation depends on with it. f"dvd://{device}", ] def play(device): """Hand the disc to the player through the normal window toggle.""" 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", "--", *player_command(device), ] ) 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())