demonstrate shell in a Tk window
diff --git a/Demo/tkinter/guido/ShellWindow.py b/Demo/tkinter/guido/ShellWindow.py
new file mode 100755
index 0000000..93a13d6
--- /dev/null
+++ b/Demo/tkinter/guido/ShellWindow.py
@@ -0,0 +1,164 @@
+import os
+import sys
+import string
+from Tkinter import *
+from ScrolledText import ScrolledText
+from Dialog import Dialog
+import signal
+
+TK_READABLE  = 1
+TK_WRITABLE  = 2
+TK_EXCEPTION = 4
+
+BUFSIZE = 512
+
+class ShellWindow(ScrolledText):
+
+	def __init__(self, master = None, cnf = {}):
+		try:
+			shell = cnf['shell']
+			del cnf['shell']
+		except KeyError:
+			try:
+				shell = os.environ['SHELL']
+			except KeyError:
+				shell = '/bin/sh'
+			shell = shell + ' -i'
+		args = string.split(shell)
+		shell = args[0]
+
+		ScrolledText.__init__(self, master, cnf)
+		self.pos = '1.0'
+		self.bind('<Return>', self.inputhandler)
+		self.bind('<Control-c>', self.sigint)
+		self.bind('<Control-t>', self.sigterm)
+		self.bind('<Control-k>', self.sigkill)
+		self.bind('<Control-d>', self.sendeof)
+
+		self.pid, self.fromchild, self.tochild = spawn(shell, args)
+		self.tk.createfilehandler(self.fromchild, TK_READABLE,
+					  self.outputhandler)
+
+	def outputhandler(self, file, mask):
+		data = os.read(file, BUFSIZE)
+		if not data:
+			self.tk.deletefilehandler(file)
+			pid, sts = os.waitpid(self.pid, 0)
+			print 'pid', pid, 'status', sts
+			self.pid = None
+			detail = sts>>8
+			cause = sts & 0xff
+			if cause == 0:
+				msg = "exit status %d" % detail
+			else:
+				msg = "killed by signal %d" % (cause & 0x7f)
+				if cause & 0x80:
+					msg = msg + " -- core dumped"
+			Dialog(self.master, {
+				'text': msg,
+				'title': "Exit status",
+				'bitmap': 'warning',
+				'default': 0,
+				'strings': ('OK',),
+			})
+			return
+		self.insert('end', data)
+		self.pos = self.index('end')
+		self.yview_pickplace('end')
+
+	def inputhandler(self, *args):
+		if not self.pid:
+			Dialog(self.master, {
+				'text': "No active process",
+				'title': "No process",
+				'bitmap': 'error',
+				'default': 0,
+				'strings': ('OK',),
+			})
+			return
+		self.insert('end', '\n')
+		line = self.get(self.pos, 'end')
+		self.pos = self.index('end')
+		os.write(self.tochild, line)
+
+	def sendeof(self, *args):
+		if not self.pid:
+			Dialog(self.master, {
+				'text': "No active process",
+				'title': "No process",
+				'bitmap': 'error',
+				'default': 0,
+				'strings': ('OK',),
+			})
+			return
+		os.close(self.tochild)
+
+	def sendsig(self, sig):
+		if not self.pid:
+			Dialog(self.master, {
+				'text': "No active process",
+				'title': "No process",
+				'bitmap': 'error',
+				'default': 0,
+				'strings': ('OK',),
+			})
+			return
+		os.kill(self.pid, sig)
+
+	def sigint(self, *args):
+		self.sendsig(signal.SIGINT)
+
+	def sigquit(self, *args):
+		self.sendsig(signal.SIGQUIT)
+
+	def sigterm(self, *args):
+		self.sendsig(signal.SIGTERM)
+
+	def sigkill(self, *args):
+		self.sendsig(signal.SIGKILL)
+
+MAXFD = 100	# Max number of file descriptors (os.getdtablesize()???)
+
+def spawn(prog, args):
+	p2cread, p2cwrite = os.pipe()
+	c2pread, c2pwrite = os.pipe()
+	pid = os.fork()
+	if pid == 0:
+		# Child
+		os.close(0)
+		os.close(1)
+		os.close(2)
+		if os.dup(p2cread) <> 0:
+			sys.stderr.write('popen2: bad read dup\n')
+		if os.dup(c2pwrite) <> 1:
+			sys.stderr.write('popen2: bad write dup\n')
+		if os.dup(c2pwrite) <> 2:
+			sys.stderr.write('popen2: bad write dup\n')
+		for i in range(3, MAXFD):
+			try:
+				os.close(i)
+			except:
+				pass
+		try:
+			os.execvp(prog, args)
+		finally:
+			print 'execvp failed'
+			os._exit(1)
+	os.close(p2cread)
+	os.close(c2pwrite)
+	return pid, c2pread, p2cwrite
+
+def test():
+	shell = string.join(sys.argv[1:])
+	cnf = {}
+	if shell:
+		cnf['shell'] = shell
+	root = Tk()
+	root.minsize(1, 1)
+	w = ShellWindow(root, cnf)
+	w.pack({'expand': 1, 'fill': 'both'})
+	w.focus_set()
+	w.tk.mainloop()
+
+if __name__ == '__main__':
+	test()