Skip to content

Instantly share code, notes, and snippets.

@sudocurse
Created July 23, 2025 14:14
Show Gist options
  • Save sudocurse/714ed0f054699d838fba0452ec790339 to your computer and use it in GitHub Desktop.
Save sudocurse/714ed0f054699d838fba0452ec790339 to your computer and use it in GitHub Desktop.
forwarding touchdesigner's running REPL
import code
import socket
import sys
'''
Forwarding a shell out of TD's textport
1. clean any leftover ports
cleanup_ports_simple([8889, 8890, 8891])
2. create a new server and keep the socket around in a list so you can clean it up later
servers = [] # list of sockets
servers.append(interact(8889))
this will block in TD.
3. connect from your shell. i'd recommend using `socat` or at least rlwrap your telnet/netcat connection.
Here's an example session:
```
socat READLINE TCP:localhost:8889 8147ms
Python 3.11.1 (heads/3.11-Derivative-dirty:0d650c50f2, Jan 26 2023, 17:30:19) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> op('/project1')
type:containerCOMP path:/project1
>>> op('/project1').name
'project1'
>>> for child in op('project1').findChildren():
... print(child)
...
/project1/box1
/project1/cam1
/project1/cam1/file1
/project1/geo1
/project1/geo1/in1
/project1/geo2
/project1/geo2/in1
/project1/res1
/project1/comp1
/project1/geo11
/project1/geo11/in1
/project1/geo12
/project1/geo12/in1
/project1/geo13
/project1/geo13/in1
/project1/info1
/project1/math1
/project1/math2
/project1/math3
/project1/null1
...
```
4. when you're done, to free up the port (so you don't get "Address is already in use" errors):
cleanup_server_simple(8889)
this is the same as the cleanup_ports function.
if you have a bunch lying around you can just throw in the whole servers list
cleanup_servers(servers)
'''
def cleanup_server_simple(port):
"""Try to bind to a specific port to force cleanup"""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', port))
s.close()
print(f"Port {port} is now free")
except OSError as e:
print(f"Port {port}: {e}")
def cleanup_ports_simple(ports=[8888, 8889, 8890, 8891, 8892]):
for port in ports:
cleanup_server_simple(port)
def cleanup_servers(servers):
for server in servers[:]:
try:
server.close()
servers.remove(server)
print(f"Closed and removed server")
except Exception as e:
print(f"Error closing server: {e}")
servers = []
def start_server(port=8889):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('localhost', port))
server.listen(1)
print(f"Python console available at: socat localhost {port}") # rlwrap'ed telnet might also be usable
return server
def wait_for_connection(server):
conn, addr = server.accept()
print(f"Connection from {addr}")
return conn
def setup_io_redirect(conn):
socket_file = conn.makefile('rw')
orig_stdin = sys.stdin
orig_stdout = sys.stdout
orig_stderr = sys.stderr
sys.stdin = socket_file
sys.stdout = socket_file
sys.stderr = socket_file
return socket_file, (orig_stdin, orig_stdout, orig_stderr)
def restore_io(socket_file, originals):
orig_stdin, orig_stdout, orig_stderr = originals
sys.stdin = orig_stdin
sys.stdout = orig_stdout
sys.stderr = orig_stderr
socket_file.close()
def interact(port=8889):
server = start_server(port)
conn = wait_for_connection(server)
socket_file, originals = setup_io_redirect(conn)
try:
code.interact(local=globals())
finally:
restore_io(socket_file, originals)
conn.close()
return server
@sudocurse
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment