#!/usr/bin/env python """Terminal Emulator A python implementation of a shell window for wily, essentially a translation of win.c by Alan Watson, the README that covers win.c contains: | Copyright (c) 1996 by Alan Watson (alan@oldp.nmsu.edu). Written by | Alan Watson. Not derived from licensed software. | | Permission is granted to anyone to use this software for any purpose | on any computer system, and to redistribute it freely, subject to the | following restrictions: | | 1. The author is not responsible for the consequences of use of this | software, no matter how awful, even if they arise from defects in it. | | 2. The origin of this software must not be misrepresented, either by | explicit claim or by omission. | | 3. Altered versions must be plainly marked as such, and must not be | misrepresented as being the original software. Hence in the name of share and share alike: This file is Copyright (c) 2004 by Sam Holden (sam@holden.id.au). Written by Sam Holden. Derived from the above mentioned C program. Permission is granted to anyone to use this software for any purpose on any computer system, and to redistribute it freely, subject to the following restrictions: 1. The author is not responsible for the consequences of use of this software, no matter how awful, even if they arise from defects in it. 2. The origin of this software must not be misrepresented, either by explicit claim or by omission. 3. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. Currently this is a cut down implementation, it ignores command line arguments and hence always uses the name +winpy for the window and runs $SHELL or /bin/sh interactively. This will hopefully be remedied in the future. It is probably less portable since it does none of the system incompatability handling that win.c does, and I doubt the python library probably doesn't do enough for our purposes. BUGS: Ugly exit if the wily window id delted. """ __revision__ = "$Id" import wily import sys import os import pty import signal import termios import re class Win: interrupt = 0x03 #ctrl-C eof = 0x04 #ctrl-D def __init__(self, name, argv=None): if argv == None: argv = [] self.c = wily.Connection() self.w = self.c.win(name) self.w.attach(wily.WEexec|wily.WEreplace|wily.WEdestroy) self.w.set_cb(wily.WEexec, self._exec_cb) self.w.set_cb(wily.WEreplace, self._replace_cb) self.w.set_cb(wily.WEdestroy, sys.exit) self.shell_output = False self._fork_shell(argv) (win, rng) = self.w.goto((0,0), ':,') self.length = rng[1] self.output_point = rng[1] def loop(self): ws = self.c.socket_fd() while True: self.c.events_non_block() (rlist, wlist, xlist) = pty.select([ws, self.master], [], []) if self.master in rlist: self._shell_output() if ws in rlist: self.c.read_socket() def _fork_shell(self, argv): (self.master, self.slave) = pty.openpty() self.shellpid = os.fork() if self.shellpid == 0: self._child_fork(argv) else: self._parent_fork() def _child_fork(self, argv): # setup signal handlers signal.signal(signal.SIGCHLD, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGQUIT, signal.SIG_DFL) signal.signal(signal.SIGTSTP, signal.SIG_IGN) signal.signal(signal.SIGTTIN, signal.SIG_IGN) signal.signal(signal.SIGTTOU, signal.SIG_IGN) # close the master fd os.close(self.master) # create a session os.setsid() # configure termios attrs = termios.tcgetattr(self.slave) attrs[0] = 0 attrs[1] = ~termios.OPOST attrs[3] = termios.ISIG|termios.ICANON for i in xrange(len(attrs[6])): attrs[6][i] = 0 attrs[6][termios.VINTR] = self.interrupt attrs[6][termios.VEOF] = self.eof termios.tcsetattr(self.slave, termios.TCSANOW, attrs) # setup stdin, stdout, and stderr os.dup2(self.slave, 0) os.dup2(self.slave, 1) os.dup2(self.slave, 2) os.close(self.slave) # setup environment os.environ['TERM'] = 'win' os.environ['WIN_WINID'] = str(self.w.id) # exec the shell shell = os.getenv('SHELL', '/bin/sh') if argv: os.execl(shell, shell, '-c', ' '.join(argv)) else: os.execl(shell, shell, '-i') def _parent_fork(self): sigexit = lambda sig, sf: sys.exit() signal.signal(signal.SIGHUP, sigexit) signal.signal(signal.SIGINT, sigexit) signal.signal(signal.SIGQUIT, sigexit) signal.signal(signal.SIGTERM, sigexit) signal.signal(signal.SIGCHLD, self._sigchld) def _sigchld(self, sig, sf): signal.signal(signal.SIGCHLD, self._sigchld) (pid, status) = os.wait() if pid == self.shellpid: sys.exit() def _shell_output(self): data = os.read(self.master, 1024) self.w.replace((self.output_point, self.output_point), data) self.shell_output = True def _shell_input(self, msg): last = max(msg.s.rfind(chr(self.interrupt)), msg.s.rfind(chr(self.eof)), msg.s.rfind('\n')) if last != -1: last_point= msg.range[0]+len(msg.s[:last+1].decode('utf-8')) txt = self.w.read((self.output_point, last_point)) if os.write(self.master, txt) != len(txt): raise Exception, "write to master failed" self.output_point = last_point def _replace_cb(self, msg): len_change = len(msg.s.decode('utf-8')) - (msg.range[1] - msg.range[0]) self.length = self.length + len_change if self.shell_output: self.shell_output = False self.output_point = msg.range[0] + len_change #TODO: scroll the window... will require patching wily else: if self.output_point > msg.range[0]: self.output_point = self.output_point + len_change if len_change + msg.range[1] > self.output_point: self._shell_input(msg) def _exec_cb(self, msg): if re.match(r'\s*[<>|A-Z]', msg.s): self.c.bounce(msg) if len(msg.s) == 0: return if msg.s[-1] == '\n': cmd = msg.s else: cmd = msg.s + '\n' self.w.replace((self.length, self.length), cmd) if __name__ == '__main__': w = Win('+winpy') w.loop()