"""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"} # Tuned for a 1080p classroom television at 1.0. A 4K set wants 2.0, # which is the same interface at the same physical size on a display with # twice the pixels. UI_SCALE_DEFAULT = 1.0 UI_SCALE_MIN = 0.8 # 2.0 is a 4K television drawn at 1080p sizes, which is the setting the # large classroom screens actually need. The old ceiling of 1.5 could not # express it, so a room asking for 2.0 silently got 1.5. UI_SCALE_MAX = 3.0 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 # How large everything in the room is drawn: the panel, the clock, the # browser, Wonder Ink, the disc player, notifications and the volume # display, all from this one number. 1.0 to 3.0. # # 1.0 suits a 1080p television at ordinary classroom distance. 2.0 is # right for a 4K set, where everything is otherwise half the size it # looks. This is an administrator's setting: there is no way to reach it # from the room, on purpose. UI_SCALE=1.0 # Church message broker. Every classroom uses the same one, so the address # is a default rather than something to fill in room by room. The room is # this machine's hostname unless ROOM is set. A username and password are # only needed if the broker asks for them. MQTT_ENABLED=true MQTT_HOST=10.40.0.2 MQTT_PORT=1883 MQTT_USERNAME= MQTT_PASSWORD= # Whether this device has Wonder Ink at all. # # Every classroom does. A laptop that goes out with a volunteer does not, # and a room that is offered a lesson application it has no licence for is # a button that can only disappoint. # # false takes the launcher off the panel and stops the command starting. # The ordinary browser, Parent Call and everything else are untouched. WONDERINK_ENABLED=true """ # 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", """# How large everything in the room is drawn: the panel, the clock, the # browser, Wonder Ink, the disc player, notifications and the volume # display, all from this one number. 1.0 to 3.0. # # 1.0 suits a 1080p television at ordinary classroom distance. 2.0 is # right for a 4K set, where everything is otherwise half the size it # looks. This is an administrator's setting: there is no way to reach it # from the room, on purpose. UI_SCALE=1.0 """, ), ( "WONDERINK_ENABLED", '''# Whether this device has Wonder Ink at all. # # Every classroom does. A laptop that goes out with a volunteer does not, # and a room that is offered a lesson application it has no licence for is # a button that can only disappoint. # # false takes the launcher off the panel and stops the command starting. # The ordinary browser, Parent Call and everything else are untouched. WONDERINK_ENABLED=true ''', ), ( "MQTT_ENABLED", """# Church message broker. Every classroom uses the same one, so the address # is a default rather than something to fill in room by room. The room is # this machine's hostname unless ROOM is set. A username and password are # only needed if the broker asks for them. MQTT_ENABLED=true MQTT_HOST=10.40.0.2 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)))