import argparse
import fcntl
import os
import pty
import select
import struct
import sys
import termios
import time
def set_win_size(fd, width, height):
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", height, width, 0, 0))
def run(cmd, args, width, height, keys, delay, settle, timeout, gap=0.4):
pid, master = pty.fork()
if pid == 0:
set_win_size(0, width, height)
os.environ["TERM"] = "xterm-256color"
os.execvp(cmd, [cmd] + args)
set_win_size(master, width, height)
out = bytearray()
start = time.time()
sent = False
sent_at = None
exited = False
while time.time() - start < timeout:
if not sent and time.time() - start >= delay and len(out) > 0:
if delay >= 0.1 and time.time() - start < 0.15:
time.sleep(0.15 - (time.time() - start))
sent = True
sent_at = time.time()
phases = keys.split(b"|")
for phase in phases:
for attempt in range(3):
try:
os.write(master, phase)
break
except OSError:
time.sleep(0.05)
if phase is not phases[-1]:
time.sleep(gap)
r, _, _ = select.select([master], [], [], 0.05)
if r:
try:
data = os.read(master, 4096)
except OSError:
break
if not data:
break
out.extend(data)
if os.environ.get("DRIVER_DEBUG"):
print("READ:", len(data), file=sys.stderr)
if sent and time.time() - sent_at >= settle:
break
wpid, status = os.waitpid(pid, os.WNOHANG)
if wpid == pid:
exited = True
break
if not exited:
try:
os.waitpid(pid, os.WNOHANG)
except ChildProcessError:
pass
try:
os.kill(pid, 9)
except ProcessLookupError:
pass
try:
os.waitpid(pid, 0)
except ChildProcessError:
pass
sys.stdout.buffer.write(bytes(out))
sys.stdout.buffer.flush()
def main():
p = argparse.ArgumentParser()
p.add_argument("--cmd", required=True)
p.add_argument("--args", nargs="*", default=[])
p.add_argument("--width", type=int, default=80)
p.add_argument("--height", type=int, default=24)
p.add_argument("--keys", default="q")
p.add_argument("--delay", type=float, default=0.8)
p.add_argument("--settle", type=float, default=0.8)
p.add_argument("--timeout", type=float, default=15.0)
p.add_argument("--gap", type=float, default=0.4)
args = p.parse_args()
keys = args.keys.encode("latin1").decode("unicode_escape").encode("latin1")
run(args.cmd, args.args, args.width, args.height, keys, args.delay, args.settle, args.timeout, args.gap)
if __name__ == "__main__":
main()