"""Hyprland windows, workspaces, and launching config-declared commands.""" import json import re import subprocess from pathlib import Path from ..module import Module, ModuleError ADDR = re.compile(r"0x[0-9a-f]{1,16}") 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 _popen(cmd): subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 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 workspace_cmd(n: int, lua: bool): if lua: return ["hyprctl", "eval", f'hl.dispatch(hl.dsp.focus({{workspace = "{n}"}}))'] return ["hyprctl", "dispatch", "workspace", str(n)] def window_cmd(verb: str, addr: str, lua: bool): if lua: dsp = "hl.dsp.focus" if verb == "focus" else "hl.dsp.window.close" return ["hyprctl", "eval", f'{dsp}({{window = "address:{addr}"}})'] return ["hyprctl", "dispatch", "focuswindow" if verb == "focus" else "closewindow", f"address:{addr}"] def _state(run): try: clients = json.loads(run(["hyprctl", "clients", "-j"]) or "[]") active = json.loads(run(["hyprctl", "activewindow", "-j"]) or "{}").get("address") monitors = json.loads(run(["hyprctl", "monitors", "-j"]) or "[]") except json.JSONDecodeError: return {"windows": [], "workspaces": [], "activeClass": None} wins = sorted( ({"address": c["address"], "class": c["class"], "title": c["title"], "workspace": c["workspace"]["id"], "active": c["address"] == active} for c in clients if c.get("mapped", True) and c["workspace"]["id"] > 0), key=lambda w: (w["workspace"], w["class"])) active_class = next((w["class"] for w in wins if w["active"]), None) return {"windows": wins, "workspaces": [m["activeWorkspace"]["id"] for m in monitors], "activeClass": active_class} def make(cfg: dict) -> Module: run = cfg.get("run", _run) lua = hypr_lua(cfg) launch = cfg.get("launch", {}) def workspace(body): n = body.get("n") if not isinstance(n, int) or not 1 <= n <= 99: raise ModuleError("workspace must be a number from 1 to 99") _popen(workspace_cmd(n, lua)) def focus(body): addr = str(body.get("address", "")) if not ADDR.fullmatch(addr): raise ModuleError("bad window address") _popen(window_cmd("focus", addr, lua)) def close(body): addr = str(body.get("address", "")) if not ADDR.fullmatch(addr): raise ModuleError("bad window address") _popen(window_cmd("close", addr, lua)) def launch_key(body): key = str(body.get("key", "")) command = launch.get(key) if command is None: raise ModuleError("unknown launch key") _popen(["sh", "-c", command] if isinstance(command, str) else command) return Module(name="windows", title="Windows", state=lambda: _state(run), actions={"workspace": workspace, "focus": focus, "close": close, "launch": launch_key})