Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

GUI to dynamically show program output

by markong (Pilgrim)
on Feb 04, 2021 at 00:37 UTC ( [id://11127874]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks,

I need some help with an issue related with GUI construction: I need a very simple GUI to show the output of a series of commands. To give you an idea of what I'm after, it's a thing very similar to the GUI I've seen often on many Debian based distros when system updates are performed (if I remember well): a text pane/view which scrolls following whatever gets out from apt/dpkg in real-time.

My researches on CPAN have suggested that there exists something similar which could be reused in the form of Gtk2::Ex::TextView::FollowAppend. I'm posting a relatively simple thing I've came up with, but I've never touched GTK2 and I'm in a bit of a rush, so I thought I could use some help here on how to "send" the output I'm collecting with IPC::Run to the Gtk2::TextBuffer.

I'm reading the API doc/details here http://gtk2-perl.sourceforge.net/doc/pod/Gtk2/TextBuffer.html and here http://gtk2-perl.sourceforge.net/doc/gtk2-perl-study-guide/x2662.html but to repeat I'm not really familiar with the GTK2 API, so I couldn't find the "right" signal to update the Gtk2::Ex::TextView::FollowAppend Gtk2::TextBuffer when something gets "pump-ed" into $out.

I've also tried inspecting the source of the main distribution module (Gtk2::Ex::ErrorTextDialog) to see how the Gtk2::Ex::TextView::FollowAppend is updated but it's pretty convoluted.

I should also add that the update of the buffer should take into account the fact that the commands print some textual "progress bar" which re-set the cursor very often and I fear it could cause problems if the text is just appended to the Gtk2::TextBuffer.

Please ignore the rawness of the GUI, I'm just interested in the mechanics of the update for now. Also, I'm open to suggestion on alternative GUIs (on GNU/Linux) but I feel like that I miss only some pieces here.

Thanks for the attention.

#!/usr/bin/perl use strict; use warnings FATAL => 'all'; use Gtk2 '-init'; use Gtk2::Ex::TextView::FollowAppend; use IPC::Run qw( start pump finish timeout ); use FindBin qw($Bin); my $window = Gtk2::Window->new; $window->set_title('Diabytes meters downloader'); $window->signal_connect (destroy => sub { Gtk2->main_quit; }); $window->set_border_width(3); my $vbox = Gtk2::VBox->new(0, 6); $window->add($vbox); my $frame = Gtk2::Frame->new('Buttons'); $vbox->pack_start($frame, 1, 1, 0); $frame->set_border_width(3); my $hbox = Gtk2::HBox->new(0 ,6); $frame->add($hbox); $hbox->set_border_width(3); my $incButton = Gtk2::Button->new('_Click Me'); $hbox->pack_start($incButton, 0 , 0, 0); my $quitButton = Gtk2::Button->new('_Quit'); $hbox->pack_start($quitButton, 0, 0 , 0); $quitButton->signal_connect( clicked => sub { Gtk2::main_quit(); }); my $scrolled = Gtk2::ScrolledWindow->new; $scrolled->set_policy('never', 'always'); $vbox->pack_start($scrolled, 1,1,0); my $textBuffer = Gtk2::TextBuffer->new(); $incButton->signal_connect( clicked => sub { &makeSomeNoise($textBuffer); }); my $textView = Gtk2::Ex::TextView::FollowAppend->new_with_buffer($text +Buffer); $textView->set(wrap_mode => 'char', editable => 0); $scrolled->add($textView); $window->set_default_size(500, 250); $window->set_position('GTK_WIN_POS_CENTER'); $window->show_all; Gtk2->main; sub makeSomeNoise { my $textBuff = shift; # IPC::Run setup my $file = File::Spec->catfile($Bin, 'somefile.txt'); my $timeFile = File::Spec->catfile($Bin, 'downloadsession.tm'); my $scriptFile = File::Spec->catfile($Bin, 'downloadsession.out') +; my @cat = ('cat', $file); #my @command = qw(scriptreplay -t downloadsession.tm -s downloadse +ssion.out); my @command = ('scriptreplay', '-t', $timeFile, '-s', $scriptFile) +; my ($in, $out, $err, $t); #my $h = start( \@cat, \$in, \$out, $t = timeout( 180 ) ); my $h = start( \@command, \$in, \$out, $t = timeout( 180 ) ); while ($h->pump) { #&refreshTextBuffer($textBuffer, $out); #$textBuff->set_text($out); #print($out); } $h->finish(); }

Replies are listed 'Best First'.
Re: GUI to dynamically show program output
by Discipulus (Canon) on Feb 04, 2021 at 07:31 UTC
Re: GUI to dynamically show program output
by kcott (Archbishop) on Feb 04, 2021 at 07:55 UTC

    G'day markong,

    "I'm open to suggestion on alternative GUIs"

    That's lucky, because I don't know Gtk2. :-)

    Here's a Tk solution which, I believe, does the sort of thing you want.

    #!/usr/bin/env perl use strict; use warnings; use Tk; use Tk::ROText; my $mw = MainWindow::->new(); $mw->geometry('512x288+100+150'); my $ctrl_F = $mw->Frame( )->pack(-side => 'left', -fill => 'y'); my $text_F = $mw->Frame( )->pack(-side => 'left', -fill => 'both', -expand => 1); my $fill_x = $ctrl_F->Button(-text => 'Fill X')->pack(); my $fill_y = $ctrl_F->Button(-text => 'Fill Y')->pack(); my $text = $text_F->Scrolled('ROText', -scrollbars => 'osoe', -wrap => 'none', )->pack(-fill => 'both', -expand => 1); $fill_x->configure(-command => sub { $_->configure(-state => 'disabled') for $fill_x, $fill_y; my $ms = 50 * (1 + int rand 10); my $iters = 1 + int rand 100; my $iter = 0; my $id; $id = $mw->repeat($ms, sub { if (++$iter < $iters) { text_show($text, 'X'); } else { text_show($text, "\n"); $id->cancel; $_->configure(-state => 'normal') for $fill_x, $fill_y; } }); }); $fill_y->configure(-command => sub { $_->configure(-state => 'disabled') for $fill_x, $fill_y; my $ms = 50 * (1 + int rand 10); my $iters = 1 + int rand 100; my $iter = 0; my $id; $id = $mw->repeat($ms, sub { if (++$iter < $iters) { my $str = 'Y' x (1 + int rand 100) . "\n"; text_show($text, $str); } else { $id->cancel; $_->configure(-state => 'normal') for $fill_x, $fill_y; } }); }); MainLoop; { my ($line, $char); BEGIN { ($line, $char) = (1, 0) } sub text_show { my ($text, $str) = @_; $text->insert("$line.$char" => $str); $text->see("$line.$char"); if (-1 < index $str, "\n") { $line += $str =~ y/\n//; $char = length(substr $str, rindex $str, "\n") - 1; } else { $char += length $str; } } }

    Notes:

    • The "Fill X" button simulates the textual "progress bar" you mentioned.
    • The "Fill Y" button simulates lines of output from your commands.
    • The anonymous block at the end is where the output display occurs. You'll want to feed you command/progressbar output to text_show().
    • I've allowed the text window to scroll horizontally as well vertically to show you that option. You may want to change -wrap => 'none' to -wrap => 'char' (which, I'd guess, is what wrap_mode => 'char' does in your Gtk2 code).
    • The Tk::ROText widget does, I assume, the same as the editable => 0 does in your Gtk2 code. Take a look at Tk::Text for all the non-read-only features.
    • The automatic scrolling is handled by $text->see("$line.$char");.
    • You may also want to look at Tk::fileevent. That's probably what I'd use; with the call to text_show() in the callback.

    — Ken

      kcott, thank you very much!

      The example really does what I need but unfortunately it doesn't show correctly the progress bar of the command. This is probably due to the fact that the textual progress bar includes a counter which is implemented by printing "\r" char (below the entire C function).

      void doProgress(char* label, size_t step, size_t total) { //progress width const int pwidth = 60; //minus label len size_t width = pwidth - strlen(label); size_t pos = (step * width) / total; size_t percent = (step * 100) / total; printf("\r%s%4d/%4d [", label, step, total); int i; //fill progress bar with = for (i = 0; i < pos; i++) { printf("%c", '='); } //fill progress bar with spaces printf("% *c", width - pos + 1, ']'); printf("%3d%%", percent); fflush(stdout); }

      The net result is that I get a mess displayed when I add the text to the TextView, because TextView doesn't interpret char escape sequences. I get the same result using Tk::DoCommand.

      After a little bit of searching I think that I could probably solve the thing by using pseudo-ttys in Tk or even embedding Xterm(1) inside Tk !

      The two examples are all in python, so I'd just ask if the same can be done with Perl Tk (maybe playing with IPC::Run module pseudo-ttys support) ?

      Comments and further suggestions are always welcome!

        "... really does what I need but ... doesn't show correctly ... probably due to ... printing "\r" char ..."

        Well, I obviously had to make an initial guess about how the textual progressbar was implemented; however, you'll be glad to know that can be very easily fixed. :-)

        I rewrote the "Fill X" callback so that it gives a rough approximation of what your doProgress() is printing. I left in the all-important \r but didn't bother adding the percentage calculation.

        $fill_x->configure(-command => sub { $_->configure(-state => 'disabled') for $fill_x, $fill_y; my $ms = 50 * (1 + int rand 10); my $iters = 1 + int rand 100; my $iter = 0; my $id; my $label = 'LABEL'; $id = $mw->repeat($ms, sub { if (++$iter <= $iters) { text_show($text, sprintf "\r%s%4d/%4d [%s]", $label, $iter, $iters, '=' x $iter ); } else { text_show($text, "\nDone!\n"); $id->cancel; $_->configure(-state => 'normal') for $fill_x, $fill_y; } }); });

        I then added some code to text_show() to handle the \r.

        { my ($line, $char); BEGIN { ($line, $char) = (1, 0) } sub text_show { my ($text, $str) = @_; if (0 == index $str, "\r") { $str = substr $str, 1; $text->delete("$line.0", "$line.$char"); $char = 0; } $text->insert("$line.$char" => $str); if (-1 < index $str, "\n") { $line += $str =~ y/\n//; $char = length(substr $str, rindex $str, "\n") - 1; } else { $char += length $str; } $text->see("$line.$char"); } }

        You may notice that I also moved the $text->see("$line.$char") statement. It logically makes more sense for this to occur after $line and $char have been recalculated; although, visually I couldn't see any difference. It probably depends on the size of the GUI window, how you've implemented scrollbars, and the value of -wrap. I'll leave you to play around with that part.

        I didn't change anything else in the original code I posted. So, just change those two blocks of code; give it a whirl; and see how you go.

        Minor update: With the numbers being displayed, I noticed an off-by-one error in the "Fill X" callback: I changed ++$iter < $iters to ++$iter <= $iters. The same error exists in the "Fill Y" callback: fix it if you want; it would only be noticeable if $iters randomised to 1, in which case you'd get no output.

        — Ken

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11127874]
Approved by 1nickt
Front-paged by kcott
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (4)
As of 2024-04-23 23:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found