Appendix C
Migrating from PyGTK
Earlier editions of this book taught PyGTK: GTK 2 through the pygtk bindings.
That stack is gone. PyGTK has had no release since 2011, GTK 2 stopped getting
fixes, and the bindings do not exist for Python 3 in any supported form.
This appendix is a translation table. It will not port a program for you, but it will tell you what each idiom you remember is called now, and which of them have no replacement because the pattern itself was dropped.
Everything below assumes the modern imports:
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw, Gio, GLib, Gtk
The shape of a program
- PyGTK
- You built a
gtk.Window, packed it, calledshow_all(), thengtk.main(), and connecteddestroytogtk.main_quit. - GTK 4
- You build a
Gtk.Application, create aGtk.ApplicationWindowin itsactivatehandler, callpresent(), and letapp.run()return when the last window closes.
The application object is not optional ceremony. It gives you the single-instance check, actions, keyboard accelerators, the menu bar, D-Bus activation and session integration — all of which PyGTK programs had to arrange by hand or do without.
# then
win = gtk.Window()
win.connect("destroy", gtk.main_quit)
win.show_all()
gtk.main()
# now
def on_activate(app):
Gtk.ApplicationWindow(application=app).present()
app = Gtk.Application(application_id="com.example.App")
app.connect("activate", on_activate)
sys.exit(app.run(sys.argv))
Imports and versions
import pygtk; pygtk.require("2.0"); import gtk becomes
import gi; gi.require_version("Gtk", "4.0"); from gi.repository import Gtk.
The require_version call has to come before the import, not after, and it is
needed for every library with more than one version installed. Skipping it does not
fail cleanly — it picks a version, warns, and crashes later.
Names lost their lowercase module: gtk.Window is Gtk.Window, gtk.HBox is
Gtk.Box, gobject is GObject, and the parts of GLib that PyGTK exposed as
gobject.timeout_add are now GLib.timeout_add.
Containers and visibility
show_all()is gone- Widgets are visible when created. Hide what you do not want with
set_visible(False). container.add(child)is gone- A widget that holds one child has
set_child().Gtk.Window,Gtk.Button,Gtk.ScrolledWindow,Gtk.Frameall work this way. box.pack_start(child, expand, fill, padding)is gone- Use
box.append(child)orbox.prepend(child), and move the three arguments onto the child:child.set_hexpand(True),child.set_halign(...),child.set_margin_start(...). gtk.HBox/gtk.VBoxare gone- One class with an orientation:
Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6). gtk.Tableis goneGtk.Grid, withattach(child, column, row, width, height).widget.destroy()on a child is gone- Remove it from its parent instead —
box.remove(child)orparent.set_child(None). Windows still haveclose().
Widgets that were renamed or replaced
| PyGTK | GTK 4 |
|---|---|
gtk.RadioButton |
Gtk.CheckButton joined with set_group() |
gtk.ComboBox, gtk.ComboBoxText |
Gtk.DropDown |
gtk.TreeView + gtk.ListStore |
Gtk.ColumnView / Gtk.ListView + Gio.ListStore |
gtk.MessageDialog |
Gtk.AlertDialog |
gtk.FileChooserDialog |
Gtk.FileDialog |
gtk.ColorSelectionDialog |
Gtk.ColorDialog |
gtk.FontSelectionDialog |
Gtk.FontDialog |
gtk.Statusbar |
Adw.Toast, or a label in the header bar |
gtk.Table |
Gtk.Grid |
gtk.Alignment |
halign / valign / margins on the child |
gtk.EventBox |
event controllers, added to any widget |
gtk.Arrow, gtk.HSeparator |
Gtk.Image with an icon name, Gtk.Separator |
gtk.STOCK_* |
icon names — see Icon Names |
gtk.UIManager, gtk.ActionGroup |
Gio.Menu + Gio.SimpleAction |
gtk.Builder (glade files) |
Gtk.Builder (.ui files), or Blueprint |
Dialogs stopped blocking
PyGTK dialogs ran a nested main loop:
response = dialog.run()
dialog.destroy()
if response == gtk.RESPONSE_OK:
...
GTK 4 dialogs are asynchronous. You pass a callback and return immediately:
dialog = Gtk.AlertDialog()
dialog.set_buttons(["Cancel", "Delete"])
dialog.choose(window, None, on_choice)
def on_choice(dialog, result, _data=None):
try:
index = dialog.choose_finish(result)
except GLib.Error:
return # dismissed, not answered
This is the single most invasive change when porting. Code shaped like “ask a question in the middle of a function and carry on with the answer” has to be split in two at the question. There is no supported way to run a nested loop and wait.
The upside is that the nested-loop bugs go with it: no more reentrant signal
handlers, no more dialogs that outlive the window that opened them, no more
destroy() you forgot.
Signals
connect() works the same, and extra arguments are still passed through to the
callback. Two differences:
Some toggled-style signals became property notifications. Gtk.Switch has no
toggled; watch notify::active instead. The handler takes an extra GParamSpec
argument you will ignore.
Input handling moved to event controllers. There is no
widget.connect("button-press-event", ...) and no gtk.EventBox to wrap a widget
that cannot receive events. Instead you attach a controller to any widget:
click = Gtk.GestureClick()
click.connect("pressed", on_pressed) # (gesture, n_press, x, y)
label.add_controller(click)
keys = Gtk.EventControllerKey()
keys.connect("key-pressed", on_key) # (controller, keyval, keycode, state)
window.add_controller(keys)
motion = Gtk.EventControllerMotion()
motion.connect("motion", on_motion) # (controller, x, y)
widget.add_controller(motion)
Drawing
expose-event and widget.window are gone, along with the GDK drawing API.
area = Gtk.DrawingArea()
area.set_draw_func(on_draw) # (area, cairo_context, width, height)
Cairo is still Cairo, so the body of an old expose-event handler usually
transplants unchanged once you take the context from the argument list instead of
calling widget.window.cairo_create(). See
Drawing with Cairo.
Threads
gtk.gdk.threads_init(), threads_enter() and threads_leave() are gone and have
no replacement. GTK 4 is not thread-safe and never pretends to be: touch widgets
only from the main thread.
To get a result from a worker thread back into the interface, hand it to the main loop:
GLib.idle_add(update_the_label, result)
GLib.idle_add and GLib.timeout_add are safe to call from any thread, and the
callback runs on the main thread. A callback returning GLib.SOURCE_CONTINUE
(True) is called again; returning GLib.SOURCE_REMOVE (False) stops it.
Things with no replacement
Some of what the previous edition covered has no modern equivalent, because the technology behind it was retired rather than replaced:
- libglade — use
Gtk.Builderwith.uifiles;gtk-builder-convertis long gone. - Glade the designer — no longer supports GTK 4. Write
.uifiles by hand, use Blueprint, or use Cambalache. - GConf — use
GSettings. See Desktop Integration. - Clutter — folded into GTK; use GTK 4’s own animation API. See Animation and Transitions.
- gtkmozembed and the Internet Explorer control — use WebKitGTK. See Embedding Web Content.
- IronPython with Gtk# — not a supported way to write GTK applications.
- Empathy and Geoclue chapters — Empathy is unmaintained; Geoclue is reached through portals now.