I'm trying to write a cross-platform, wrapper around console apps that will allow me to connect to their stdio from network/remote clients. It is a VERY pared down concept of what Terminado does. In Windows, I'm trying pywinpty so that functions like up-arrow and down-arrow work properly (i.e. move through recent command history).
Each new connection gets a "reader" task that writes directly to the subprocess stdin and there is a "writer" class that monitors the a global queue of pty output and forwards the bytes on to all connected clients. The connected clients are a list of writers in a global variable as is the PTY_PROC object.
There is also the pty_reader, that takes text from the PTY and puts it on the global byte queue.
I am testing using Telnet. I get some good data to the client, but some of it is garbled up. Mainly:
- when I type in characters, they are doubled up on the client being typed in but not on others
D:\sandbox\epics\pyProcServ\area_51>didrir
Local echo is off and the terminal type is ANSI.
- Some times it seems parts of escape codes are being printed.
When the client connects, this is what it gets:
0;C:\Windows\System32\cmd.exeMicrosoft Windows [Version 10.0.18363.1556][9C
(c) 2019 Microsoft Corporation. All rights reserved.
52C
D:\sandbox\epics\pyProcServ\area_51>[16C
So far, I've tried matching columns (80) and rows (24) and have been looking in to different terminal settings (i.e VT100, etc), but to no avail.
I'm hoping it's something simple and "obvious".
The core essentials (I believe) are here:
async def client_reader(reader, addr = ''):
# Monitor incoming data and put the bytes in to the subproc stdin
cmd = ''
while True:
try:
s = await reader.read(1024)
except asyncio.CancelledError as e:
break
PTY_PROC.write(s.decode())
print(f"client_reader ({addr}): Cancelled and tidying up...")
return(f"client_reader ({addr})")
async def write_clients():
# Watches the global CLIENTS_QUEUE and writes bytes out to
# all the clients in "WRITERS"
data = None
while True:
try:
data = await CLIENTS_QUEUE.get()
if not data:
break
for writer in WRITERS:
if data != None:
writer.write(data.encode())
await writer.drain()
except asyncio.CancelledError:
break
for writer in WRITERS:
# flush existing data to clients
if data != None:
writer.write(data.encode())
writer.write(b"\r\n\r\n### Server shutting down or restarting...")
await writer.drain()
writer.close()
await writer.wait_closed()
print(f"write_clients exiting.")
return("write_clients")
async def pty_output():
# Read chars/strings from pty and place on byte queue.
global CACHED_OUTPUT
timeout = 0.1
s = ''
while True:
try:
r, _, _ = select.select([PTY_PROC.fd], [], [], timeout)
if not r:
await asyncio.sleep(0.1)
continue
s = PTY_PROC.read(1024)
await CLIENTS_QUEUE.put(s.encode())
except asyncio.CancelledError:
print(f"pty_output: Cancelled and tidying up...")
break
if len(s) > 0: # flush any remaining data to the queue
await CLIENTS_QUEUE.put(s.encode())
return("pty_output")