"""Deciding which displays a classroom machine drives. The decision only, so it can be tested against the arrangement a real machine is in rather than against a running compositor. The laptop that prompted this is a fixed appliance: on AC, battery out, lid shut, one external television on HDMI. Its internal panel should be off. But external-only is a SETTING and not a property of laptops — another laptop may want its own screen, or both — so nothing here asks what shape the machine is. IDENTIFYING THE INTERNAL PANEL. By kscreen's own output type rather than by connector name. KScreen reports an integer from KScreen::Output::Type, and 7 is Panel: the built-in screen, whatever the connector happens to be called on that hardware. Matching "eDP-1" would be matching one machine's spelling — eDP, LVDS and DSI are all internal, numbered differently per driver, and renamed between kernel versions. The names are still checked, but only as a fallback for a driver that reports type Unknown, which some virtual and older devices do. """ # KScreen::Output::Type. Only the two this has to tell apart are named. TYPE_PANEL = 7 # Fallback for a driver that reports no type at all. INTERNAL_NAMES = ("edp", "lvds", "dsi", "emb") ALL = "all" EXTERNAL_ONLY = "external-only" def is_internal(output): """Whether this is the machine's own built-in screen.""" if output.get("type") == TYPE_PANEL: return True if output.get("type") not in (None, 0): return False name = (output.get("name") or "").lower() return any(name.startswith(prefix) for prefix in INTERNAL_NAMES) def connected(outputs): return [item for item in outputs or [] if item.get("connected", True)] def plan(outputs, mode=ALL): """Which connected outputs to enable and which to disable. Returns (enable, disable) as lists of names. The one rule that overrides the setting: never leave the machine with no screen at all. A laptop asked for external-only whose television is switched off keeps its own panel, because a machine nobody can see is a machine nobody can fix standing in front of it. It is corrected the next time this runs with the television on. """ here = connected(outputs) if not here: return [], [] internal = [item for item in here if is_internal(item)] external = [item for item in here if not is_internal(item)] if mode != EXTERNAL_ONLY or not external: return [item.get("name") for item in here], [] return ( [item.get("name") for item in external], [item.get("name") for item in internal], ) def changes(outputs, mode=ALL): """The subset of plan() that is not already true. Applying only what differs is what keeps a television power cycle from becoming a stream of mode sets, and keeps this quiet in the journal when there is nothing to do. """ enable, disable = plan(outputs, mode) state = { item.get("name"): item.get("enabled", True) for item in connected(outputs) } return ( [name for name in enable if not state.get(name, False)], [name for name in disable if state.get(name, False)], )