"""The virtual monitor half: a headless Hyprland output sized to the tablet.""" import json import os import re import shutil import signal import subprocess import sys import time from pathlib import Path def run(cmd, timeout=2): try: return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout).stdout.strip() except (OSError, subprocess.TimeoutExpired): return "" def tablet_resolution(cfg: dict, adb=None) -> str: res = cfg.get("display", {}).get("resolution", "auto") if res != "auto": return res read = adb or (lambda: run(["adb", "shell", "wm", "size"])) m = re.search(r"(\d+)x(\d+)", read()) if not m: sys.exit("Could not read the tablet resolution; set display.resolution in the config") w, h = sorted(map(int, m.groups()), reverse=True) return f"{w}x{h}" def hypr_lua(cfg: dict) -> bool: mode = cfg.get("hyprland", "auto") if mode == "auto": return (Path.home() / ".config/hypr/hyprland.lua").is_file() return mode == "lua" def _swaybg_for(output: str, proc: Path = Path("/proc")): """(pid, image, mode) of the swaybg process painting `output`, from /proc.""" for cmdline in proc.glob("[0-9]*/cmdline"): try: argv = cmdline.read_bytes().decode(errors="replace").split("\0") except OSError: continue if not argv or not argv[0].endswith("swaybg"): continue out = image = None mode = "fill" for i, arg in enumerate(argv): if i + 1 >= len(argv): break if arg == "-o": out = argv[i + 1] elif arg == "-i": image = argv[i + 1] elif arg == "-m": mode = argv[i + 1] if out == output and image: return int(cmdline.parent.name), image, mode return None def setup_display(cfg: dict, run_cmd=None, popen=None) -> None: run_cmd = run_cmd or run popen = popen or (lambda cmd: subprocess.Popen( cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)) d = cfg.get("display", {}) mon = d.get("name", "TAB") monitors = json.loads(run_cmd(["hyprctl", "monitors", "-j"]) or "[]") if not any(m["name"] == mon for m in monitors): run_cmd(["hyprctl", "output", "create", "headless", mon]) time.sleep(1) mode = f"{tablet_resolution(cfg)}@{d.get('refresh', 60)}" pos = d.get("position", "auto-right") scale = d.get("scale", 1) if hypr_lua(cfg): run_cmd(["hyprctl", "eval", f'hl.monitor({{output="{mon}", mode="{mode}", position="{pos}", scale={scale}}})']) else: run_cmd(["hyprctl", "keyword", "monitor", f"{mon},{mode},{pos},{scale}"]) if d.get("wallpaper", True): others = [m["name"] for m in monitors if m["name"] != mon] tool = shutil.which("awww") or shutil.which("swww") if tool: for line in run_cmd([tool, "query"]).splitlines(): if others and others[0] in line and "image: " in line: run_cmd([tool, "img", "-o", mon, line.split("image: ", 1)[1]], timeout=10) break elif shutil.which("swaybg"): # swaybg runs one process per output; copy the first monitor's image. source = next((found for name in others if (found := _swaybg_for(name))), None) if source: existing = _swaybg_for(mon) if existing: try: os.kill(existing[0], signal.SIGTERM) except OSError: pass popen(["swaybg", "-o", mon, "-i", source[1], "-m", source[2]]) print(f"{mon}: {mode} at {pos}")