#! /usr/bin/env python import os import sys import signal import errno import copy import pty import optparse import termios import asyncore class FileDispatcher(asyncore.file_dispatcher): def __init__(self, in_fd, out_fd, quit_char=None): asyncore.file_dispatcher.__init__(self, in_fd) self.out_fd = out_fd self.quit_char = quit_char def writable(self): return False def handle_read(self): # called from the select loop whenever data is available try: data = self.recv(1024) # terminate the select loop if data == self.quit_char: raise asyncore.ExitNow( '\n%s terminated.' % os.path.basename(sys.argv[0])) os.write(self.out_fd, data) except OSError, err: if err[0] != errno.EAGAIN: raise asyncore.ExitNow(err) def parse_options(argv): formatter = optparse.IndentedHelpFormatter(max_help_position=30) parser = optparse.OptionParser( usage='usage: python %prog [options]', formatter=formatter) parser.add_option('-e', '--exec', type='string', dest='pgm', help='program to spawn in a new xterm terminal ' 'that will use the slave pseudo terminal') parser.add_option('-t', '--tty', type='string', default='-tty', metavar='TTY_OPT', help='pass to PGM the slave pseudo terminal name using' ' the TTY_OPT option (default \'%default\')') parser.add_option('-a', '--args', type='string', help='PGM arguments (must be quoted)') parser.add_option('-m', '--master_fd', type='int', metavar='FD', help='master pseudo terminal file descriptor to use') (options, args) = parser.parse_args(args=argv) return options def spawn_xterm(options, ptyname, slave_fd, master_fd): argv = ['xterm', '-e', options.pgm, options.tty, ptyname] if options.args is not None: # FIXME quotes in args are not handled argv.extend(options.args.split()) pid = os.fork() if pid == 0: os.close(slave_fd) os.close(master_fd) os.setsid() os.execvp(argv[0], argv) master_fd = None def sigint_handler(signum, frame): # write a C-c character if master_fd is not None: os.write(master_fd, chr(3)) def main(): global master_fd options = parse_options(sys.argv[1:]) master_fd = options.master_fd if not isinstance(master_fd, int): master_fd, slave_fd = pty.openpty() ptyname = os.ttyname(slave_fd) print "Slave pseudo terminal to use: '%s'" % ptyname if options.pgm is not None: spawn_xterm(options, ptyname, slave_fd, master_fd) print 'Type C-a to exit.\n' signal.signal(signal.SIGINT, sigint_handler) # no echo, no canonical processing attr = termios.tcgetattr(0) init_attr = copy.deepcopy(attr) attr[3] = attr[3] & ~termios.ECHO attr[3] = attr[3] & ~termios.ICANON termios.tcsetattr(0, termios.TCSADRAIN, attr) # interconnect stdin to master_fd, and master_fd to stdout # with an asyncore select loop # C-a is the Quit character master_slave = FileDispatcher(0, master_fd, chr(1)) slave_master = FileDispatcher(master_fd, 1) err = '' try: while asyncore.socket_map: asyncore.poll() except asyncore.ExitNow, err: pass os.close(slave_fd) os.close(master_fd) termios.tcsetattr(0, termios.TCSADRAIN, init_attr) if err: print err if __name__ == '__main__': main()