aboutsummaryrefslogtreecommitdiffstats
path: root/tools/gbtype
blob: c741c8514792594758dc0ad50920edd598ff7ac7 (plain) (blame)
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
#!/usr/bin/env python3
"""gbtype - inject shell input into a running gbhub Game Boy, no OSK needed.

The Game Boy's link port *is* its console when nothing SLIP-frames the bytes,
so text sent over the hub's control socket lands straight on the shell's stdin
- exactly as if typed on the on-screen keyboard, but driven from your host.

Start the hub first (in another terminal):   sudo tools/gbhub --daemon

  tools/gbtype 'ls -l'         run 'ls -l' on GB0 (a trailing Enter is added)
  tools/gbtype -g 1 ps         target GB1 (the Nth joined Game Boy)
  tools/gbtype -n abc          type 'abc' but no Enter (leave it on the line)
  tools/gbtype -r              just press Enter
  tools/gbtype -l              list the connected Game Boys
  printf 'ls\\ncat readme\\n' | tools/gbtype     pipe a script via stdin

Options must come before the command; the first non-option word starts the
literal text (so 'gbtype ls -l' needs no quoting, and '-l' there is not --list).
No root needed - the hub opens the control socket world-connectable."""
import os, sys, socket

CTL = "/tmp/gbhub.ctl"

def usage(code=0):
    (sys.stdout if code == 0 else sys.stderr).write(__doc__ + "\n")
    sys.exit(code)

def send(header, payload=b""):
    if not os.path.exists(CTL):
        sys.exit("gbtype: no hub control socket at %s\n"
                 "        start it first:  sudo tools/gbhub --daemon" % CTL)
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    try: s.connect(CTL)
    except OSError as e: sys.exit("gbtype: cannot connect %s (%s)" % (CTL, e))
    s.sendall(header + b"\n" + payload)
    s.shutdown(socket.SHUT_WR)            # end-of-payload marker for the hub
    out = b""
    while True:
        try: d = s.recv(4096)
        except Exception: break
        if not d: break
        out += d
    s.close()
    text = out.decode(errors="replace")
    if text.startswith("err"):            # make failures loud and scriptable
        sys.stderr.write(text)
        sys.exit(1)
    sys.stdout.write(text)

def main():
    args = sys.argv[1:]
    idx = None
    newline = True
    i = 0
    while i < len(args):
        a = args[i]
        if a in ("-h", "--help"): usage(0)
        elif a in ("-l", "--list"): send(b"list"); return
        elif a in ("-g", "--gb"):
            i += 1
            if i >= len(args): usage(1)
            idx = args[i]
        elif a in ("-n", "--no-enter"): newline = False
        elif a in ("-r", "--enter"):
            send(b"type" + (b" " + idx.encode() if idx else b""), b"\n"); return
        elif a == "--": i += 1; break
        elif a.startswith("-") and len(a) > 1:
            sys.stderr.write("gbtype: unknown option %s\n" % a); usage(1)
        else: break                       # first positional: rest is literal text
        i += 1

    words = args[i:]
    if words:
        payload = " ".join(words).encode() + (b"\n" if newline else b"")
    else:
        payload = sys.stdin.buffer.read()             # piped script, sent verbatim
        if newline and payload and not payload.endswith(b"\n"):
            payload += b"\n"

    send(b"type" + (b" " + idx.encode() if idx is not None else b""), payload)

if __name__ == "__main__":
    main()