FAQ Index - Search - Recent Changes - Everything - Add entry

<< Previous Entry | FAQ Entry 14.23 | Next Entry >>

14.23. How do I send the output of an external process to a gtk.TextView without freezing the GUI?

The problem here is to avoid waiting for the external process to finish. That can easily be done with threads but it should be possible to get it working using pipes in non-blocking IO mode and monitoring output using the gobject.io_add_watch() function. The problem with this last approach is that only sockets, not pipes, can be used with the select interface in Win32... so that solution seems not too portable for that reason.

This example shows how to do it the "threaded way", as it seems quite straightforward and portable through platforms without additional tweaking [1].

 #!/usr/bin/env python
 """Show a shell command's output in a gtk.TextView without freezing the UI"""

 import os, threading, locale

 import gobject
 import gtk

 gobject.threads_init()
 gtk.gdk.threads_init()

 encoding = locale.getpreferredencoding()
 utf8conv = lambda x : unicode(x, encoding).encode('utf8')

 def on_button_clicked(button, view, buffer, command):
     thr = threading.Thread(target= read_output, args=(view, buffer, command))
     thr.start()

 def read_output(view, buffer, command):
     stdin, stdouterr = os.popen4(command)
     while 1:
         line = stdouterr.readline()
         if not line:
             break
         gtk.gdk.threads_enter()
         iter = buffer.get_end_iter()
         buffer.place_cursor(iter)
         buffer.insert(iter, utf8conv(line))
         view.scroll_to_mark(buffer.get_insert(), 0.1)
         gtk.gdk.threads_leave()

 sw = gtk.ScrolledWindow()
 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 textview = gtk.TextView()
 textbuffer = textview.get_buffer()
 sw.add(textview)
 win = gtk.Window()
 win.resize(300,500)
 win.connect('delete-event', gtk.main_quit)
 button = gtk.Button(u"Press me!")
 command = 'ls -R %s' % (os.getcwd(),)
 button.connect("clicked", on_button_clicked, textview, textbuffer, command)
 vbox = gtk.VBox()
 vbox.pack_start(button, False)
 vbox.pack_start(sw)
 win.add(vbox)
 win.show_all()

 gtk.main()

Note that this code is dangerous: if the widget is destroyed while the thread is still running, nasty things may happen, from nothing visible to segfault.

[1] Nobody really knows if this works on windows.

Dangerous code is wonderful, but how would you do this safely? Is there locking involved? Where?

When multiple threads are writing to the same text buffer, the program has a tendency to fail an assert and segfault on the buffer.insert line. If you explicitly silence the assert error, the interpreter tends to crash. This probably isn't a result of bad interpreter code, but just really nasty thread handling.

Moral of the story: Don't let multiple threads write to the same text-buffer. Create a new one for every thread.

PyGTK FAQ Wizard | PyGTK Homepage | Feedback to faq at pygtk.org