#!/bin/sh
# wren-dim: written by Wren Linux parts/core-commands.sh
# Makes it plain which window has the focus: the other windows on its workspace fade, so
# whatever is behind them (the wallpaper, or the window under a pop-up) shows through. sway can't
# darken a window, only make it see-through. Windows on other workspaces and screens are left alone.
#   wren-dim            run (sway starts it)
#   wren-dim off        stop, for good; every window goes back
#   wren-dim on         start again
#   wren-dim 0.4        how much they fade, from 0.1 to 0.9; 0.3 unless you choose (it's kept)
#   wren-dim status
# It only reads window numbers from sway, and only sets opacity. It fades relative to what a
# window had, so a window that is already see-through stays that way when it has the focus.
# (the name after "-" is how a later wren-dim tells this one is running)
WREN_DIM_SELF=$(readlink -f -- "$0") exec python3 -I - wren-dim "$@" <<'PY'
import json
import os
import signal
import subprocess
import sys

CONFIG_HOME = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
SETTING = os.path.join(CONFIG_HOME, "wren", "dim")
RUN = os.environ.get("XDG_RUNTIME_DIR") or "/tmp"
PID = os.path.join(RUN, "wren-dim.pid")
DEFAULT = 0.3


def setting():
    try:
        with open(SETTING) as f:
            return f.read().strip()
    except OSError:
        return ""


def strength():
    try:
        s = float(setting())
    except ValueError:
        return DEFAULT
    return s if 0.1 <= s <= 0.9 else DEFAULT


def running():
    try:
        with open(PID) as f:
            pid = int(f.read())
        with open(f"/proc/{pid}/cmdline", "rb") as f:
            return pid if b"wren-dim" in f.read() else None
    except (OSError, ValueError):
        return None


def stop():
    pid = running()
    if pid:
        os.kill(pid, signal.SIGTERM)
        for _ in range(50):
            if not running():
                break
            subprocess.run(["sleep", "0.1"])


def keep(value):
    os.makedirs(os.path.dirname(SETTING), exist_ok=True)
    with open(SETTING, "w") as f:
        f.write(value + "\n")


def start():
    subprocess.Popen([os.environ["WREN_DIM_SELF"]], start_new_session=True, stdin=subprocess.DEVNULL,
                     stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def swaymsg(command):
    subprocess.run(["swaymsg", "-q", command], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def to_dim():
    """The numbers of the windows to fade: the others on the focused window's workspace."""
    out = subprocess.run(["swaymsg", "-t", "get_tree"], capture_output=True, text=True).stdout
    tree = json.loads(out)
    dim = set()

    def windows(node, focused, found):
        focused = focused or node.get("focused", False)
        kids = node.get("nodes", []) + node.get("floating_nodes", [])
        if not kids and node.get("type") in ("con", "floating_con"):
            found.append((node["id"], focused))
        for k in kids:
            windows(k, focused, found)

    def workspaces(node):
        if node.get("type") == "workspace":
            found = []
            windows(node, False, found)
            if any(f for _, f in found):
                dim.update(i for i, f in found if not f)
            return
        for k in node.get("nodes", []):
            workspaces(k)

    workspaces(tree)
    return dim


def run():
    if setting() == "off" or running():
        return 0
    with open(PID, "w") as f:
        f.write(str(os.getpid()))
    fade = strength()
    dimmed = set()

    def apply(want):
        nonlocal dimmed
        cmds = [f"[con_id={i}] opacity minus {fade}" for i in want - dimmed]
        cmds += [f"[con_id={i}] opacity plus {fade}" for i in dimmed - want]
        if cmds:
            swaymsg("; ".join(cmds))
        dimmed = set(want)

    def finish(*_):
        apply(set())
        try:
            os.unlink(PID)
        except OSError:
            pass
        os._exit(0)

    signal.signal(signal.SIGTERM, finish)
    signal.signal(signal.SIGINT, finish)
    watch = subprocess.Popen(["swaymsg", "-m", "-t", "subscribe", '["window","workspace"]'],
                             stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
    apply(to_dim())
    # (sway's events give whole objects, one after another: only the fact of one matters here)
    for _ in watch.stdout:
        try:
            apply(to_dim())
        except (ValueError, KeyError):
            pass
    finish()


def main():
    arg = sys.argv[2] if len(sys.argv) > 2 else ""
    if not arg:
        return run()
    if arg == "status":
        print(f"on, fading {strength()}" if running() else "off")
        return 0
    if arg == "off":
        keep("off")
        stop()
        return 0
    if arg == "on":
        if setting() == "off":
            keep(str(DEFAULT))
    else:
        try:
            value = float(arg)
        except ValueError:
            value = -1
        if not 0.1 <= value <= 0.9:
            print("usage: wren-dim [on | off | status | a number from 0.1 to 0.9]", file=sys.stderr)
            return 2
        keep(arg)
    stop()
    start()
    return 0


sys.exit(main())
PY
