Files
Classroom_Desktop/lib/classroom_config.py
T
ekklesiaandClaude Opus 5 13aac85b87 docs: record Parent Call, the DVD migration and what is left to test
Three things in this window that a container cannot answer, and they are
now the top of the next-steps list: a real key pressed into Parent Call,
a television switched on after the computer, and a disc that plays.

Also brings the comments in the shared libraries up to date. The only
mention of sway left in the tree is the upgrade step that removes the
sway-era login shell from a room being upgraded.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 13:09:42 -05:00

191 lines
5.6 KiB
Python

"""Device configuration for the Ekklesia Classroom shell.
Everything that has to agree about /etc/ekklesia-classroom/device.conf
reads it through here, so the dock, the notification daemon and the DVD
commands cannot drift apart over what a setting means.
"""
import math
from pathlib import Path
CONFIG_FILE = Path("/etc/ekklesia-classroom/device.conf")
TRUTHY = {"1", "true", "yes", "on", "enabled"}
# The dock is tuned for a 1080p classroom TV at 1.0. Smaller rooms with a
# 32" panel sit near the bottom of this range, a 65" set near the top.
UI_SCALE_DEFAULT = 1.0
UI_SCALE_MIN = 0.8
UI_SCALE_MAX = 1.5
DEVICE_CONFIG_TEMPLATE = """# Ekklesia Classroom device configuration
UPDATE_URL=https://updates.riverofglorymn.org
CHANNEL=stable
# Show the Sanctuary LIVE placeholder in the dock. Live viewing is not
# implemented yet; this only controls whether the current placeholder appears.
LIVE_ENABLED=false
# Which output the room plays through.
#
# auto prefer a connected HDMI or DisplayPort television, and fall
# back to the analog jack. This is right for almost every room.
# hdmi always the television, even if it does not report itself.
# analog always the jack.
#
# auto re-decides whenever the hardware changes, so a television switched
# on after the computer is picked up without anybody touching anything,
# and sound that is already playing is moved across to it.
AUDIO_OUTPUT=auto
# Size of the dock, clock and notifications, 0.8 to 1.5.
# Larger rooms and larger TVs want a larger number.
UI_SCALE=1.0
# Church message broker. The room is this machine's hostname unless ROOM
# is set. Leave MQTT_ENABLED=false until a broker exists.
MQTT_ENABLED=false
MQTT_HOST=
MQTT_PORT=1883
MQTT_USERNAME=
MQTT_PASSWORD=
"""
# Sections added after the first classroom release. Keeping them separate lets
# an upgrade make a setting discoverable without replacing anything an
# administrator has already put in device.conf.
DEVICE_CONFIG_SECTIONS = (
(
"LIVE_ENABLED",
"""# Show the Sanctuary LIVE placeholder in the dock. Live viewing is not
# implemented yet; this only controls whether the current placeholder appears.
LIVE_ENABLED=false
""",
),
(
"AUDIO_OUTPUT",
"""# Which output the room plays through.
#
# auto prefer a connected HDMI or DisplayPort television, and fall
# back to the analog jack. This is right for almost every room.
# hdmi always the television, even if it does not report itself.
# analog always the jack.
#
# auto re-decides whenever the hardware changes, so a television switched
# on after the computer is picked up without anybody touching anything,
# and sound that is already playing is moved across to it.
AUDIO_OUTPUT=auto
""",
),
(
"UI_SCALE",
"""# Size of the dock, clock and notifications, 0.8 to 1.5.
# Larger rooms and larger TVs want a larger number.
UI_SCALE=1.0
""",
),
(
"MQTT_ENABLED",
"""# Church message broker. The room is this machine's hostname unless ROOM
# is set. Leave MQTT_ENABLED=false until a broker exists.
MQTT_ENABLED=false
MQTT_HOST=
MQTT_PORT=1883
MQTT_USERNAME=
MQTT_PASSWORD=
""",
),
)
def read_device_config(path=None):
config = {}
path = Path(path) if path is not None else CONFIG_FILE
if not path.exists():
return config
for raw_line in path.read_text().splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
config[key.strip()] = value.strip()
return config
def enabled(config, key, default=False):
if key not in config:
return default
return config[key].lower() in TRUTHY
def ensure_device_config(path=None):
"""Create device.conf or append any missing settings exactly once.
Existing bytes are otherwise left alone: per-room settings, comments and
even keys this release does not know about all survive an upgrade.
"""
path = Path(path) if path is not None else CONFIG_FILE
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(DEVICE_CONFIG_TEMPLATE)
return
text = path.read_text()
config = read_device_config(path)
additions = []
for key, section in DEVICE_CONFIG_SECTIONS:
if key not in config:
additions.append(section.rstrip("\n"))
if not additions:
return
separator = "" if not text or text.endswith("\n") else "\n"
path.write_text(text + separator + "\n\n".join(additions) + "\n")
def ui_scale(config):
"""UI_SCALE, clamped to something a classroom can actually use."""
raw = config.get("UI_SCALE", "")
try:
value = float(raw)
except (TypeError, ValueError):
return UI_SCALE_DEFAULT
if not math.isfinite(value):
return UI_SCALE_DEFAULT
return min(max(value, UI_SCALE_MIN), UI_SCALE_MAX)
# The scale the classroom's own GTK surfaces render at.
#
# UI_SCALE is applied to the whole output (see classroom-scale),
# which scales Chrome, VLC and every other application along with the
# panel. Our surfaces are measured in the same logical pixels the
# compositor is
# already scaling, so scaling them here as well would apply UI_SCALE
# twice and the dock would come out half again too big.
#
# This is the seam: if compositor scaling ever has to be abandoned,
# changing this back to ui_scale(config) restores per-surface scaling
# without touching the templates.
SHELL_SCALE = 1.0
def scaled(value, scale):
"""A pixel measurement at the given scale, never smaller than 1."""
return max(1, int(round(value * scale)))