I'm new to GTK development, so I've been going through the examples just to capture the basics. I've done a lot of Unix GUI development before, but it was all Xt/Motif stuff. So, the GTK scheduler is something new to me.
That said, I was going through the color selection example
here , and was pretty curious how it handled the gtk_dialog_run command. I was mostly interested if during the call (while it was waiting for a response on line 132) a timer would continue to run. I though, for sure, it wouldn't.
To my surprise, a timer (added via g_timeout_add) did continue on in the background. My first thought was, "oh, neat, they threaded the timers." But then I figured, "but, why not see." So I threw together the code below:
Code :
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
GtkWidget *colorseldlg = NULL;
GtkWidget *drawingarea = NULL;
GdkColor color;
static pthread_key_t _g_uidPTK;
typedef struct
{
int tid;
} tspecific_t;
typedef struct
{
int val;
char *txt;
} app_timer_t;
static tspecific_t *get_tsd()
{
static pthread_mutex_t mx = PTHREAD_MUTEX_INITIALIZER;
static int nextId = 1;
tspecific_t *tdata;
if ((tdata = pthread_getspecific(_g_uidPTK)) == NULL)
{
tdata = malloc(sizeof(*tdata));
pthread_setspecific(_g_uidPTK, tdata);
pthread_mutex_lock(&mx);
tdata->tid = nextId;
nextId++;
pthread_mutex_unlock(&mx);
}
return tdata;
}
static int get_tid()
{
tspecific_t *tdata = get_tsd();
return tdata->tid;
}
static gboolean progress_timeout( gpointer _timer )
{
#define timer ((app_timer_t *) _timer)
printf("[tid:%d] called %s [%d]...\n", get_tid(), timer->txt, ++(timer->val));
/* As this is a timeout function, return TRUE so that it
* continues to get called */
return TRUE;
#undef timer
}
/* Color changed handler */
static void color_changed_cb( GtkWidget *widget,
GtkColorSelection *colorsel )
{
GdkColor ncolor;
printf("[tid:%d] color changed...\n", get_tid());
gtk_color_selection_get_current_color (colorsel, &ncolor);
gtk_widget_modify_bg (drawingarea, GTK_STATE_NORMAL, &ncolor);
}
/* Drawingarea event handler */
static gboolean area_event( GtkWidget *widget,
GdkEvent *event,
gpointer client_data )
{
gint handled = FALSE;
gint response;
GtkColorSelection *colorsel;
/* Check if we've received a button pressed event */
printf("[tid:%d] Got event %d...\n", get_tid(), event->type);
if (event->type == GDK_BUTTON_PRESS)
{
handled = TRUE;
/* Create color selection dialog */
if (colorseldlg == NULL)
colorseldlg = gtk_color_selection_dialog_new ("Select background color");
/* Get the ColorSelection widget */
colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (colorseldlg)->colorsel);
gtk_color_selection_set_previous_color (colorsel, &color);
gtk_color_selection_set_current_color (colorsel, &color);
gtk_color_selection_set_has_palette (colorsel, TRUE);
/* Connect to the "color_changed" signal, set the client-data
* to the colorsel widget */
g_signal_connect (colorsel, "color_changed",
G_CALLBACK (color_changed_cb), (gpointer) colorsel);
/* Show the dialog */
printf("[tid:%d] Running dialog...\n", get_tid());
response = gtk_dialog_run (GTK_DIALOG (colorseldlg));
printf("[tid:%d] Got response.\n", get_tid());
if (response == GTK_RESPONSE_OK)
gtk_color_selection_get_current_color (colorsel, &color);
else
gtk_widget_modify_bg (drawingarea, GTK_STATE_NORMAL, &color);
gtk_widget_hide (colorseldlg);
}
return handled;
}
/* Close down and exit handler */
static gboolean destroy_window( GtkWidget *widget,
GdkEvent *event,
gpointer client_data )
{
gtk_main_quit ();
return TRUE;
}
/* Main */
gint main( gint argc,
gchar *argv[] )
{
GtkWidget *window;
app_timer_t t1 = { 0, "T1" };
app_timer_t t2 = { 0, "T2" };
/* Initialize the toolkit, remove gtk-related commandline stuff */
gtk_init (&argc, &argv);
/* create key for thread assignment */
pthread_key_create(&_g_uidPTK, free);
/* Create toplevel window, set title and policies */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Color selection test");
gtk_window_set_policy (GTK_WINDOW (window), TRUE, TRUE, TRUE);
/* Attach to the "delete" and "destroy" events so we can exit */
g_signal_connect (window, "delete-event",
G_CALLBACK (destroy_window), (gpointer) window);
/* Create drawingarea, set size and catch button events */
drawingarea = gtk_drawing_area_new ();
color.red = 0;
color.blue = 65535;
color.green = 0;
gtk_widget_modify_bg (drawingarea, GTK_STATE_NORMAL, &color);
gtk_widget_set_size_request (GTK_WIDGET (drawingarea), 200, 200);
gtk_widget_set_events (drawingarea, GDK_BUTTON_PRESS_MASK);
g_signal_connect (GTK_OBJECT (drawingarea), "event",
GTK_SIGNAL_FUNC (area_event), (gpointer) drawingarea);
/* Add drawingarea to window, then show them both */
gtk_container_add (GTK_CONTAINER (window), drawingarea);
gtk_widget_show (drawingarea);
gtk_widget_show (window);
/* add a few timeouts to test how they are handled */
g_timeout_add (2000, progress_timeout, &t1);
g_timeout_add (3000, progress_timeout, &t2);
/* Enter the gtk main loop (this never returns) */
gtk_main ();
/* Satisfy grumpy compilers */
return 0;
}
Most of it is their code, I just added the stuff to assign and retrieve a unique ID for each thread.
Upon running it, I was further surprised! The timers and the call waiting for the dialog to come back are "running" in the same thread. Here's some output:
Code :
[tid:1] Got event 13...
[tid:1] Got event 2...
[tid:1] Got event 2...
[tid:1] called T1 [1]...
[tid:1] called T2 [1]...
[tid:1] Got event 4...
[tid:1] Running dialog...
[tid:1] called T1 [2]...
[tid:1] called T1 [3]...
[tid:1] called T2 [2]...
[tid:1] called T1 [4]...
[tid:1] called T2 [3]...
[tid:1] Got response.
[tid:1] called T1 [5]...
[tid:1] called T1 [6]...
[tid:1] called T2 [4]...
Humm...now, I know this can be accomplished with some funky long jumping and the like. A quick debugging session with GDB further proves that setting a breakpoint inside the timer and inspecting the stack have them both "running" together. See below:
Code :
(gdb) break 61
Breakpoint 1 at 0x80490b5: file gtktst.c, line 61.
(gdb) run
Starting program: /home/dreamwarrior/CStuff/gtkStuff/gtktst
[Thread debugging using libthread_db enabled]
[tid:1] Got event 13...
[tid:1] Got event 2...
[tid:1] Got event 2...
[tid:1] Got event 4...
[tid:1] Running dialog...
Breakpoint 1, progress_timeout (_timer=0xbffff394) at gtktst.c:61
61 printf("[tid:%d] called %s [%d]...\n", get_tid(), timer->txt, ++(timer->val));
(gdb) where
#0 progress_timeout (_timer=0xbffff394) at gtktst.c:61
#1 0x008b7d5c in ?? () from /lib/libglib-2.0.so.0
#2 0x008b75e5 in g_main_context_dispatch () from /lib/libglib-2.0.so.0
#3 0x008bb2d8 in ?? () from /lib/libglib-2.0.so.0
#4 0x008bb817 in g_main_loop_run () from /lib/libglib-2.0.so.0
#5 0x001deabf in gtk_dialog_run () from /usr/lib/libgtk-x11-2.0.so.0
#6 0x0804926d in area_event (widget=0x8077858, event=0x80774b0,
client_data=0x8077858) at gtktst.c:120
#7 0x0026b424 in ?? () from /usr/lib/libgtk-x11-2.0.so.0
#8 0x00834252 in g_closure_invoke () from /usr/lib/libgobject-2.0.so.0
#9 0x0084899d in ?? () from /usr/lib/libgobject-2.0.so.0
#10 0x00849c33 in g_signal_emit_valist () from /usr/lib/libgobject-2.0.so.0
#11 0x0084a256 in g_signal_emit () from /usr/lib/libgobject-2.0.so.0
#12 0x003984da in ?? () from /usr/lib/libgtk-x11-2.0.so.0
#13 0x00263a5d in gtk_propagate_event () from /usr/lib/libgtk-x11-2.0.so.0
#14 0x002650c7 in gtk_main_do_event () from /usr/lib/libgtk-x11-2.0.so.0
#15 0x0055939a in ?? () from /usr/lib/libgdk-x11-2.0.so.0
#16 0x008b75e5 in g_main_context_dispatch () from /lib/libglib-2.0.so.0
#17 0x008bb2d8 in ?? () from /lib/libglib-2.0.so.0
#18 0x008bb817 in g_main_loop_run () from /lib/libglib-2.0.so.0
#19 0x002653c9 in gtk_main () from /usr/lib/libgtk-x11-2.0.so.0
#20 0x08049523 in main (argc=1, argv=0xbffff464) at gtktst.c:203
(gdb) quit
This sort of thing has me worried. What is safe to do in my timer with other code sitting on the stack? I certainly couldn't grab a mutex before the gtk_dialog_run call that any timer would grab, I'd deadlock! So...what else isn't safe? The documentation isn't really clear, and in fact, to me it implies that gtk_dialog_run "blocks" the event loop; but it obviously doesn't.