git/intake/main
scripts/grid-probe.py
114 lines3 KB last change raw · history
#!/usr/bin/env python3
"""Run the TUI as a kitty terminal and report what the cover grid emitted.

Usage: scripts/grid-probe.py [--root DIR] [--width N] [--height N]
"""
import argparse, fcntl, os, pty, re, select, struct, sys, termios, time

APC = b"\x1b_G"

class Session:
    def __init__(self, args):
        self.raw = b""
        env = dict(os.environ)
        env["TERM"] = "xterm-kitty"
        env["COLORTERM"] = "truecolor"
        env["LINES"] = str(args.height)
        env["COLUMNS"] = str(args.width)
        pid, fd = pty.fork()
        if pid == 0:
            os.environ.update(env)
            os.execv(args.binary, [args.binary, "tui", "--root", args.root])
        self.fd = fd
        fcntl.ioctl(fd, termios.TIOCSWINSZ,
                    struct.pack("HHHH", args.height, args.width, 0, 0))
        self.pid = pid

    def drain(self, seconds):
        end = time.time() + seconds
        out = b""
        while time.time() < end:
            r, _, _ = select.select([self.fd], [], [], 0.1)
            if not r:
                continue
            try:
                chunk = os.read(self.fd, 1 << 20)
            except OSError:
                break
            if not chunk:
                break
            out += chunk
        self.raw += out
        return out

    def send(self, keys):
        os.write(self.fd, keys)
        return self.drain(0.8)

    def close(self):
        try:
            os.close(self.fd)
        except OSError:
            pass
        try:
            os.kill(self.pid, 9)
        except ProcessLookupError:
            pass
        os.waitpid(self.pid, 0)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", default="/tmp/intake-tour")
    ap.add_argument("--binary", default="./out/intake")
    ap.add_argument("--width", type=int, default=104)
    ap.add_argument("--height", type=int, default=34)
    ap.add_argument("--save", default="", help="write the raw output here")
    args = ap.parse_args()

    s = Session(args)
    try:
        s.drain(3.0)
        # browse -> metadata -> track names -> cover art -> search for candidates
        for _ in range(4):
            os.write(s.fd, b"\r")
            s.drain(2.0)
        s.drain(3.0)
    finally:
        s.close()

    data = s.raw
    if args.save:
        with open(args.save, "wb") as f:
            f.write(data)
        print(f"raw output saved to {args.save}")
    print(f"captured {len(data)} bytes")
    print(f"graphics commands: {data.count(APC)}")
    cells = data.count("\U0010EEEE".encode())
    print(f"placeholder cells: {cells}")
    for label, pat in [
        ("38:2:...m (colon truecolor)", rb"\x1b\[38:2:[0-9]+:[0-9]+:[0-9]+m"),
        ("38;2;...m (semicolon truecolor)", rb"\x1b\[38;2;[0-9]+;[0-9]+;[0-9]+m"),
        ("38;5;...m (256 colour)", rb"\x1b\[38;5;[0-9]+m"),
        ("38:5:...m", rb"\x1b\[38:5:[0-9]+m"),
    ]:
        found = set(re.findall(pat, data))
        print(f"{label}: {len(found)} distinct")
        for c in list(found)[:6]:
            print(f"    {c.decode()}")
    # A grid row is a run of cells; report how wide the widest run is.
    widest = 0
    for line in data.split(b"\n"):
        n = line.count("\U0010EEEE".encode())
        widest = max(widest, n)
    print(f"widest grid row:   {widest} cells")
    if cells == 0:
        print("\nNO PLACEHOLDER CELLS REACHED THE TERMINAL")
        return 1
    print("\nthe grid reached the terminal")
    return 0


if __name__ == "__main__":
    sys.exit(main())