Files
Classroom_Desktop/lib/classroom_config.py
T

185 lines
5.0 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
# Set to true on classrooms that have a DVD drive.
DVD_ENABLED=false
# 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
# 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 = (
(
"DVD_ENABLED",
"""# Set to true on classrooms that have a DVD drive.
DVD_ENABLED=false
""",
),
(
"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
""",
),
(
"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 dock_items(config):
"""Dock items in display order, with disabled capabilities omitted."""
items = ["browser"]
if enabled(config, "DVD_ENABLED"):
items.append("dvd")
if enabled(config, "LIVE_ENABLED"):
items.append("live")
return tuple(items)
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 by sway to the whole output (see classroom-scale),
# which scales Chrome, VLC and every other application along with the
# dock. Our surfaces are measured in the same logical pixels sway 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)))