[pygtk] Show command output (new IEs4Linux GUI)

Edward Catmur ed at catmur.co.uk
Sat Dec 2 04:47:12 WST 2006


On Thu, 2006-11-30 at 04:14 -0200, Internet Explorer Linux wrote:
> I'm working on a pygtk user interface to my script. My GUI configures
> some things and run an executable (IEs4linux installer, written in
> bash). I need to open some kind of window to show the installer
> output. 
> 
> I tried with TextView, but I don't know how to make it non-blocking. I
> tried with Threads, but it waits until installer finishes to show the
> output on TextView. I searched on Google and got some pages talking
> about this (even with "solutions"), but I could not make it work. 

You need to tie the fd (from your child's stdout) into the GLib
mainloop.  The way to do this is to make it nonblocking and add a watch
on it; your watch callback then adds the data that appears on the fd to
the textview.

Obviously threads would work as well, but if you can write your
application single-threaded it'll save a load of heartache debugging
races when you get the reentrancy and locking wrong.

Here's a fairly comprehensive example; I put it together to demonstrate
watching the output of a DVD burning script, but it's obviously
applicable to your situation.

Ed

#!/usr/bin/env python

import gobject, gtk, sys, os, fcntl

def get_stock_icons(widget, id):
	set = widget.style.lookup_icon_set(id)
	return map(lambda x: set.render_icon(widget.style, gtk.TEXT_DIR_NONE,
		gtk.STATE_NORMAL, x, widget, None), set.get_sizes())

class LogWindow:
	def input_callback(self, source, condition):
		if condition & (gobject.IO_HUP):
			self.eof()
			return False
		try:
			while True:
				line = source.readline()
				print line.__repr__()
				if line == '':
					self.eof()
					return False
				self.append_text(line)
		except IOError:
			pass
		return True

	def eof(self):
		self.ok.set_sensitive(True)
		self.cancel.set_sensitive(False)
		self.warn.hide()

	def append_text(self, text):
		end_iter = self.buffer.get_end_iter()
		endmark = self.buffer.create_mark(None, end_iter)
		self.textview.move_mark_onscreen(endmark)
		at_end = self.buffer.get_iter_at_mark(endmark).equal(end_iter)
		self.buffer.insert(end_iter, text)
		if at_end:
			endmark = self.buffer.create_mark(None, end_iter)
			self.textview.scroll_mark_onscreen(endmark)

	def delete_event(self, widget, data = None):
		self.warn.show_all()
		return True

	def destroy(self, widget, data = None):
		gtk.main_quit()

	def expanded(self, widget, param_spec, data = None):
		if widget.get_expanded():
			self.window.set_resizable(True)
		else:
			self.window.set_resizable(False)

	def response(self, widget, response_id, data = None):
		if response_id == gtk.RESPONSE_OK:
			self.destroy(widget)
		elif response_id == gtk.RESPONSE_CANCEL:
			self.delete_event(widget)

	def __init__(self, source):
		# make source non-blocking
		fd = source.fileno()
		fcntl.fcntl(fd, fcntl.F_SETFL, 
				fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
		gobject.io_add_watch(source, 
				gobject.IO_IN | gobject.IO_PRI | gobject.IO_HUP,
				self.input_callback)

		window = gtk.MessageDialog(buttons = gtk.BUTTONS_OK_CANCEL)
		for x in window.action_area.get_children():
			if x.get_use_stock():
				if x.get_label() == gtk.STOCK_OK:
					self.ok = x
				elif x.get_label() == gtk.STOCK_CANCEL:
					self.cancel = x
		self.ok.set_sensitive(False)
		window.set_markup( 
"""<b>The files are being burnt to DVD.</b>

Please wait while the process completes.""")
		window.set_title("Burning DVD")
		gtk.window_set_default_icon_list(
				*get_stock_icons(window, gtk.STOCK_CDROM))
		window.connect("delete_event", self.delete_event)
		window.connect("destroy", self.destroy)
		window.connect("response", self.response)
		self.window = window

		warn = gtk.MessageDialog(parent = window,
				flags = gtk.DIALOG_MODAL,
				type = gtk.MESSAGE_WARNING,
				buttons = gtk.BUTTONS_CANCEL)
		warn.add_button("Abort DVD", gtk.RESPONSE_CLOSE)
		warn.set_markup("""<b>Are you sure you want to abort burning the
DVD?</b>

If the burning process has already started, the DVD will be ruined.""")
		def warn_response(widget, response_id, data = None):
			if response_id == gtk.RESPONSE_CANCEL:
				warn.hide()
			elif response_id == gtk.RESPONSE_CLOSE:
				self.returnError = 1
				self.destroy(widget, True)
		warn.connect("response", warn_response)
		self.warn = warn

		expander = gtk.Expander("_Display detailed output")
		expander.set_use_underline(True)
		expander.connect("notify::expanded", self.expanded)
		window.vbox.pack_start(expander, True, True, 0)
				
		textview = gtk.TextView()
		textview.set_editable(False)
		self.textview = textview
		self.buffer = self.textview.get_buffer()
		scroll = gtk.ScrolledWindow()
		scroll.add(self.textview)
		scroll.set_size_request(0, 100)
		expander.add(scroll)
		
		window.show_all()

	def main(self):
		self.returnError = 0
		gtk.main()
		return self.returnError

if __name__ == "__main__":
	sys.exit(LogWindow(sys.stdin).main())



More information about the pygtk mailing list