diff options
| author | user <user@clank> | 2026-07-17 20:31:00 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-17 20:31:00 +0200 |
| commit | 138e0ad2eb25ee078464c7e2ff7d6a919a188634 (patch) | |
| tree | 67090285cbd0ec47a4d2f87a93717f63aebf7d84 /tools/gbtype | |
| parent | irc: a bitchx/irssi-style IRC client (diff) | |
| download | gbos-138e0ad2eb25ee078464c7e2ff7d6a919a188634.tar.gz gbos-138e0ad2eb25ee078464c7e2ff7d6a919a188634.tar.xz gbos-138e0ad2eb25ee078464c7e2ff7d6a919a188634.zip | |
gbhub: control socket + gbtype to inject shell input without the OSK
Typing into a Game Boy meant driving the on-screen keyboard by hand. But
the link port already *is* the shell's console: bytes the hub sends
un-SLIP-framed land straight on stdin (that's what GB.type() has always
done for spawn-mode command scripting). This just exposes it.
gbhub now serves a control socket (/tmp/gbhub.ctl, world-connectable like
the join socket) accepting two commands: 'list' and 'type [idx]' followed
by a payload (delimited by the client's half-close). Injection is chunked
and paced to respect the 64-byte kernel console ring, so arbitrarily long
input - even multi-line scripts - reaches the shell without overflow. The
server runs in both daemon and spawn modes.
tools/gbtype is the client:
tools/gbtype 'ls -l' run a command on GB0 (Enter appended)
tools/gbtype -g 1 ps target the Nth joined Game Boy
tools/gbtype -n abc no trailing Enter
tools/gbtype -r just press Enter
tools/gbtype -l list connected Game Boys
printf 'ls\nuname\n' | tools/gbtype pipe a script via stdin
Options precede the command; the first non-option word starts literal
text, so 'gbtype ls -l' needs no quoting. No root required.
Verified end to end: injected list/uname/echo through the real CTL
protocol and saw the shell execute each and echo output back.
Diffstat (limited to '')
| -rwxr-xr-x | tools/gbtype | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/tools/gbtype b/tools/gbtype new file mode 100755 index 0000000..f58a1f8 --- /dev/null +++ b/tools/gbtype @@ -0,0 +1,79 @@ +#!/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() + sys.stdout.write(out.decode(errors="replace")) + +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() |
