From 8ec35a8a7ce2186c5f134db5cec04f5fc0e740ac Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Mon, 22 Sep 2014 12:43:43 -0400 Subject: need to override some stuff in the core pty module hopefully this can make its way upstream eventually (pity as in "it's such a pity i had to write this code *again*") --- pity.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ termcast_client.py | 8 +++-- 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 pity.py diff --git a/pity.py b/pity.py new file mode 100644 index 0000000..26a9e49 --- /dev/null +++ b/pity.py @@ -0,0 +1,94 @@ +import fcntl +import os +import pty +import signal +import termios +import tty + +CHILD = pty.CHILD +STDIN_FILENO = pty.STDIN_FILENO +STDOUT_FILENO = pty.STDOUT_FILENO +STDERR_FILENO = pty.STDERR_FILENO + +def fork(handle_window_size=False): + # copied from pty.py, with modifications + master_fd, slave_fd = openpty() + slave_name = os.ttyname(slave_fd) + pid = os.fork() + if pid == CHILD: + # Establish a new session. + os.setsid() + os.close(master_fd) + + if handle_window_size: + clone_window_size_from(slave_name, STDIN_FILENO) + + # Slave becomes stdin/stdout/stderr of child. + os.dup2(slave_fd, STDIN_FILENO) + os.dup2(slave_fd, STDOUT_FILENO) + os.dup2(slave_fd, STDERR_FILENO) + if (slave_fd > STDERR_FILENO): + os.close (slave_fd) + + # Explicitly open the tty to make it become a controlling tty. + tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR) + os.close(tmp_fd) + else: + os.close(slave_fd) + + # Parent and child process. + return pid, master_fd, slave_name + + +def openpty(): + return pty.openpty() + +def spawn(argv, master_read=pty._read, stdin_read=pty._read, handle_window_size=False): + # copied from pty.py, with modifications + # note that it references a few private functions - would be nice to not + # do that, but you know + if type(argv) == type(''): + argv = (argv,) + pid, master_fd, slave_name = fork(handle_window_size) + if pid == CHILD: + os.execlp(argv[0], *argv) + try: + mode = tty.tcgetattr(STDIN_FILENO) + tty.setraw(STDIN_FILENO) + restore = 1 + except tty.error: # This is the same as termios.error + restore = 0 + + if handle_window_size: + signal.signal( + signal.SIGWINCH, + lambda signum, frame: _winch(slave_name, pid) + ) + + while True: + try: + pty._copy(master_fd, master_read, stdin_read) + except InterruptedError: + continue + except OSError: + if restore: + tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) + break + + os.close(master_fd) + return os.waitpid(pid, 0)[1] + +def clone_window_size_from(slave_name, from_fd): + slave_fd = os.open(slave_name, os.O_RDWR) + try: + fcntl.ioctl( + slave_fd, + termios.TIOCSWINSZ, + fcntl.ioctl(from_fd, termios.TIOCGWINSZ, " " * 1024) + ) + finally: + os.close(slave_fd) + +def _winch(slave_name, child_pid): + clone_window_size_from(slave_name, STDIN_FILENO) + os.kill(child_pid, signal.SIGWINCH) diff --git a/termcast_client.py b/termcast_client.py index 661f803..2e6ad6c 100644 --- a/termcast_client.py +++ b/termcast_client.py @@ -1,7 +1,7 @@ import argparse import json import os -import pty +import pity import shutil import socket import sys @@ -17,7 +17,11 @@ class Client(object): sock = socket.socket() sock.connect((self.host, self.port)) sock.send(self._build_connection_string()) - pty.spawn(argv, lambda fd: self._master_read(fd, sock)) + pity.spawn( + argv, + lambda fd: self._master_read(fd, sock), + handle_window_size=True + ) def _master_read(self, fd, sock): data = os.read(fd, 1024) -- cgit v1.2.3