#!/usr/bin/python3 """Watch for HDMI connection state change and update screen layout.""" import json import os import subprocess import sys import time from typing import Any import pyudev context = pyudev.Context() monitor = pyudev.Monitor.from_netlink(context) # description of an HDMI connection state change want = { "action": "change", "device_node": "/dev/dri/card0", "device_type": "drm_minor", "sys_path": "/sys/devices/pci0000:00/0000:00:02.0/drm/card0", "sys_name": "card0", } def run_swaymsg() -> bytes: """Run swaymsg to get list of outputs.""" p = subprocess.run(["swaymsg", "-r", "-t", "get_outputs"], capture_output=True) return p.stdout def parse_json(outputs: bytes) -> dict[str, dict[str, Any]]: """Parse get_outputs JSON.""" try: json_data = json.loads(outputs) except json.decoder.JSONDecodeError: print("JSON parse error") print(outputs) raise return {o["name"]: o for o in json_data} def get_outputs(attempts: int = 10) -> dict[str, dict[str, Any]] | None: """Ask sway for the current list of outputs.""" for attempt in range(attempts): outputs = run_swaymsg() if outputs.strip() == b"": return None try: return parse_json(outputs) except json.decoder.JSONDecodeError: if attempt == attempts - 1: raise time.sleep(1) return None def is_sony_tv(output: dict[str, Any]) -> bool: """Is a given output a Sony TV.""" return bool(output["make"] == "Sony" and output["model"] == "SONY TV") def sony_tv_connected() -> None: """Run command when HDMI is connected.""" subprocess.run([os.path.expanduser("~/bin/desk")]) def hdmi_disconnected() -> None: """Run command when HDMI is disconnected.""" subprocess.run([os.path.expanduser("~/bin/unplugged")]) def is_output_connected(name: str) -> bool: """Is HDMI connected.""" state = open(f"/sys/class/drm/card0-{name}/status").read() states = {"connected\n": True, "disconnected\n": False} if state not in states: print(f"unknown state: [{state}]") sys.exit(1) return states[state] def handle_state_change() -> str: """Something happened, check and handle state change.""" hdmi_name = "HDMI-A-1" if not is_output_connected(hdmi_name): hdmi_disconnected() return "HDMI disconnected, switching to laptop screen." outputs = get_outputs() hdmi = outputs and outputs.get(hdmi_name) if hdmi and is_sony_tv(hdmi): sony_tv_connected() return "Sony TV connected to HDMI, switching to desk layout." return "Unknown device connected to HDMI, doing nothing." def watch() -> None: """Watch for state change and respond.""" d: pyudev.Device for d in iter(monitor.poll, None): if all(getattr(d, key) == value for key, value in want.items()): print(handle_state_change()) if __name__ == "__main__": handle_state_change() watch()