1import os 2import sys 3import string 4from Tkinter import * 5from ScrolledText import ScrolledText 6from Dialog import Dialog 7import signal 8 9BUFSIZE = 512 10 11class ShellWindow(ScrolledText): 12 13 def __init__(self, master=None, shell=None, **cnf): 14 if not shell: 15 try: 16 shell = os.environ['SHELL'] 17 except KeyError: 18 shell = '/bin/sh' 19 shell = shell + ' -i' 20 args = string.split(shell) 21 shell = args[0] 22 23 apply(ScrolledText.__init__, (self, master), cnf) 24 self.pos = '1.0' 25 self.bind('<Return>', self.inputhandler) 26 self.bind('<Control-c>', self.sigint) 27 self.bind('<Control-t>', self.sigterm) 28 self.bind('<Control-k>', self.sigkill) 29 self.bind('<Control-d>', self.sendeof) 30 31 self.pid, self.fromchild, self.tochild = spawn(shell, args) 32 self.tk.createfilehandler(self.fromchild, READABLE, 33 self.outputhandler) 34 35 def outputhandler(self, file, mask): 36 data = os.read(file, BUFSIZE) 37 if not data: 38 self.tk.deletefilehandler(file) 39 pid, sts = os.waitpid(self.pid, 0) 40 print 'pid', pid, 'status', sts 41 self.pid = None 42 detail = sts>>8 43 cause = sts & 0xff 44 if cause == 0: 45 msg = "exit status %d" % detail 46 else: 47 msg = "killed by signal %d" % (cause & 0x7f) 48 if cause & 0x80: 49 msg = msg + " -- core dumped" 50 Dialog(self.master, 51 text=msg, 52 title="Exit status", 53 bitmap='warning', 54 default=0, 55 strings=('OK',)) 56 return 57 self.insert(END, data) 58 self.pos = self.index("end - 1 char") 59 self.yview_pickplace(END) 60 61 def inputhandler(self, *args): 62 if not self.pid: 63 self.no_process() 64 return "break" 65 self.insert(END, "\n") 66 line = self.get(self.pos, "end - 1 char") 67 self.pos = self.index(END) 68 os.write(self.tochild, line) 69 return "break" 70 71 def sendeof(self, *args): 72 if not self.pid: 73 self.no_process() 74 return "break" 75 os.close(self.tochild) 76 return "break" 77 78 def sendsig(self, sig): 79 if not self.pid: 80 self.no_process() 81 return "break" 82 os.kill(self.pid, sig) 83 return "break" 84 85 def sigint(self, *args): 86 return self.sendsig(signal.SIGINT) 87 88 def sigquit(self, *args): 89 return self.sendsig(signal.SIGQUIT) 90 91 def sigterm(self, *args): 92 return self.sendsig(signal.SIGTERM) 93 94 def sigkill(self, *args): 95 return self.sendsig(signal.SIGKILL) 96 97 def no_process(self): 98 Dialog(self.master, 99 text="No active process", 100 title="No process", 101 bitmap='error', 102 default=0, 103 strings=('OK',)) 104 105MAXFD = 100 # Max number of file descriptors (os.getdtablesize()???) 106 107def spawn(prog, args): 108 p2cread, p2cwrite = os.pipe() 109 c2pread, c2pwrite = os.pipe() 110 pid = os.fork() 111 if pid == 0: 112 # Child 113 for i in 0, 1, 2: 114 try: 115 os.close(i) 116 except os.error: 117 pass 118 if os.dup(p2cread) <> 0: 119 sys.stderr.write('popen2: bad read dup\n') 120 if os.dup(c2pwrite) <> 1: 121 sys.stderr.write('popen2: bad write dup\n') 122 if os.dup(c2pwrite) <> 2: 123 sys.stderr.write('popen2: bad write dup\n') 124 os.closerange(3, MAXFD) 125 try: 126 os.execvp(prog, args) 127 finally: 128 sys.stderr.write('execvp failed\n') 129 os._exit(1) 130 os.close(p2cread) 131 os.close(c2pwrite) 132 return pid, c2pread, p2cwrite 133 134def test(): 135 shell = string.join(sys.argv[1:]) 136 root = Tk() 137 root.minsize(1, 1) 138 if shell: 139 w = ShellWindow(root, shell=shell) 140 else: 141 w = ShellWindow(root) 142 w.pack(expand=1, fill=BOTH) 143 w.focus_set() 144 w.tk.mainloop() 145 146if __name__ == '__main__': 147 test() 148