The GTK+ main loop's primary role is to listen for events on a file descriptor connected to the X server, and forward them to widgets. the section called Receiving GDK Events in GTK+ in the chapter called GDK Basics describes the main loop's event handling in more detail. This section explains the main loop in general terms, and describes how to add new functionality to the main loop: callbacks to be invoked when the loop is idle, at a specified interval, when a file descriptor is ready for reading or writing, and when the main loop exits.
The main loop is primarily implemented by glib, which has a generic main loop abstraction. GTK+ attaches the glib main loop to GDK's X server connection, and presents a convenient interface (the glib loop is slightly lower-level than the GTK+ loop). The core GTK+ main loop interface is shown in Figure 28.
gtk_main() runs the main loop. gtk_main() will not return until gtk_main_quit() is called. gtk_main() can be called recursively; each call to gtk_main_quit() exits one instance of gtk_main(). gtk_main_level() returns the level of recursion; that is, it returns 0 if no gtk_main() is on the stack, 1 if one gtk_main() is running, etc.
All instances of gtk_main() are functionally identical; they are all watching the same connection to the X server and working from the same event queue. gtk_main() instances are used to block, halting a function's flow of control until some conditions are met. All GTK+ programs use this technique to keep main() from exiting while the application is running. The gnome_dialog_run() function (see the section called Modal Dialogs in the chapter called User Communication: Dialogs) uses a recursive main loop, so it doesn't return until the user clicks a dialog button.
Sometimes you want to process a few events, without handing the flow of control to gtk_main(). You can perform a single iteration of the main loop by calling gtk_main_iteration(). This might process a single event, for example; it depends on what tasks are pending. You can check whether any events need to be processed by calling the gtk_events_pending() predicate. Together, these two functions allow you to temporarily return control to GTK+, so the GUI can "catch up." For example, during a long computation, you will want to display a progress bar; you must allow the GTK+ main loop to run periodically, so GTK+ can redraw the progress bar. Use this code:
while (gtk_events_pending()) gtk_main_iteration();
A quit function is a callback to be invoked when gtk_main_quit() is called. In other words, the callback runs just before gtk_main() returns. The callback should be a GtkFunction, defined as follows:
typedef gint (*GtkFunction) (gpointer data);
Quit functions are added with gtk_quit_add() (Figure 29). When adding a quit function, you must specify a main loop level, as returned by gtk_main_level(). The second and third arguments specify a callback and callback data.
The callback's return value indicates whether the callback should be invoked again. As long as the callback returns TRUE, it will be repeatedly invoked. As soon as it returns FALSE, it is disconnected. When all quit functions have returned FALSE, gtk_main() can return.
gtk_quit_add() returns an ID number that can be used to remove the quit function with gtk_quit_remove(). You can also remove a quit function by passing its callback data to gtk_quit_remove_by_data().
Timeout functions are connected and disconnected exactly as quit functions are; the expected callback is the same. gtk_timeout_add() expects an interval argument; the callback is invoked every interval milliseconds. If the callback ever returns FALSE, it is removed from the list of timeout functions, just as if you'd called gtk_timeout_remove(). It is not safe to call gtk_timeout_remove() from within a timeout function; this modifies the timeout list while GTK+ is iterating over it, causing a crash. Instead, return FALSE to remove a function.
Idle functions run continuously while the GTK+ main loop has nothing else to do. Idle functions run only when the event queue is empty and the main loop would normally sit idly, waiting for something to happen. As long as they return TRUE they are invoked over and over; when they return FALSE, they are removed, just as if gtk_idle_remove() had been called.
The idle function API, shown in Figure 31, is identical to the timeout and quit function APIs. Again, gtk_idle_remove() should not be called from within an idle function, because it will corrupt GTK+'s idle function list. Return FALSE to remove the idle function.
Idle functions are mostly useful to queue "one-shot" code, which is run after all events have been handled. Relatively expensive operations such as GTK+ size negotiation and GnomeCanvas repaints take place in idle functions that return FALSE. This ensures that expensive operations are performed only once, even though multiple consecutive events independently request the recalculation.
The GTK+ main loop contains a simple scheduler; idle functions actually have priorities assigned to them, just as UNIX processes do. You can assign a non-default priority to your idle functions, but it's a complicated topic outside the scope of this book.
Input functions are handled on the GDK level. They are invoked when a given file descriptor is ready for reading or writing. They're especially useful for networked applications.
To add an input function, you specify the file descriptor to monitor, the state you want to wait for (ready for reading or writing), and a callback/data pair. Figure 32 shows the API. Functions can be removed using the tag returned by gdk_input_add(). Unlike quit, timeout, and idle functions, it should be safe to call gdk_input_remove() from inside the input function; GTK+ will not be in the midst of iterating over the list of input functions.
To specify the condition(s) to wait for, use the GdkInputCondition flags: GDK_INPUT_READ, GDK_INPUT_WRITE, and GDK_INPUT_EXCEPTION. You can OR one or more flags together. These correspond to the three file descriptor sets passed to the select() system call; consult a good UNIX programming book for details. If any condition is met, the input function is invoked.
The callback should look like this:
typedef void (*GdkInputFunction) (gpointer data, gint source_fd, GdkInputCondition condition);
It receives your callback data, the file descriptor being watched, and the conditions that were met (possibly a subset of those you were watching for).