fang0654 has asked for the wisdom of the Perl Monks concerning the following question:

I am pretty new to Perl, and writing my first program in Gtk2. I have a program that executes a string of shell commands when a button is pressed. I am trying to get the STDOUT/STDERR from the console and into a TextView box that I have. I have it somewhat working, with the following code:
# Code to open the filehandle my $fh = FileHandle->new(); open ($fh, "tail -f /usr/rip/cdimport.log |"); # Code to draw the actual textview $frame = Gtk2::Frame->new('Console Window'); my $ctextBox = Gtk2::TextView->new; my $cscroll = Gtk2::ScrolledWindow->new; my $cevent_box = Gtk2::EventBox->new; $color = Gtk2::Gdk::Color->parse ("black"); $cevent_box->modify_bg ('normal', $color); my $calign = Gtk2::Alignment->new (0.5, 0.5, 1.0, 1.0); $calign->set_border_width (1); $cevent_box->add ($calign); $calign->add ($ctextBox); $cscroll->add($cevent_box); my $cbuffer = $ctextBox->get_buffer; my $cend_mark = $cbuffer->create_mark( 'end', $cbuffer->get_end_iter, +FALSE ); $cbuffer->signal_connect( insert_text => sub { $ctextBox->scroll_to_mark( $cend_mark, 0.0, TRUE, 0.0, 0.0 ); } ); $frame->add($cscroll); $box1->pack_end($frame, TRUE, TRUE, 0); $ctextBox->set_buffer($cbuffer); $ctextBox->set_editable(FALSE); $cbuffer->set_text("Console window"); # Code to set up the watch on the opened filehandle my $ctag; $ctag = Gtk2::Helper->add_watch ( $fh->fileno, 'in', sub { watch_console ($ctag, $fh, $cbuffer) } ); sub watch_console { my ($fd, $fh, $buffer) = @_; my $sysbuffer; if ( not sysread($fh, $sysbuffer, 4096) ) { # obviously the connected pipe was closed Gtk2::Helper->remove_watch ($fd) or die "couldn't remove watcher"; close($fh); return 1; } $buffer->set_text($sysbuffer); return 1; }
Then I would run the system commands with the following:
system("find /tmp/$id/ -iname \"*.wav\" -print0 | xargs -0 -n +1 -I '{}' ffmpeg -i '{}' -f mp3 '{}'.mp3 2>> /usr/rip/cdimport.log")
This works, to an extent. The refresh of the textview is slow and fragmented. Any suggestions on a better implementation?

BTW, I know that the scrollview doesn't work in the above piece of code - I'll figure that one out next.

Replies are listed 'Best First'.
Re: Realtime update of a textbuffer from STDOUT/STDERR in Gtk2
by zentara (Cardinal) on Aug 31, 2007 at 16:51 UTC
    You should always show a complete, working example, so we don't have to guess or flesh out your script to test. From your complaint that the textview refresh is slow(fragmented), you probably need to force an update
    $buffer->set_text($sysbuffer); Gtk2->main_iteration while Gtk2->events_pending;
    is an often used code line that forces the gtk2 event loop to do one loop, and update everything.

    Here is a pretty good complete scrolled textview example to help you out. (A commented out IO::Pipe method works too)

    #!/usr/bin/perl -w use strict; use Glib qw(TRUE FALSE); use Gtk2 -init; use Gtk2::Helper; #use IO::Pipe; #open a pipe to a command #my $fh = new IO::Pipe; #$fh->reader("top -b"); #$fh->reader("counter1.pl"); #open(FH, "counter1.pl |") or die "$!\n"; open(FH, "top -b |") or die "$!\n"; #add a watch to this file handle my $helper_tag = Gtk2::Helper->add_watch(fileno FH, 'in',sub{ &watch_callback('FH'); }); #standard window creation, placement, and signal connecting my $window = Gtk2::Window->new('toplevel'); $window->signal_connect('delete_event' => sub { exit;}); $window->set_border_width(5); $window->set_position('center_always'); my $hbox = Gtk2::HBox->new(); $hbox->set( "border_width" => 0 ); $window->add($hbox); my $scroll = Gtk2::ScrolledWindow->new; $scroll->set_size_request(600,600); my $textview = Gtk2::TextView->new; my $buffer = $textview->get_buffer; create_tags($buffer); $scroll->add($textview); $hbox->pack_start($scroll,1, 1, 1 ); # expand?, fill?, padding; $window->show_all(); #our main event-loop Gtk2->main; sub watch_callback { my ($fh) = @_; my $line; #read 1000 caracters of the buffer #$fh->sysread($line,1000); sysread(FH,$line,1000); #remove the newline # print $line; chomp $line; if($line){ $buffer->insert_with_tags_by_name($buffer->get_end_iter, $l +ine,'blue'); }else{ Gtk2::Helper->remove_watch($helper_tag); } #absolutely need the main iteration here, or you #won't get the end iter correctly Gtk2->main_iteration while Gtk2->events_pending; $textview->scroll_to_iter($buffer->get_end_iter,0,0,0,0); # my $end_mark = $buffer->create_mark( 'end', $buffer->get_end_ +iter, FALSE ); # $textview->scroll_to_mark( $end_mark, 0.0, FALSE, 0.0, 1.0 ); #important so we can loop again return 1; } ###########################################3 sub create_tags{ my $buffer = shift; $buffer->create_tag('blue', foreground => 'blue', ); $buffer->create_tag('col', foreground => 'green', ); } ######################################### __END__

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
Re: Realtime update of a textbuffer from STDOUT/STDERR in Gtk2
by almut (Canon) on Aug 31, 2007 at 16:51 UTC

    Not sure what exactly you mean by "slow and fragmented" (I haven't run your program), but if it's updating in about 1 sec intervals, you could try to reduce the sleep interval of tail, for example to 0.1 sec

    -s, --sleep-interval=S with -f, sleep for approximately S seconds (default 1.0) between i +terations.

    Just an idea.

Re: Realtime update of a textbuffer from STDOUT/STDERR in Gtk2
by fang0654 (Initiate) on Aug 31, 2007 at 18:02 UTC
    Thank you both. The option to tail definately handles the 'slow' part. Now I just need to handle the fragmented part. I think my problem is in the fact that everytime new data is in the filehandle, the buffer is overwritten. Is there a way to append data to the end of the buffer as it comes in? I'm sorry I didn't post the full program before, here it is (Excuse the sloppiness - it's been a learning experience):
      Your code has too much in it, for me to run. I need to get Linux::CDROM, setup a bunch of directories, run as root, etc. If you want answers to questions, make the simplest possible script that demonstrates the problem. That way people can run it, and easily see the glitch. Usually when you do this simplification, you will answer your own question. This script just has too much clutter for me to make sense out of.

      I think my problem is in the fact that everytime new data is in the filehandle, the buffer is overwritten. Is there a way to append data to the end of the buffer as it comes in?

      Well, you are overwriting it with set_text. You can make a global variable, that you append to

      my $buftext =''; ..... $buftext .= $sysbuffer; $buffer->set_text($buftext); # or better.....insert at the end_iter $buffer->insert( $buffer->get_end_iter, $sysbuffer );

      The end_mark and end_iter concepts are hard to get right, it's best to make a few simple examples for yourself to play with them.

      Also, if you read the warning messages, you should change 'add' to 'add_with_viewport' at the lines specified; it will make the scrollbars work better.


      I'm not really a human, but I play one on earth. Cogito ergo sum a bum
        Thank you, that change works like a charm. Your help is much appreciated!