#!/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())