185 lines
5.0 KiB
Python
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)))
|