Re: Capture console app output into texview?



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 grobgeld 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 rodriguez gmail com> wrote:

On Thu, Nov 19, 2009 at 10:11 PM, Till Harbaum / Lists <lists 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




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]