[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