Mixing GTK with other software3Mixing GTK with other softwareMixing GTK with other software
How to combine GTK with other code and event loops
Overview
Often people want to use GTK in combination with another library or existing
body of code that is not GTK-aware. The general problem people encounter
is that the control flow of the other code does not return to GTK, so
widgets do not repaint, mouse and keyboard events are ignored, and so forth.
This section describes some approaches to solving this problem. The most
suitable approach depends on the code that's involved, the platforms you're
targetting, and your own familiarity with each approach.
Periodically yield to GTK main loop
This is the simplest method, but requires you to modify the non-GTK code.
Say you have a function that does some kind of lengthy task:
void
do_lengthy_task (void)
{
int i;
for (i = 0; i < BIG_NUMBER; ++i)
{
do_small_part_of_task ();
}
}
You simply insert code into this function that processes pending main loop tasks, if any:
void
do_lengthy_task (void)
{
int i;
for (i = 0; i < BIG_NUMBER; ++i)
{
do_small_part_of_task ();
/* allow main loop to process pending events; NULL
* means the default context.
*/
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
}
}
The primary disadvantage of this approach is that you have to trade off UI
responsiveness and the performance of the task. That is, if
do_small_part_of_task() does very little of the task, you'll spend lots of CPU
time on g_main_context_iteration(). While if
do_small_part_of_task() does a lot of work, the GUI will seem noticeably
"chunky" to the user.
Another disadvantage to this approach is that you can't have more than one
lengthy task at the same time, unless you manually integrate them.
The big advantage of this approach is that it's simple and straightforward, and
works fine for simple applications such as tossing up a progress bar during the
lengthy task.
Run the other code as a slave of the GTK main loop
As a slightly cleaner solution, you can ask the main loop to run a small part of your
task whenever it isn't busy — that is, when it's idle.
GLib provides a function g_idle_add() that's useful
for this. An "idle handler" added with g_idle_add()
will be run continuously as long as it returns TRUE. However,
the main loop gives higher priority to GUI-related tasks, so will run those instead
when appropriate.
Here's a simple example:
gboolean
my_idle_handler (gpointer user_data)
{
do_small_part_of_task ();
if (task_complete)
return G_SOURCE_REMOVE; /* removes the idle handler */
else
return G_SOURCE_CONTINUE; /* runs the idle handler again */
}
g_idle_add (my_idle_handler, NULL);
If your task involves reading data from the network, you should instead use
g_input_add(); this will allow the
main loop to sleep until data is available on a file descriptor, then
wake up to read that data.
g_idle_add() returns a main loop source ID you can
use to remove the idle handler with g_source_remove().
This is useful for cancelling a task, for example. Another approach is to keep a flag
variable and have the idle handler itself return FALSE when appropriate.
Use multiple processes
If you can't break a task into small chunks — the
"do_small_part_of_task()" function in the above examples — you'll have to
separate your program into two parts, by spawning a child thread or process.
A process does not share the same address space (variables and data) with its parent.
A thread does share the same address space, so a change made to a variable in
one thread will be visible to other threads as well.
This manual can't go into full detail on processes, threads, and other UNIX
programming topics. You may wish to get a book or two — two I'm familiar
with are Beginning Linux Programming (WROX Press) and Advanced Programming in
the UNIX Environment (by Richard Stevens.
Those books also cover the central issue you'll need to address in order to have
a multi-process application: how to communicate between the processes. The
simplest solution is to use pipes; g_input_add() in combination with g_spawn_async_with_pipes() should make
this reasonably convenient. There are other possibilities, of course, such as
sockets, shared memory, and X Window System client message events, depending on
your needs.
Use multiple threadsIntegrate the GTK main loop with another main loopThings that won't work
signals