aboutsummaryrefslogtreecommitdiffstats
path: root/deckit/modules/windows.py
blob: 022bdd72afba38113593bf395028e61078c26296 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""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})