DVD support stays optional per room: DVD_ENABLED decides whether the button is built at all, and rooms without it poll nothing. Upgrades append a commented DVD_ENABLED=false so the option is discoverable without changing what those rooms already do. The dock polls the drive with CDROM_DRIVE_STATUS, which is a status query rather than a read, so it does not spin the disc up. A disc that becomes ready announces itself once and brings the DVD logo up to full strength; an empty or unplugged drive holds it back, the same gray versus colour language the LIVE icon already uses. Nothing autoplays — playback always needs a deliberate press. Playback is VLC rather than mpv because mpv dropped DVD menu support, and menus are the part that matters for a commercial disc. It goes through classroom-window like everything else, so the player minimizes and restores from the dock exactly as the browser does. Pressing the button with no drive, no disc, or a disc still spinning up gives a plain-language notification instead of failing silently. Reading the drive needs group cdrom, which the postinst grants and the OTA reboot activates. Commercial CSS discs additionally need libdvdcss, which Debian cannot ship; classroom-dvd-css-setup bootstraps it from contrib. That is deliberately not run from the postinst, which executes while dpkg already holds the apt lock. The optical helpers live in a shared module both the dock and classroom-dvd import, and the dock imports it defensively: if it ever goes missing the shell loses disc detection rather than failing to start. Co-Authored-By: Claude Opus 5 <[email protected]>
126 lines
2.8 KiB
Python
126 lines
2.8 KiB
Python
"""Optical drive helpers for the Ekklesia Classroom shell.
|
|
|
|
Everything here is deliberately dependency free and non-blocking so the
|
|
dock can poll drive state from its GTK main loop without stalling and
|
|
without spinning the drive up.
|
|
"""
|
|
|
|
import fcntl
|
|
import glob
|
|
import os
|
|
|
|
# linux/cdrom.h
|
|
CDROM_DRIVE_STATUS = 0x5326
|
|
CDROM_DISC_STATUS = 0x5327
|
|
CDROMEJECT = 0x5309
|
|
CDROMCLOSETRAY = 0x5319
|
|
|
|
# CDSL_CURRENT: "whatever slot is loaded right now".
|
|
CDSL_CURRENT = 0x7FFFFFFF
|
|
|
|
# Drive status values.
|
|
CDS_NO_INFO = 0
|
|
CDS_NO_DISC = 1
|
|
CDS_TRAY_OPEN = 2
|
|
CDS_DRIVE_NOT_READY = 3
|
|
CDS_DISC_OK = 4
|
|
|
|
# Disc status values.
|
|
CDS_AUDIO = 100
|
|
CDS_DATA_1 = 101
|
|
CDS_DATA_2 = 102
|
|
CDS_XA_2_1 = 103
|
|
CDS_XA_2_2 = 104
|
|
CDS_MIXED = 105
|
|
|
|
|
|
def devices():
|
|
"""Every optical device node currently present."""
|
|
return sorted(glob.glob("/dev/sr[0-9]*"))
|
|
|
|
|
|
def default_device():
|
|
"""The optical device the classroom should use, if any."""
|
|
found = devices()
|
|
|
|
if not found:
|
|
return None
|
|
|
|
return found[0]
|
|
|
|
|
|
def _ioctl(device, request, argument=CDSL_CURRENT):
|
|
"""Run a CD-ROM ioctl, returning None when the drive cannot be read."""
|
|
try:
|
|
fd = os.open(device, os.O_RDONLY | os.O_NONBLOCK)
|
|
except OSError:
|
|
return None
|
|
|
|
try:
|
|
return fcntl.ioctl(fd, request, argument)
|
|
except OSError:
|
|
return None
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
def drive_status(device=None):
|
|
"""CDS_* drive status, or None when there is no readable drive."""
|
|
if device is None:
|
|
device = default_device()
|
|
|
|
if device is None:
|
|
return None
|
|
|
|
return _ioctl(device, CDROM_DRIVE_STATUS)
|
|
|
|
|
|
def disc_status(device=None):
|
|
"""CDS_* disc type. Only call this once a disc has become ready."""
|
|
if device is None:
|
|
device = default_device()
|
|
|
|
if device is None:
|
|
return None
|
|
|
|
return _ioctl(device, CDROM_DISC_STATUS)
|
|
|
|
|
|
def has_disc(device=None):
|
|
"""True when a disc is loaded and the drive has finished reading it."""
|
|
return drive_status(device) == CDS_DISC_OK
|
|
|
|
|
|
def eject(device=None):
|
|
"""Open the tray. Returns True when the drive accepted the request."""
|
|
if device is None:
|
|
device = default_device()
|
|
|
|
if device is None:
|
|
return False
|
|
|
|
return _ioctl(device, CDROMEJECT, 0) is not None
|
|
|
|
|
|
def close_tray(device=None):
|
|
"""Close the tray. Returns True when the drive accepted the request."""
|
|
if device is None:
|
|
device = default_device()
|
|
|
|
if device is None:
|
|
return False
|
|
|
|
return _ioctl(device, CDROMCLOSETRAY, 0) is not None
|
|
|
|
|
|
def describe(status):
|
|
"""Human readable drive status, used by classroom-dvd status."""
|
|
return {
|
|
None: "no drive",
|
|
CDS_NO_INFO: "unknown",
|
|
CDS_NO_DISC: "no disc",
|
|
CDS_TRAY_OPEN: "tray open",
|
|
CDS_DRIVE_NOT_READY: "drive not ready",
|
|
CDS_DISC_OK: "disc ready",
|
|
}.get(status, "unknown")
|