Re: How does one pipe output from process to text buffer?



On Wed, 2006-12-27 at 06:17 +0000, tomas tuxteam de wrote: 
On Tue, Dec 26, 2006 at 10:41:33PM -0500, Tony Freeman wrote:
Hello,

I need some example code or a tutorial for how to pipe output from a
process to a GtkTextBuffer.

The idea of this program is to spawn off 6 or more ssh commands and have
the output go to it's own textbuffer in it's assigned notebook page.

This is what I have so far.  Now I need the output from the process to
go to it's respective text buffer.


void on_confirm_okbutton_clicked (GtkWidget *widget, gpointer data)
{
    /* ****************************************
     * Globals used:
     *      serverlist : string array of server names
     *      command    : the options to send to mainscript program
     *      notebook1  : notebook widget
     */
[...]
            /* now fire off the process and redirect output
             * to our textbuffer */
            GPid pid;
            g_spawn_async_with_pipes( NULL,
                                      ssh_command, 
                                      NULL, 
                                      G_SPAWN_CHILD_INHERITS_STDIN,
                                      NULL,
                                      NULL,
                                      &pid,
                                      NULL, NULL, NULL,
                                      NULL );
    }

This looks like the interesting part. First of all, I doubt
G_SPAWN_CHILD_INHERITS_STDIN is what you are after. I guess you'll have
to give each child its own file descriptors (possibly all three, to let
the user see possible errors), like so:

|             GPid pid;
|             gint stdin, stdout, stderr;
|             g_spawn_async_with_pipes( NULL,        /* workdir */
|                                       ssh_command, /* argv */
|                                       NULL,        /* envp */
|                                       NULL,        /* flags */
|                                       NULL,        /* child setup func */
|                                       NULL,        /* --- udata */
|                                       &pid,
|                                       &stdin, &stdout, &stderr,
|                                       NULL );      /* error */

Now you'll have to catch whatever comes from stdout and stderr and
append it to the text buffer of the text widget (possibly making sure
that the in and err part don't get too messed up, and possibly making
nifty things like painting the errors in red :-). Likewise, you'll have
to (somehow) collect user input, sending it via stdin to the process
(when it is ready to take input).

To this purpose, you may wrap the descriptors in GIOChannels and watch
the channels with g_io_add_watch(), which is just a fancy way to use the
select() system call. I'll sketch this with stdin (the others work
analogously). In a full-fledged version you'll typically want to collect
all this stuff in a struct which you may attach to the corresponding
notebook widget, so you may easily find all those thingies in callbacks,
etc -- like so:

|  typedef struct {
|    GIOChannel *stdin;
|    GIOChannel *stdout;
|    GIOChannel *stderr;
|    /* other useful things like the text widget go here */
|  } ssh_slave;

Now comes the fun part. After the spawn_async() (and after checking for
errors ;-) you do:

|  ssh_slave *slave = g_new(ssh_slave, 1);
|  slave->stdin = g_io_channel_unix_new(stdin);
|  GIOFunc watch_slave;
|  g_io_channel_add_watch_full(ch_stdin, G_IO_IN, watch_slave, slave);


Then you define your watch function (I'd use the same function for all
three channels, but I'm a really, really lazy person ;-)

| gboolean watch_slave(GIOChannel *src, GIOCondition *cond, gpointer udata)
| {
|   ssh_slave *slave = (ssh_slave *) udata; /* hopefully ;-) */
|   gchar buf[1024]; /* or another meaningful size */
|   GIOstatus st;
|   GIOError *err;
| 
|   /* this is for stdin, stderr: */
|   st = g_io_channel_read_chars(src, buf, sizeof(buf), &got, &err);
|   /* handle errors */
|   ...
|   /* append whatever you got to the text widget, which you can hopefully
|      pull from slave */
|   ...
|   /* Do likewise for stdout. You might have a buffer of pending chars
|      hidden in slave->xxx and add/remove the "write watch" depending
|      on whether there is write pending (otherwise the program will hog
|      CPU spinning here). */
|   ...
|   return TRUE; /* typically. Return FALSE if you want the watch removed */
| }

That's at least how I go about this. Hope it gives you some ideas.

Regards
- -- tomÃs

Thanks!

I'm getting really close now!  Thanks for your help.

Currently, I have the program doing what I want but for one thing ... it
freezes until all my ssh commands have returned.  Once control is
returned, all the notebook pages have all the proper ssh output
associated with it :-)

I'd like to be able to click on each notebook page as the ssh commands
are running, and be able to see real-time output being written.

Anyway ... 

This is what I have so far:

[...]

GPid pid;
gint stdout;
GIOChannel *gioout;
g_spawn_async_with_pipes( NULL, 
       ssh_command, 
       NULL, 
       G_SPAWN_SEARCH_PATH,
       NULL,
       NULL,
       &pid,
       NULL, &stdout, NULL,
       NULL );
gioout = g_io_channel_unix_new(stdout);
g_io_channel_set_encoding(gioout, NULL, NULL);
g_io_add_watch(gioout, 
(G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_NVAL),
(GIOFunc)watch_out, 
textview);

[...]

Note ... if I replace G_SPAWN_SEARCH_PATH with NULL I get a compiler
error ... so I keep it at G_SPAWN_SEARCH_PATH.

Also ... it took me all day and night to finally brew up the combination
of (G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_NVAL).  I was under the die-hard
impression that I wanted to watch for G_IO_OUT (after all I want ssh
output), but it turns out I was thinking completely opposite of what
really needs to be thought?  Anyway, this combination finally started
putting output in the notebook text buffers.

Here is the watch_out callback function:

gboolean watch_out (GIOChannel *channel, GIOCondition condition,
gpointer data)
{
GIOStatus status = G_IO_STATUS_NORMAL;
GtkTextView *textview;
GtkTextBuffer *buffer;
gchar buf[1024];
gchar *buftext;
gsize bytes_read;

/* set up pointers to our textbuffer */
textview = GTK_TEXT_VIEW((GtkWidget *)data);
buffer = gtk_text_view_get_buffer(textview);

/* send ssh output to our gui */
status = g_io_channel_read_chars(channel, buf, sizeof(buf), 
                                 &bytes_read, NULL);
buftext = g_strdup_printf("%s", buf); /*creates null-term str*/
gtk_text_buffer_set_text(buffer, buftext, -1);

/* decide if we should close the channel */
switch (status) {
     case G_IO_STATUS_NORMAL:
          return TRUE;
     default:
          return FALSE;
}
}


I've spent some time experiementing with the buf.  I've reduced it down
to 64 (buf[64]) ... when the application becomes un-frozen and I can
click around in the notebook pages, I see that only the last couple of
lines of ssh output has been written to the page.

It seems the buf[] is filling up with data and then being discarded
without writing that data to my textbuffer - until the end - at which
point it writes the data to the textbuffer as it dies away.

Sound reasonable?  How do I fix this?

-- Tony







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