Here's my solution to the problem. It runs the external command in an
external thread. This is probably an overkill for the problem at hand, on
the other hand it is a good demo for how to create a worker thread and
capture its output in the GUI. This currently does not work with windows at
it is using popen() and g_io_channel_unix_new() but it should be trivial to
fix and is left as an exercise for the reader.

Compile with:

gcc -o stdout-to-textview `pkg-config --cflags --libs gtk+-2.0 gthread-2.0`
stdout-to-textview.c

//======================================================================
//  stdout-to-textview.c
//
//  An example how to place stdout from a an external process
//  into a text view buffer by running the process in a separate
//  thread.
//
//  This program is released under the LGPL v3.0.
//
//  Dov Grobgeld <dov.grobg...@gmail.com>
//  Fri Nov 20 09:22:39 2009
//----------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>

// This structure contains all the thread info for the job.
typedef struct {
    GMutex *update_mutex;
    GCond *update_cond;
    GMutex *mutex_to_run;

    gchar *cmd;
    gchar *info;
}  JobData;

// Sorry, out of laziness I made the widgets global.
GtkWidget *w_text_view = NULL;
GtkWidget *w_entry_cmd = NULL;
GMutex *mutex_one_job_at_a_time = NULL;

// Create the data for a job
JobData *job_data_new(const char *cmd,
                      GMutex *mutex_to_run)
{
    JobData *job_data = g_new0(JobData, 1);

    job_data->cmd = g_strdup(cmd);
    job_data->update_mutex = g_mutex_new();
    job_data->update_cond = g_cond_new();
    job_data->mutex_to_run = mutex_to_run;

    return job_data;
}

// free the data from a job
void job_data_free(JobData *job_data)
{
    g_free(job_data->cmd);
    g_mutex_free(job_data->update_mutex);
    g_cond_free(job_data->update_cond);
    g_free(job_data);
}


// This function receives a requst from a worker thread asking to
// update the gui with the required info.
gboolean cb_update_job(JobData *job_data)
{
    if (job_data->info) {
        GtkTextBuffer *text_buffer =
gtk_text_view_get_buffer(GTK_TEXT_VIEW(w_text_view));
        GtkTextIter end_iter;
        gtk_text_buffer_get_end_iter(text_buffer,
                                     &end_iter);
        gtk_text_buffer_insert(text_buffer,
                               &end_iter,
                               job_data->info,
                               -1);
        gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(w_text_view),
                                      &end_iter, 0.0, TRUE, 0.0, 0.5);
        g_free(job_data->info);
        job_data->info = NULL;
    }

    // Indicate that the update is done
    g_mutex_lock(job_data->update_mutex);
    g_cond_signal(job_data->update_cond);
    g_mutex_unlock(job_data->update_mutex);

    return FALSE;
}

// A helper function run in the job thread receiving the string that
// should be displayed in the textview.
void job_add_to_text_viewer(JobData *job_data,
                            const char *info)
{
    job_data->info = g_strdup(info);

    // Lock mutex to make sure that we will receive the condition signal
    g_mutex_lock(job_data->update_mutex);
    g_idle_add((GSourceFunc)cb_update_job, job_data);

    // Wait for cb_update_job to tell me that the update is done
    g_cond_wait(job_data->update_cond,
                job_data->update_mutex);
    g_mutex_unlock(job_data->update_mutex);
}

// The thread entry point. It will do the job, send the data to the
// GUI and self destruct when it is done.
static gpointer thread_worker(JobData *job_data)
{
    FILE *fh = popen(job_data->cmd,"r");
    printf("thread_worker running %s\n", job_data->cmd);
    GIOChannel *gh = g_io_channel_unix_new(fileno(fh));
    GIOStatus status;
    GError *error = NULL;
    gsize length;
    gsize terminator_pos;
    gchar *str_return;

    while( (status = g_io_channel_read_line(gh,
                                            &str_return,
                                            &length,
                                            &terminator_pos,
                                            &error)) == G_IO_STATUS_NORMAL)
{
        job_add_to_text_viewer(job_data,
                               str_return);
        g_free(str_return);
    }

    g_io_channel_unref(gh);
    pclose(fh);
    job_add_to_text_viewer(job_data,
                           "Job done!");

    g_mutex_unlock(job_data->mutex_to_run);
    g_thread_exit(NULL);
    if (job_data)
        job_data_free(job_data);

    return NULL;
}

// Callback for the run button
void cb_clicked_run(GtkWidget *widget,
                    gpointer  user_data)
{
    const gchar *cmd = gtk_entry_get_text(GTK_ENTRY(w_entry_cmd));
    GError *error = NULL;
    printf("Run %s\n", cmd);

    // create a thread that will run the external command
    // tbd...
    JobData *job_data = job_data_new(cmd, mutex_one_job_at_a_time);
    g_thread_create((GThreadFunc)thread_worker, job_data, FALSE, &error);
    if (error) {
        printf("%s\n", error->message);
        g_error_free(error);
    }
}

void create_gui()
{
    GtkWidget *w_top = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    GtkWidget *w_vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(w_top), w_vbox);

    GtkWidget *w_scrolled_win = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_set_size_request(w_scrolled_win, 500, 400);
    gtk_box_pack_start(GTK_BOX(w_vbox), w_scrolled_win,
                       TRUE, TRUE, 0);
    w_text_view = gtk_text_view_new();
    gtk_container_add(GTK_CONTAINER(w_scrolled_win),
                      w_text_view);

    // An hbox with an entry for the command to run
    GtkWidget *w_hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(w_vbox), w_hbox,
                       FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(w_hbox), gtk_label_new("Command:"),
                       FALSE, FALSE, 0);
    w_entry_cmd = gtk_entry_new();
    gtk_entry_set_text(GTK_ENTRY(w_entry_cmd),
                       "perl -e '$|++; for $i (0..10) { print \"$i\\n\";
sleep 1 }'");
    gtk_box_pack_start(GTK_BOX(w_hbox), w_entry_cmd,
                       TRUE, TRUE, 0);
    GtkWidget *w_button_run = gtk_button_new_with_label("Run");
    gtk_box_pack_start(GTK_BOX(w_hbox), w_button_run,
                       FALSE, FALSE, 0);
    g_signal_connect(w_button_run, "clicked",
                     G_CALLBACK(cb_clicked_run), NULL);

    // Finally quit button
    GtkWidget *w_button_quit = gtk_button_new_with_label("Quit");
    gtk_box_pack_start(GTK_BOX(w_vbox), w_button_quit,
                       FALSE, FALSE, 0);
    g_signal_connect(w_button_quit, "clicked",
                     G_CALLBACK(gtk_main_quit), NULL);


    gtk_widget_show_all(w_top);
}

int main(int argc, char **argv)
{
    // init threads
    g_thread_init(NULL);
    gtk_init(&argc, &argv);

    // Only allow running one command at a time
    mutex_one_job_at_a_time = g_mutex_new();

    create_gui();

    gtk_main();
    exit(0);
}


On Fri, Nov 20, 2009 at 10:38, Emmanuel Rodriguez <
emmanuel.rodrig...@gmail.com> wrote:

> On Thu, Nov 19, 2009 at 10:11 PM, Till Harbaum / Lists <li...@harbaum.org
> >wrote:
>
> > Hi,
> >
> > i am trying to run a text mode application in the background of my gtk
> one
> > and display its output in a textview. I know this is supposed to be done
> > using g_spawn_async_with_pipes and then link to the output via
> > g_io_add_watch. I even got something that sort of works, but the output
> is
> > very much delayed and comes in chunks and worse, the CPU load is at max
> > while and after i run my code.
> >
> > Are there any examples for doing this? There must be many programs doing
> > something similar to run e.g. some little helper program or similar.
> >
> > Can you consider using the VteTerminal[1] widget? This is the same
> terminal
> widget used in gnome-terminal and in Ubuntu/Debian graphical frontends to
> dpkg. It lets you embed a ternimal that you can bind to any program, you're
> not forced to bind it to a shell.
>
> VteTerminal will take care of monitoring your process and grabbing all
> output for you. The only drawback is that the widget doesn't work on win32.
>
> [1] http://library.gnome.org/devel/vte/unstable/VteTerminal.html
>
> Emmanuel Rodriguez
> _______________________________________________
> gtk-app-devel-list mailing list
> gtk-app-devel-list@gnome.org
> http://mail.gnome.org/mailman/listinfo/gtk-app-devel-list
>
_______________________________________________
gtk-app-devel-list mailing list
gtk-app-devel-list@gnome.org
http://mail.gnome.org/mailman/listinfo/gtk-app-devel-list

Reply via email to