#!/usr/bin/env python3 """Match the classroom's panel to the machine it is on. Two of the bar's launchers depend on the machine rather than on the release, and this edits the marked line for each of them in the installed layout, and nothing else. the disc player a room with no optical drive should not be offered one. The machine is asked; nothing is remembered. Wonder Ink WONDERINK_ENABLED in device.conf. One future device is a laptop that will not have Wonder Ink at all, and a device that is not a classroom should not carry a lesson application it cannot use. The two are deliberately answered differently. A drive is something a machine can be asked about, and asking beats remembering to edit a file when a computer moves between rooms. A licence is not, so it is a setting. Run before the display manager by ekklesia-classroom-layout.service, and again by udev when a drive is plugged in or pulled out. That ordering is the whole point: the panel is built from this layout the moment a session starts, and a room that is told about its hardware afterwards keeps the wrong bar until the next login. It was being left to the provisioning service, which runs at boot alongside the session rather than before it. It writes the *layout*, not the running panel. Editing plasma-org.kde.plasma.desktop-appletsrc underneath a live plasmashell was tried and it destroyed the panel: the shell owns that file and rewrites it, so an edit races with it. The layout is a file the package owns and the shell only reads, and classroom-desktop-reset makes the shell read it again at the next login. """ import glob import os import re import sys sys.path.insert(0, "/usr/local/lib/ekklesia-classroom") sys.path.insert( 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib"), ) from classroom_config import enabled, read_device_config # noqa: E402 LAYOUT = ( "/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop" "/contents/layouts/org.kde.plasma.desktop-layout.js" ) DVD_MARKER = "/* CLASSROOM-DVD-LAUNCHER */" DVD_ENTRY = "ekklesia-dvd.desktop" WONDERINK_MARKER = "/* CLASSROOM-WONDERINK-LAUNCHER */" WONDERINK_ENTRY = "ekklesia-wonderink.desktop" # The kernel names optical drives sr0, sr1, ... regardless of bus. Both # views are checked: /dev is what anything opening the drive will use, and # /sys/block is the kernel's own list, which cannot be left behind by a # device node somebody or something created by hand. DRIVE_GLOB = "/dev/sr[0-9]*" SYSFS_GLOB = "/sys/block/sr[0-9]*" def drives(): return sorted(glob.glob(DRIVE_GLOB)), sorted(glob.glob(SYSFS_GLOB)) def has_optical_drive(): nodes, blocks = drives() # Said out loud every time. When a room shows a disc player it has no # drive for, or hides one it has, this line in the journal is the # difference between an answer and an afternoon. print("classroom-desktop-layout: /dev " f"[{', '.join(nodes) or 'none'}], /sys/block " f"[{', '.join(blocks) or 'none'}]") return bool(nodes and blocks) def rewrite(text, marker, entry, wanted): """Comment one marked launcher in or out, idempotently.""" line = re.compile( r"^(\s*)(//\s*)?(" + re.escape(marker) + r'\s*"applications:' + re.escape(entry) + r'")', re.M, ) def swap(match): indent, _, body = match.groups() return f"{indent}{'' if wanted else '// '}{body}" return line.sub(swap, text) def wonderink_wanted(): """WONDERINK_ENABLED, defaulting to true. A classroom that has never heard of the setting is a classroom, so the absent case is the one every existing room is in and it has to mean yes. Said out loud for the same reason the drive is. """ config = read_device_config() wanted = enabled(config, "WONDERINK_ENABLED", default=True) print(f"classroom-desktop-layout: WONDERINK_ENABLED " f"[{config.get('WONDERINK_ENABLED', 'unset')}]") return wanted def main(): path = sys.argv[1] if len(sys.argv) > 1 else LAYOUT try: with open(path, "r", encoding="utf-8") as handle: before = handle.read() except OSError as error: print(f"classroom-desktop-layout: {error}", file=sys.stderr) return 0 if DVD_MARKER not in before: print("classroom-desktop-layout: no DVD marker in the layout", file=sys.stderr) return 0 after = rewrite(before, DVD_MARKER, DVD_ENTRY, has_optical_drive()) if WONDERINK_MARKER in before: after = rewrite( after, WONDERINK_MARKER, WONDERINK_ENTRY, wonderink_wanted() ) else: print("classroom-desktop-layout: no Wonder Ink marker in the layout", file=sys.stderr) if after == before: print("classroom-desktop-layout: the layout already matches " "this machine") return 0 # Replace the file rather than edit in place, so a full disk or a # crash leaves the old layout intact rather than half of a new one. temporary = f"{path}.new" try: with open(temporary, "w", encoding="utf-8") as handle: handle.write(after) os.replace(temporary, path) except OSError as error: print(f"classroom-desktop-layout: {error}", file=sys.stderr) return 1 print("classroom-desktop-layout: the layout now matches this machine") return 0 if __name__ == "__main__": sys.exit(main())