halting problem :: Episode 1.a: The GIMP Toolkit

:: ~9 min read

A side episode! We’re going to take a look to GTK in its first major API cycle

The history of the GNOME project is also the history of its core development platform. After all, you can’t have a whole graphical environment without a way to write not only applications for it, but its own components. Linux, for better or worse, does not come with its own default GUI toolkit; and if we’re using GNU as the userspace environment we’re still out of something that can do the job of putting things on the screen for people to use.

While we can’t tell the history of GNOME without GTK, we also cannot tell the history of GTK without GNOME; the two projects are fundamental entwined, both in terms of origins and in terms of direction. Sure, GTK can be used in different environments, and on different platforms, now; and having a free-as-in-free software GUI toolkit was definitely the intent of the separation from GIMP’s own code base; nevertheless, GTK as we know it today would not exist without GNOME, just like GNOME would not have been possible without GTK.

We’ve talked about GTK’s origin as the replacement toolkit for Motif created by the GIMP developers, but we haven’t spent much time on it during the first chapter of the history of the GNOME project.

If you managed to fall into a time hole, and ended up in 1996 trying to write a GUI application on Linux or any commercial Unix, you’d have different choices depending on:

  • the programming language you wish to use in order to write your application
  • the license you wish to use once you release your application

The academic and professional space on Unix was dominated by OpenGroup’s Motif toolkit, which was mostly a collection of widgets and utilities on top of the X11 toolkit, or Xt. Xt’s API, like all pre-Xorg X11 libraries API, is very 1987: pointers hidden inside type names; global locks; explicit event loop. Xt is also notable because it lacks basically any feature outside of “initialise an application singleton”; “create this top level window”, either managed by a window manager compatible with the Inter-Client Communication Conventions Manual, or an unmanaged “pop up” window that is left to the application developer to handle; and an event dispatch API, in order to write your own event loop. Motif integrated with Xt to provide everything else: from buttons to text entries; from menus to scroll bars.

Motif was released under a proprietary license, which required paying royalties to the Open Group.

If you wanted to release your application under a copyleft license, you’d probably end up writing something using another widget collection, released under the same terms as X11 itself, and like Motif based on the X toolkit, called the “X Athena widgets”—which is something I would not wish on my worst enemy; you could also use the GNUstep project toolkit, a reimplementation of the NeXT frameworks; but like on NeXT, you’d have to use the (then) relatively niche Objective C language.

If you didn’t want to suffer pain and misery with the Athena widgets, or you wanted to use anything except Objective C, you’d have to write your own layout, rendering, and input handling layer on top of raw Xlib calls — an effort commonly known amongst developers as: “writing a GUI toolkit”.

GIMP developers opted for the latter.

Since GTK was a replacement for an extant toolkit, some of the decisions that shaped its API were clearly made with Motif terminology in mind: managed windows (“top levels”), and unmanaged windows (“pop ups”); buttons and toggle buttons; menus and menu shells; scale and spin widgets; panes and frames. Originally, GTK objects were represented by opaque integer identifiers, like objects in OpenGL; that was, fortunately, quickly abandoned in favour of pointers to structures. The widget hierarchy was flat, and you could not derive new widgets from the existing ones.

GTK as a project provided, and was divided into three basic and separate libraries:

  • GLib, a C utility library, meant to provide the fundamental data structures that the C standard library does not have: linked lists, dynamic arrays, hash tables, trees
  • GDK, or the GIMP drawing toolkit, which wrapped Xlib function calls and data types, like graphic contexts, visuals, colormaps, and display connections
  • GTK, or the GIMP toolkit, which contained the various UI elements, from windows, to buttons, to labels, to menus

Once GTK was spun off into its own project in late 1996, GTK gained the necessary new features needed to write applications and libraries. The widget-specific callback mechanism to handle events was generalised into “signals”, which are just a fancy name for the ability to call a list of functions tied to a well known name, like “clicked” for buttons, or “key-press-event” for key presses. Additionally, and more importantly, GTK introduced a run time type system that allowed deriving new widgets from existing ones, as well as creating new widgets outside of the GTK source tree, to allow applications to define their own specialised UI elements.

The new GTK gained a “plus”, to distinguish it from the in-tree GTK of the early GIMP.

Between 1996 and 1997, GTK development was mostly driven by the needs of GIMP, which is why you could find specialised widgets such as a ruler or a color wheel in the standard API, whereas things like lists and trees of widgets were less developed, and amounted to simple containers that would not scale to large sets of items—as any user of the file selection widget at the time would be able to tell you.

Aside from the API being clearly inspired by Motif, the appearance of GTK in its 0.x and 1.0 days was very much Motif-like; blocky components, 1px shadows, aliased arrows and text. Underneath it all, X11 resources like windows, visuals, server-side allocated color maps, graphic contexts, and rendering primitives. Yes, back in the old days, GTK was fully network transparent, like every other X11 toolkit. It would take a few more years, and two major API cycles, for GTK to fully move away from that model, and towards its own custom, client-side rendering.

GTK’s central tenets about how the widget hierarchy worked were also mutuated in part from Motif; you had a tree of widgets, starting from the top level window, down to the leaf widgets like text labels. Widgets capable of holding other widgets, or containers, were mostly meant to be used as layout managers, that is UI elements encoding a layout policy for their children widgets—like a table, or an horizontal box. The layout policies provided by GTK eschewed the more traditional “pixel perfect” positioning and sizing, as provided by the Windows toolkits; or the “struts and springs” model, provided by Apple’s frameworks. With GTK, you packed your widgets inside different containers, and those would be sized by their contents, as well as by packing options, such as a child filling all the available space provided by its parent; or allowing the parent widget to expand, and aligning itself within the available space.

As we’ve seen in episode 1.2, one of the first things the Red Hat Advanced Development labs did was to get GTK 1.0 out of the door in advance of the GNOME 1.0 release, in order to ensure that the GNOME platform could be consumed both by desktop components and by applications alike. While that happened, GTK started its 1.2 development cycle.

The 1.2 development effort went into stabilisation, performance, and the occasional new widget. GLib was split off from GTK’s repository at the start of the development cycle, as it was useful for other, non-GUI C projects.

As a result of “Project Bob”, Owen Taylor took the code initially written by Carsten Haitzler, and wrote a theming engine for GTK. GTK 1.0 gave you API inside GDK to draw the components of any widget: lines, polygons, ovals, text, and shadows; the GDK API would then call into the Xlib one, and send commands over the wire to the X server. In GTK 1.2, instead of using the API inside GDK, you’d go through a separate layer inside GTK; you had access to the same primitives, augmented by a state tracker for things like colors, background pixmaps, and fonts. That state was defined via an ancillary configuration file, called a “resource” file, with its own custom syntax. You could define the different colors for each different state for each widget class, and GTK would store that information inside the style state tracker, ready to be used when rendering.

Additionally, GTK allowed to load “engines” at run time: small pieces of compiled C code that would be injected into each GTK application, and that replaced the default drawing implementation provided by GTK. Engines would have access to the style information, and, critically, as we’re going to see in a moment, to the windowing system surface that the widget would be drawn into.

It’s important to note that theme engines in GTK 1.2 could only influence colors and fonts; additionally, widgets would be built in isolation one from the other, from disjoint primitives. As a theme engine author you had no idea if the text you’re drawing is going to end up in a button, or inside a label; or if the background you’re rendering is going to be a menu or a top level window. Which is why theme engine authors tried to be sneaky about this; due to how GTK and GDK were split, the GDK windowing system surface needed to have an opaque back pointer to the GTK widget that owned it. If you knew this detail, you could obtain the GTK widget currently being drawn, and determine not only the type of the widget, but also its current position within the scene graph. Of course, this meant that theme engines, typically developed in isolation, would need to be cognisant of the custom widgets inside applications, and that any bug inside an engine would either break new applications, which had no responsibility towards maintaining an internal state for theme engines to poke around with, or simply crash everything that loaded it. As we’re going to see in the future, this approach to theming—at the same time limited in what it could achieve out of the box, and terrifyingly brittle as soon as it was fully exploited—would be a problem that both GTK developers and GNOME theme developers would try to address; and to the surprise of absolutely nobody, the solution made a bunch of people deeply unhappy.

Given that GTK 1.2 had to maintain API and ABI compatibility with GTK 1.0, nothing much changed over the course of its development history. By the time GNOME 1.2 was released, the idea was to potentially release GTK 1.4 as an interim version. A new image loading library, called GdkPixbuf, was written to replace the aging imlib2, using the GTK type system. Additionally, as we saw in episode 1.5, Owen Taylor was putting the finishing touches on a text shaping library called Pango, capable of generating complex text layouts through Unicode strings. Finally, Tim Janik was hard at work on a new type system, to be added to GLib, capable of being used for more than just GUI development. All of these changes would require a clear cut with the backward compatibility of the 1.x series, which meant that the 1.3 development cycle would result in GTK 2.0, and thus would be part of the GNOME 2.0 development effort—but this is all in the future. Or in the past, if you think fourth dimensionally.

Next week, we’re going to see what happens when people that really don’t want to use C to write their applications need to deal with the fact that the whole toolkit and core platform they are consuming is written in object oriented C, in the side episode about language bindings.

References

history of gnome gnome podcast

Follow me on Mastodon