All,
This may take a moment to set up. I have a small editor project I've worked on the past several months[1]. I originally wanted to implement an inotify watch on the current editor file to protect against modification by a foreing process. I created a custom signal, and routines to add, remove, and monitor (with pselect) the watch and routines to block and unblock emission of the custom signal to allow for normal save/save as without triggering the callback. The problem I had was how to add the monitor to the gtk_event_loop so that a check was performed on each iteration of the loop. I settled on using g_idle_add and went to the gtk-app-devel list to determine if the approach was reasonable or if there was a better way. The concensus was to use GIO/GFileMonitor instead of working with inotify directly.
Fast forward to the current problem. I rewrote the implementation to use GFileMonitor/g_file_monitor_file and rewrote the block and unblock routines to block handling of the "changed" signal to allow a normal save/save as without firing the callback. The problem is when I block the instance and handler_id for the callback before saving the file, the callback still fires. When using the inotify implementation with a custom signal, blocking the emission of the signal worked great. I have posted this back to the gtk-app-devel list, but have received nothing in return -- that's why I'm asking here. Why does g_signal_handler_block with the GIO/GFileMonitor not block handling to the "changed" signal callback? (more importantly, how do I fix it)
note: (MCVE - complete test code is at https://github.com/drankinatty/gtktest). To build with GtkSourceView2, simply type make with=-DWGTKSOURCEVIEW2, it will build as bin/gtkwrite, otherwise to build without, simply type make and it will build as bin/gtkedit.
The logic of the relevant code is as follows (app is an instance of the struct holding relevant editor variables/info and settings) The GIO/GFileMonitor implementation is in gtk_filemon.[ch] and the wrapper around the save function is in gtk_filebuf.c:
typedef struct {
...
gchar *filename;
GFileMonitor *filemon;
gulong mfp_handler;
...
} kwinst;
kwinst *app = g_slice_new (kwinst);
I set the watch with:
GFile *gfile = g_file_new_for_path (app->filename);
...
/* create monitor for app->filename */
app->filemon = g_file_monitor_file (gfile,
G_FILE_MONITOR_NONE,
cancellable, &err);
...
/* connect changed signal to monitored file saving ID */
app->mfp_handler = g_signal_connect (G_OBJECT(app->filemon), "changed",
G_CALLBACK (file_monitor_on_changed), data);
Both the instance (app->filemon) and handler_id (app->mfp_handler) are saved. (mfp is just short for modified by foreign process) In order to prevent handling of changes during normal save/save as operations, I created block and unblock functions to prevent the changes to the file from firing the callback, e.g. show below with debug g_print calls:
void file_monitor_block_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_print ("blocking changed (%lu)\n", app->mfp_handler);
g_signal_handler_block (app->filemon, app->mfp_handler);
}
void file_monitor_unblock_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_print ("unblocking changed (%lu)\n", app->mfp_handler);
g_signal_handler_unblock (app->filemon, app->mfp_handler);
}
To implement the block/unblock, I wrap the file 'save/save as' function with the block, then save[2], then unblock, but the callback is still firing on normal saves. e.g. the relevant part of the save function I have is:
if (app->mfp_handler) /* current file monitor on file */
file_monitor_block_changed (app); /* block "changed" signal */
g_print (" buffer_write_file (app, filename)\n");
buffer_write_file (app, filename); /* write to file app->filename */
if (filename)
file_monitor_add (app); /* setup monitoring on new name */
else if (app->mfp_handler)
file_monitor_unblock_changed (app); /* unblock "changed" signal */
With the debug g_print statements above, issuing save results in the following output:
$ ./bin/gtkwrite
blocking changed (669)
buffer_write_file (app, filename)
unblocking changed (669)
Monitor Event: File = /home/david/tmp/foo.txt.UY9IXY
G_FILE_MONITOR_EVENT_DELETED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CREATED
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT
Monitor Event: File = /home/david/tmp/foo.txt
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED
It makes no difference whether I include the block/unblock, the firing of the callback is unchanged. I suspect that the problem lies in the fact that the "changed" signal and it handling are implemented through GIO rather than GTK (as the custom signal for the inotify implementation was) and there being some difference in the abiliby to block handling of the "changed" signal using g_signal_handler_block -- though I cannot find a distinction in the documentation. The documentation states:
To get informed about changes to the file or directory you are monitoring,
connect to the “changed” signal. The signal will be emitted in the thread-default
main context of the thread that the monitor was created in (though if the global
default main context is blocked, this may cause notifications to be blocked
even if the thread-default context is still running).
https://developer.gnome.org/gio/stable/GFile.html#g-file-monitor-file
The application itself makes no use of any explicit threading and does not fork any part of the save. So I'm at a loss as to why the block/unblock does not prevent handling of the "changed" signal. The save is completely wrapped in the block/unblock, unless the g_file_set_contents call is asyncronous, there shouldn't be any timing issues I can see.
Why would calls to g_signal_handler_block/g_signal_handler_unblock fail to prevent handling of the "changed" signal emitted on changes to the current file? I can g_signal_handler_disconnect and nothing fires, but I shouldn't have to disconnect to temporarily block handling. What am I missing?
For completeness the file_monitor_on_changed function is included below along with the footnotes:
void file_monitor_on_changed (GFileMonitor *mon,
GFile *file, GFile *other,
GFileMonitorEvent evtype,
gpointer data)
{
kwinst *app = (kwinst *)data;
g_print ("Monitor Event: File = %s\n", g_file_get_parse_name (file));
switch (evtype)
{
case G_FILE_MONITOR_EVENT_CHANGED:
/* prompt or emit custom signal modified by foreign process */
g_print ("G_FILE_MONITOR_EVENT_CHANGED\n");
break;
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
g_print ("G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT\n");
break;
case G_FILE_MONITOR_EVENT_DELETED:
/* avoid firing on normal '.tmp' file delete */
if (g_strcmp0 (g_file_get_parse_name (file), app->filename)) {
g_print (" ignoring 'tmp' file delete.\n");
break;
}
/* prompt or emit custom signal modified by foreign process */
g_print ("G_FILE_MONITOR_EVENT_DELETED\n");
/* prompt save file */
break;
case G_FILE_MONITOR_EVENT_CREATED:
g_print ("G_FILE_MONITOR_EVENT_CREATED\n");
break;
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
g_print ("G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED\n");
break;
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
g_print ("G_FILE_MONITOR_EVENT_PRE_UNMOUNT\n");
break;
case G_FILE_MONITOR_EVENT_UNMOUNTED:
g_print ("G_FILE_MONITOR_EVENT_UNMOUNTED\n");
break;
case G_FILE_MONITOR_EVENT_MOVED:
g_print ("G_FILE_MONITOR_EVENT_MOVED\n");
/* prompt save file */
break;
case G_FILE_MONITOR_EVENT_RENAMED:
/* prompt save file */
g_print ("G_FILE_MONITOR_EVENT_RENAMED\n");
break;
case G_FILE_MONITOR_EVENT_MOVED_IN:
g_print ("G_FILE_MONITOR_EVENT_MOVED_IN\n");
break;
case G_FILE_MONITOR_EVENT_MOVED_OUT:
g_print ("G_FILE_MONITOR_EVENT_MOVED_OUT\n");
break;
default:
g_print ("unknown EVENT on changed signal.\n");
}
if (mon || other) {}
}
Using g_signal_handler_block works fine in other cases
As a point brought up in the comments, I can confirm I can easily block, unblock other signal handlers without issues. Specifically, when working with the inotify implementation, I created the following custom signal:
/* create signal to monitor file modified by foreign process */
app->SIG_MODFP = g_signal_new ("modified-foreign",
GTK_TYPE_TEXT_BUFFER,
GTK_RUN_ACTION,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_POINTER);
Then connecting the signal to a handler and saving the handler_id as follows:
/* custom signals */
app->mfp_handler2 = g_signal_connect (GTK_TEXT_BUFFER(app->buffer),
"modified-foreign",
G_CALLBACK (on_modified_foreign), app);
The on_modified_foreign callback is a simple test callback for purposes of testing the block/unblock:
void on_modified_foreign (GtkTextBuffer *buffer,
kwinst *app)
{
dlg_info ("File has changed on disk, reload?", "Modified by Foreign Process");
if (buffer || app) {}
}
Above dlg_info is just a wrapper to gtk_message_dialog_new to pop up a dialog when the "modified-foreign" signal is emitted.
Then implementing a simple test where one menuitem causes the signal to be emitted, e.g.:
void menu_status_bigredbtn_activate (GtkMenuItem *menuitem, kwinst *app)
{
g_signal_emit_by_name (G_OBJECT(app->buffer), "modified-foreign::", app);
}
And, finally blocking/unblocking works just fine:
void menu_status_block_activate (GtkMenuItem *menuitem, kwinst *app)
{
if (!app->mfp_handler2) return;
GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
g_signal_handler_block (buffer, app->mfp_handler2);
}
void menu_status_unblock_activate (GtkMenuItem *menuitem, kwinst *app)
{
if (!app->mfp_handler2) return;
GtkTextBuffer *buffer = GTK_TEXT_BUFFER(app->buffer);
g_signal_handler_unblock (buffer, app->mfp_handler2);
}
It all works fine. Select the bigredbtn menu item, the signal is emitted and up pops the dialog. Then selecting the block menu item and then again trying the bigredbtn -- nothing happens, no dialog, no nothing. Then selecting the unblock menu item and again selecting the bigredbtn, the dialog again pops up on each selection. (and the signals emitted while the handler was blocked were not queued and do not invoke the handler once it is unblocked)
So that is where I'm stuck. A large part of the problem is without picking though the GIO source line-by-line it, to a large extent, is a big black box. All works fine on the GTK side, but when doing the same thing involving a GIO function, the results seem not to work as expected.
Thanks for any other insight you may have on this issue.
footnote 1: https://github.com/drankinatty/gtkwrite
footnote 2: buffer_write_file calls g_file_set_contents to write to disk.
Ok, I've played with it long enough, and I've found a solution. The key seems to be, for whatever reason in the grand scheme of GIO, to call the block and unblock from within the same source that the signals were originally connected in. For example, adding block/unblock functions within the gtk_filemon.c source and then calling from anywhere (as done from a test menu item above) works fine, e.g.:
void file_monitor_block_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_signal_handler_block (app->filemon, app->mfp_handler);
}
void file_monitor_unblock_changed (gpointer data)
{
kwinst *app = (kwinst *)data;
if (!app->filemon || !app->mfp_handler) return;
g_signal_handler_unblock (app->filemon, app->mfp_handler);
}
This allows the calls from the above two functions in menu_status_block_activate and menu_status_unblock_activate to work as intended and successfully block handling of the "changed" signal while blocked, and resume when unblocked. Why it cannot be done by direct call to g_signal_handler_block and g_signal_handler_unblock using the instance and handler_id will just have to remain a documentation mystery for the time being.
note: I'll leave the github.com gtktest code up until mid-April if anyone wants to play with it, thereafter the working code will exist at https://github.com/drankinatty/gtkwrite.
Related
My objective is to read a large file. Each time I read a new line from the file, I want to display the current line number on the GTK UI.
I've read several excellent articles on SO and other places for doing this, none of which are giving me the desired sequencing of the threads. Below is an outline of my latest attempt. I launch a worker thread that uses g_idle_add to report the current line in the main loop. Unfortunately, the function called by g_idle_add runs only once and after the worker thread completes. Why does this happen?
/* Function that displays the counter in a GTK widget. Unfortunately, this thread runs AFTER proces_huge_file is complete. */
void display(gpointer data) {
/* Display the current line number in the GTK UI */
return FALSE;
}
/* Thread that reads the file line-by-line, and launches a function to display the current line number in the main loop. */
void process_huge_file(gpointer *data) {
gint line_number = 0;
while (getline(&csv_line, &len, fp) != -1) {
line_number ++; /* Current line number, add it to the data structure */
g_idle_add(G_SOURCE_FUNC(line_number_in_status_bar), data);
/* Process the line */
}
}
void on_app_activate(GApplication *app, gpointer data) {
/* Build the GTK UI and start running the worker thread. */
GThread *thread = g_thread_new ("process_huge_file", (GThreadFunc) process_huge_file, data);
g_thread_join (thread);
}
I resolved the matter, but I'm not sure I could defend this in a code review. Someone needs to write a complete book on GTK threading. (Huge thanks to the developers who posted a full threaded example on github.)
The strategy is to launch a worker thread that reads the file, and that thread sends a status to another thread with access to the main loop. Here are the components.
Inside main, we connect a signal to a button. Clicking the button launches the function that processes the file.
int main(int argc, char *argv[]) {
g_signal_connect(G_OBJECT(button_go), "clicked", G_CALLBACK(process_file), data_passer);
}
In the function that processes the file, we launch the worker thread.
gboolean process_file(GtkButton *button, gpointer data) {
/* Get a pointer to the main loop. */
gloop = g_main_loop_new(NULL, FALSE);
/* Launch the worker thread.*/
GThread *process_g_thread = g_thread_new("process_thread", (GThreadFunc)process_thread, gloop);
/* Continue running the main loop while the worker is running. */
g_main_loop_run(gloop);
/* Halt further processing in the main loop at this point until the worker thread completes. */
g_thread_join(process_g_thread);
/* Decrement the reference count for the main loop. */
g_main_loop_unref(gloop);
}
Inside process_thread worker, send calls to a thread with access to the main GTK loop for updating the UI.
gboolean process_thread(gpointer data) {
while (getline(&csv_line, &len, file_handle) != -1) {
/* Every time we read a line, increment the current line number. */
current_line_number++;
/* When the main loop is idle, call a function that displays the current line number in the main loop. */
gdk_threads_add_idle((GSourceFunc)line_number_in_status_bar, data);
}
}
Inside the thread with access to the main loop, display the current line number.
gboolean line_number_in_status_bar(gpointer data) {
/* Get the status bar's context, remove any current message. */
guint status_bar_context_info = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_bar), "STATUS_BAR_CONTEXT_INFO");
gtk_statusbar_remove(GTK_STATUSBAR(data_passer->status_bar), status_bar_context_info, data_passer->status_bar_context_info_message_id);
/* Construct a message to display in the status bar. */
gchar progress_message[100];
g_snprintf(progress_message, 100, "Reading line %d...", data_passer->current_line_number);
/* Push the message to the status bar. */
data_passer->status_bar_context_info_message_id = gtk_statusbar_push(GTK_STATUSBAR(data_passer->status_bar), status_bar_context_info, progress_message);
/* Return FALSE to remove the source function. */
return FALSE;
}
I am trying to build a GUI application for an activity monitor in C, and decided to use GTK library along with Glade to help with the design. (Using Ubuntu 20.04)
Upon pressing the button, values are displayed in the respective positions and are updated upon every click.
The only problem is I need it to update by itself in real-time, so I shifted the code into an infinite loop with sleep(1), so it updates them after every 1 second. But values are not even being displayed on the GUI now.
To test if the code is even being executed, I tried printing values on the console from different parts of the code, and they are indeed being printed.
Things I've tried, but didn't work:
Switching between loops and recursions, both failed.
Replacing the sleep() function with a self-made timer using time.h library
Encapsulate the GUI-displaying code into a function, and have that whole function called in a loop.
Forcing the refresh on GUI using GDK functions, so it updates the GUI manually in every iteration.
Used gtk_show_all in different parts of the code to force it to display at the end of every iteration.
I think it has to do with the button trigger, and output is only updated on the GUI after the callback function is executed (from my observation with console prints).
So I am trying to have the button programmatically pressed in intervals, to avoid having to click it myself every time, but could not find much on the topic.
If you can think of any way to make this work or an alternative to the approach I am taking, kindly help out.
The main idea is that the output GUI should have values updated in real-time, regardless of the button.
Thanks in advance!
This is the function used to print out the values on the GUI:
struct timespec tm;
tm.tv_sec = 0;
tm.tv_nsec = 1000 * 1000 * 1000;
myproc_t* myprocs = NULL;
unsigned int myprocs_len = 0;
//call to function that will return the processes and their specifications
sample_processes(&myprocs, &myprocs_len, tm);
if(s == 0){
// sort by CPU usage
qsort(myprocs, myprocs_len, sizeof(myprocs[0]), myproc_comp_pcpu);
}
else if(s == 1){
// sort by Memory usage
qsort(myprocs, myprocs_len, sizeof(myprocs[0]), myproc_comp_rss);
}
for (i = 0; i < myprocs_len && i < 5; i++)
{
if (strlen(myprocs[i].cmd) == 0) {
break;
}
//convert specs read from /proc file to string format
sprintf(pid, "%d", myprocs[i].tid);
sprintf(cpu, "%.2f",myprocs[i].pcpu);
sprintf(memory, "%lu", myprocs[i].vm_rss/1000);
sprintf(cmd, "%s", myprocs[i].cmd);
switch(i)
{
case 0:
gtk_label_set_text(GTK_LABEL(PID1), pid);
gtk_label_set_text(GTK_LABEL(CPU1), cpu);
gtk_label_set_text(GTK_LABEL(MEM1), memory);
gtk_label_set_text(GTK_LABEL(CMD1), cmd);
case 1:
gtk_label_set_text(GTK_LABEL(PID2), pid);
gtk_label_set_text(GTK_LABEL(CPU2), cpu);
gtk_label_set_text(GTK_LABEL(MEM2), memory);
gtk_label_set_text(GTK_LABEL(CMD2), cmd);
case 2:
gtk_label_set_text(GTK_LABEL(PID3), pid);
gtk_label_set_text(GTK_LABEL(CPU3), cpu);
gtk_label_set_text(GTK_LABEL(MEM3), memory);
gtk_label_set_text(GTK_LABEL(CMD3), cmd);
case 3:
gtk_label_set_text(GTK_LABEL(PID4), pid);
gtk_label_set_text(GTK_LABEL(CPU4), cpu);
gtk_label_set_text(GTK_LABEL(MEM4), memory);
gtk_label_set_text(GTK_LABEL(CMD4), cmd);
case 4:
gtk_label_set_text(GTK_LABEL(PID5), pid);
gtk_label_set_text(GTK_LABEL(CPU5), cpu);
gtk_label_set_text(GTK_LABEL(MEM5), memory);
gtk_label_set_text(GTK_LABEL(CMD5), cmd);
}
}
Using a loop with sleep(1) or anything that blocks is always a no-go, since that means you're effectively blocking the UI thread from doing any actual work. The normal workflow is the following:
You want to have a main loop running, either by using gtk_main() or gtk_application_new() and connecting to the "activate" signal (which will be called when you call gtk_application_run()).
Initialize your backend code
Initialize your UI code, in which you create the necessary widgets for each process. At this point, you probably already want to make it visible using gtk_widget_show() and friends
For the periodic updates, you should post a periodic event to the main loop, which you can using API like g_timeout_add_seconds (). In the callback you can call gtk_label_set_text() on the labels you created in the previous step. As long as the callback returns G_SOURCE_CONTINUE, the callback will keep being called periodically at the specified interval
To expand on what #nielsdg correctly said, UI code based on event loop (such as GTK) must limit the blocking code to the bare minimum.
/* Never do this: it will freeze the UI */
static void
on_button_clicked()
{
do {
/* Your code here */
sleep(1);
while (condition);
}
Instead, unroll your code and leverage the main event loop:
static gboolean
iteration(gpointer user_data)
{
/* Your code here */
return condition ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
}
static void
on_button_clicked()
{
g_timeout_add(1000, iteration, NULL);
}
This just gives you the main idea. The code above has some problems, above all if you click twice it will happily start two cooperative loops.
I'm running into issues trying to register to receive the "InvitationReceived" signal from wpa_supplicant's dbus interface for p2p using the gdbus library in C.
I can create a proxy connection to the P2P dbus interface just fine and call methods on it, but when I try to connect a signal handler to the proxy, I just get the following error saying the signal is invalid (the relevant output from the code sample):
(process:6764): GLib-GObject-WARNING **:
/tmp/buildd/glib2.0-2.42.1/./gobject/gsignal.c:2461: signal
'InvitationReceived' is invalid for instance '0x909ae0' of type
'GDBusProxy'
Which is weird, since "InvitationReceived" is the name of the signal as defined by the wpa_supplicant dbus api.
Code Sample:
static void on_wpa_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data) {
g_print("on_wpa_ready\n");
GError *error = NULL;
GVariant *output;
GDBusProxy *p2p_proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
if (error) {
g_print("proxy finish error: %s\n", error->message);
g_error_free(error);
return;
}
/* call p2p_listen */
g_clear_error(&error);
output = NULL;
output = g_dbus_proxy_call_sync(p2p_proxy,
"Listen",
g_variant_new("(i)", 0), //params
G_DBUS_CALL_FLAGS_NONE,
10, //timeout_msec
NULL,
&error
);
if (error) {
g_print("Listen call error: %s\n", error->message);
g_error_free(error);
g_print("continuing...\n");
}
else {
/* it gets to this print stmt, so the method was able to be called */
g_print("Listen successful\n");
}
/* register for signal from p2p device */
/* THIS IS WHERE THE ERROR IS */
error = NULL;
g_signal_connect(p2p_proxy,
"InvitationReceived",
G_CALLBACK(on_signal), // stub func that does something simple
NULL);
}
int main (int argc, char **argv) {
GMainLoop *loop;
/* connect to wpa_supplicant p2p dbus interface */
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"fi.w1.wpa_supplicant1", //name,
"/fi/w1/wpa_supplicant1/Interfaces/0", //object_path,
"fi.w1.wpa_supplicant1.Interface.P2PDevice", //interface_name,
NULL,
on_wpa_ready, //callback,
NULL);
);
loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
}
Is there some special path that needs to be appended to the signal name? Or am I supposed to use a different proxy for registering signal handlers from the one used to call methods?
(Finally found the problem / a solution -- posting here for future gdbus users)
From the GIO documentation for GDBusProxy:
The generic “g-properties-changed” and “g-signal” signals are not very
convenient to work with. Therefore, the recommended way of working
with proxies is to subclass GDBusProxy, and have more natural
properties and signals in your derived class.
As this paragraph hints, it turns out that the base GDBusProxy class only supports the two generic signals that all interfaces are guaranteed to have: g-properties-changed and g-signal. Registering for the g-signal signal on a proxy invokes the callback whenever any signal is emitted by the object of the given proxy, including the one I happened to be looking for in this case.
Instead of going the subclassing route suggested in the documentation, I ended up just using a quick-and-dirty catchall handler:
// the new snippet for connecting a signal handler:
g_signal_connect(p2p_proxy,
"g-signal",
G_CALLBACK(on_signal), // stub func that does something simple
NULL);
And then checking for which signal was actually called in the callback function:
/* callback function */
static void on_signal (GDBusProxy *proxy,
gchar *sender_name,
gchar *signal_name,
GVariant *params,
gpointer user_data) {
if (g_strcmp0(signal_name, "InvitationReceived")) {
// ignore all irrelevant signals
return;
}
g_print("InvitationReceived signal received\n");
/* other handling code.... */
}
I have a simple multithreaded Gtk+2.0 application that acquires data from multiple sources (microphone, webcam, temperature sensor), and displays data from these as images on screen (webcam frame grabs, microphone data represented as oscilloscope renders, text, etc).
It's to my understanding from the Gtk manual and various articles that only the main processing thread should use any Gtk functions/calls that affect the UI. However, the main() entry point blocks on gtk_main() until I close the UI. With the exception of event handlers that are mapped to things like when I click on a button or slider in my UI, it seems the only option left open to me is to spawn a few pthreads and have them do the periodic sampling of data and updating information on-screen in the UI.
I remember from doing some MFC GUI development a long ways back that a similar principle applied: only a single specific thread should be updating the UI elements. How do I accomplish this in C with Gtk+2.0?
Thank you.
According to the documentation, the main even loop can accept sources from different threads:
A GMainContext can only be running in a single thread, but sources can
be added to it and removed from it from other threads.
So you can inject the code in the UI thread from your working thread by:
creating a GSource (e.g., by using g_idle_source_new);
adding the code you want to be executed with g-source-set-callback;
attaching it to the UI thread context with g_source_attach().
Here is a sample program for GTK+2 and GLib >= 2.32:
#include <gtk/gtk.h>
#define N_THREADS 100
#define N_ITERATIONS 100
GtkWidget *bar;
GMainContext *context;
static gboolean
update_progress_bar(gpointer user_data)
{
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar),
g_random_double_range(0, 1));
return G_SOURCE_REMOVE;
}
static gpointer
thread_func(gpointer user_data)
{
int n_thread = GPOINTER_TO_INT(user_data);
int n;
GSource *source;
g_print("Starting thread %d\n", n_thread);
for (n = 0; n < N_ITERATIONS; ++n) {
/* If you want to see anything you should add a delay
* to let the main loop update the UI, e.g.:
* g_usleep(g_random_int_range(1234, 567890));
*/
source = g_idle_source_new();
g_source_set_callback(source, update_progress_bar, NULL, NULL);
g_source_attach(source, context);
g_source_unref(source);
}
g_print("Ending thread %d\n", n_thread);
return NULL;
}
gint
main(gint argc, gchar *argv[])
{
GtkWidget *window;
GThread *thread[N_THREADS];
int n;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
bar = gtk_progress_bar_new();
gtk_container_add(GTK_CONTAINER(window), bar);
context = g_main_context_default();
for (n = 0; n < N_THREADS; ++n)
thread[n] = g_thread_new(NULL, thread_func, GINT_TO_POINTER(n));
gtk_widget_show_all(window);
gtk_main();
for (n = 0; n < N_THREADS; ++n)
g_thread_join(thread[n]);
return 0;
}
I would do your sampling in separate threads, as you suggest. The question then is how you update the UI. What I would do is to use a 'self-pipe'. This is normally done to communicate from signal handlers, but it works just fine between threads when one of the threads can't wait on a condition variable. What you do here is set up a dedicated pipe, write one character to the pipe in the thread that has got the data, and select() on the read end of the pipe in the main program's select() loop. Details here: Using self-pipe, how can I avoid that the event loop stalls on read()? - useful background on its origins here.
You can then make GTK+ listen on the read end of the pipe. Your problem is thus reduced to making GTK+ respond to something on an FD - see here (first answer) for how.
Using GTK and C, how can I start/stop a long calculation (in a seperate thread) using a button? I have working code that does just that but I have little confidence that it isa reasonable method (i.e., "right").
I have a single button whose label toggles from "start" to "stop". I also have a global pthread_t variable to store a thread. My approach is to either launch or cancel a thread through the button's clicked signal handler depending on the value of a global boolean-like "idle" flag which indicates if the thread is currently running or not.
I wanted a working well-designed minimum test case so that I can easily understand the code to adapt for a larger program. This question is very similar to Python&PyGTK: Stop while on button click but that question is in python which I don't know.
My code --- posted below --- seems to work but I'm not confident in it because I can easily bring the system to its knees by just clicking the start/stop button a few times in rapid succession.
I'd be curious to see how others would (independently) solve this, how their approach compares to mine, and also a code-review for my own approach if it is actually a decent way.
#include <gtk/gtk.h>
#include <pthread.h>
/* suppress unused variable warnings */
#define UNUSED(x) (void)(x)
typedef struct _Data {
GtkWidget *window1,
*button1;
gint idle;
pthread_t calcthread;
} Data;
static Data *data;
void *calcfunc(void *arg) {
int i;
UNUSED(arg);
data->idle=FALSE;
gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
/* This is intended to simulated a long calculation that may finish.
Adjust the limit as needed */
for(i=1;i<2e9;++i) {
}
data->idle=TRUE;
pthread_exit(NULL);
}
/* this is our click event handler.... it suppose to start or stop
the "calcthread" depending on the value of the "idle" flag */
void on_button1_clicked(GtkWidget *widget, Data *ldata) {
int ret;
UNUSED(widget);
UNUSED(ldata);
if ( data->idle==TRUE ) {
printf("idle.. starting thread\n");
ret=pthread_create( &data->calcthread, NULL, calcfunc, NULL);
if ( ret !=0 ) {
g_error("ERROR: could not create thread\n");
}
} else {
printf("not idle... canceling thread...");
ret= pthread_cancel( data->calcthread );
if ( ret != 0 ) {
g_error("ERROR: could not cancel thread\n");
} else {
printf("canceled\n");
}
data->idle=TRUE;
gtk_button_set_label(GTK_BUTTON(data->button1),"start");
}
}
/* just defines our setup */
int main (int argc, char *argv[]) {
g_thread_init(NULL);
gdk_threads_init();
gdk_threads_enter();
gtk_init(&argc, &argv);
data=g_slice_new0(Data);
data->idle=TRUE; /* initial state */
printf("idle is %d\n",data->idle);
/* add widgets and objects to our structure */
data->window1=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(data->window1),250,250);
data->button1=gtk_button_new_with_label("Start");
gtk_container_add(GTK_CONTAINER(data->window1),GTK_WIDGET(data->button1));
gtk_signal_connect(GTK_OBJECT(data->window1), "delete-event",
gtk_main_quit, NULL);
gtk_signal_connect(GTK_OBJECT(data->button1), "clicked",
G_CALLBACK(on_button1_clicked), NULL);
gtk_widget_show_all(GTK_WIDGET(data->window1));
gtk_main();
/* Don't forget to free the memory! */
g_slice_free(Data, data);
gdk_threads_leave();
return 0;
}
As you are calling GTK functions from the secondary thread you need to wrap the call to
gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
with gdk_threads_enter/gdk_threads_leave calls. However, it is better practice to only call GTK functions from one thread. The easiest way is with an idle function using g_idle_add as this will be called from the main thread, however in your case you could just move the call to gtk_button_set_label from calcfunc into on_button1_clicked.
You should also set data->idle = FALSE in the on_button1_clicked handler to solve the race condition where you click the button too quickly.
Another way you could do this is without threads and that is to run the GTK main loop during the long operation. In your loop you just need to pump the Gtk event loop.
for(i=1;i<2e9;++i) {
while (gtk_events_pending ()) {
gtk_main_iteration ();
}
}
This means you avoid all the threading problems and needing to lock data access. You could stop the calculation by checking a boolean value each iteration which gets set in the on_button1_clicked handler.
The following code does what I asked. It uses pthreads. I don't know if it's the most elegant but it seems to work. The trick was using two flags: one for the idle state and one for a cancel request, which avoids needing to cancel the thread using the "pthread_cancel" function, which I find to be unusual in real code.
#include <gtk/gtk.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#define UNUSED(x) (void)(x)
#define handle_error_en(en, msg) do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
typedef struct _Data {
GtkWidget *window1,
*button1;
} Data;
static Data *data;
static pthread_mutex_t calcmutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t calcthread=0;
static gboolean idle=TRUE,cancel_request=FALSE;
void *calcfunc(void *arg) {
int i,s;
UNUSED(arg);
g_print("\tstarting thread\n");
s = pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
if (s != 0) {
handle_error_en(s, "pthread_setcancelstate");
}
gdk_threads_enter();
gtk_button_set_label(GTK_BUTTON(data->button1),"Stop");
gdk_threads_leave();
g_print("\tstarting work...\n");
for (i=0; i<100000000 ;++i) {
/* check for cancelation */
pthread_mutex_lock(&calcmutex);
if ( cancel_request ) {
g_print("\t[cancel request noted].\n");
pthread_mutex_unlock(&calcmutex);
break;
}
pthread_mutex_unlock(&calcmutex);
/* do "calculation" */
i=i*1*-1*1*-1;
}
g_print("\tdone work.\n");
gdk_threads_enter();
gtk_button_set_label(GTK_BUTTON(data->button1),"Start");
gdk_threads_leave();
pthread_mutex_lock(&calcmutex);
cancel_request=FALSE;
idle=TRUE;
pthread_mutex_unlock(&calcmutex);
g_print("\tdone thread.\n");
pthread_exit(NULL);
}
void on_button1_clicked(GtkWidget *widget, gpointer *ldata) {
int s;
UNUSED(widget);
UNUSED(ldata);
g_print("entered on_button1_clicked\n");
pthread_mutex_lock(&calcmutex);
if ( idle ) {
g_print("idle, starting thread\n");
s = pthread_create(&calcthread, NULL, calcfunc, NULL);
if (s != 0) {
handle_error_en(s, "pthread_create");
}
idle=FALSE;
} else {
g_print("not idle and not first time, making canceling request.\n");
cancel_request=TRUE;
}
pthread_mutex_unlock(&calcmutex);
g_print("finished on_button1_clicked\n");
}
/* just defines our setup */
int main (int argc, char *argv[]) {
g_thread_init(NULL);
gdk_threads_init();
gdk_threads_enter();
gtk_init(&argc, &argv);
data=g_slice_new0(Data);
printf("initial idle is %d\n",idle);
/* add widgets and objects to our structure */
data->window1=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(data->window1),250,250);
data->button1=gtk_button_new_with_label("Start");
gtk_container_add(GTK_CONTAINER(data->window1),GTK_WIDGET(data->button1));
gtk_signal_connect(GTK_OBJECT(data->window1), "delete-event",
gtk_main_quit, NULL);
gtk_signal_connect(GTK_OBJECT(data->button1), "clicked",
G_CALLBACK(on_button1_clicked), NULL);
gtk_widget_show_all(GTK_WIDGET(data->window1));
gtk_main();
/* free the memory and stuff */
g_slice_free(Data, data);
pthread_mutex_destroy(&calcmutex);
gdk_threads_leave();
return 0;
}
This compiles warningless with
gcc -Wall -Wextra -Wconversion -pedantic `pkg-config --cflags --libs gtk+-2.0` start_stop.c -o start_stop
Well in Java I would call interrupt() on that Thread, the thread would then get an InterruptedException, and would be able to clean up in its exception handler's catch or finally block before exiting.
In C there is several options:
send that thread a signal with kill(), and have the signal handler longjmp() to a point in your code where you previously called setjmp(). For cleanup, you'd just do something when setjmp() returns non-zero, meaning it's resuming from the subsequent longjmp() call.
call pthread_cancel(). The only real cleanup you get here is that the cancellation handlers you previously registered with pthread_cleanup_push() will get called.
have either a volatile variable or a lock protected variable that gets checked periodically (for example once every loop iteration) and set to some value when the calculation should be canceled. Cleanup is easy because you can do whatever you like when the flag is set and you break out of the loop.
I dislike all of them: signals mean you have to handle partial failures (eg. short reads and writes on files and sockets) and errno==EINTR correctly everywhere in your code while avoiding all kinds of gotchas that exist with signal handlers (such as the small stack size and limits on what system calls are safe to use), pthread_cancel() means you have to drag state around between the thread function and the cancellation handlers, and the flag can impact performance since it has to read uncached memory or take a lock every time and if you don't check it often enough then the thread won't respond immediately when the flag is set.