1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
|
;#!/usr/bin/perl
#
# Example script showing how to use Term::VT102 with an SSH command. SSHs to
# localhost and runs a shell, and dumps what Term::VT102 thinks should be on
# the screen.
#
# Logs all terminal output to STDERR if STDERR is redirected to a file.
#
use Term::VT102;
use IO::Handle;
use POSIX ':sys_wait_h';
use IO::Pty;
use strict;
$| = 1;
my $cmd = 'ssh -v -t localhost';
# Create the terminal object.
#
my $vt = Term::VT102->new (
'cols' => 80,
'rows' => 24,
);
# Convert linefeeds to linefeed + carriage return.
#
$vt->option_set ('LFTOCRLF', 1);
# Make sure line wrapping is switched on.
#
$vt->option_set ('LINEWRAP', 1);
# Create a pty for the SSH command to run on.
#
my $pty = new IO::Pty;
my $tty_name = $pty->ttyname ();
if (not defined $tty_name) {
die "Could not assign a pty";
}
$pty->autoflush ();
# Run the SSH command in a child process.
#
my $pid = fork;
if (not defined $pid) {
die "Cannot fork: $!";
} elsif ($pid == 0) {
#
# Child process - set up stdin/out/err and run the command.
#
# Become process group leader.
#
if (not POSIX::setsid ()) {
warn "Couldn't perform setsid: $!";
}
# Get details of the slave side of the pty.
#
my $tty = $pty->slave ();
$tty_name = $tty->ttyname();
# Linux specific - commented out, we'll just use stty below.
#
# # Set the window size - this may only work on Linux.
# #
# my $winsize = pack ('SSSS', $vt->rows, $vt->cols, 0, 0);
# ioctl ($tty, &IO::Tty::Constant::TIOCSWINSZ, $winsize);
# File descriptor shuffling - close the pty master, then close
# stdin/out/err and reopen them to point to the pty slave.
#
close ($pty);
close (STDIN);
close (STDOUT);
open (STDIN, "<&" . $tty->fileno ())
|| die "Couldn't reopen " . $tty_name . " for reading: $!";
open (STDOUT, ">&" . $tty->fileno())
|| die "Couldn't reopen " . $tty_name . " for writing: $!";
close (STDERR);
open (STDERR, ">&" . $tty->fileno())
|| die "Couldn't redirect STDERR: $!";
# Set sane terminal parameters.
#
system 'stty sane';
# Set the terminal size with stty.
#
system 'stty rows ' . $vt->rows;
system 'stty cols ' . $vt->cols;
# Finally, run the command, and die if we can't.
#
exec $cmd;
die "Cannot exec '$cmd': $!";
}
my ($cmdbuf, $stdinbuf, $iot, $eof, $prevxy, $died);
# IO::Handle for standard input - unbuffered.
#
$iot = new IO::Handle;
$iot->fdopen (fileno(STDIN), 'r');
# Removed - from Perl 5.8.0, setvbuf isn't available by default.
# $iot->setvbuf (undef, _IONBF, 0);
# Set up the callback for OUTPUT; this callback function simply sends
# whatever the Term::VT102 module wants to send back to the terminal and
# sends it to the child process - see its definition below.
#
$vt->callback_set ('OUTPUT', \&vt_output, $pty);
# Set up a callback for row changes, so we can process updates and display
# them without having to redraw the whole screen every time. We catch CLEAR,
# SCROLL_UP, and SCROLL_DOWN with another function that triggers a
# whole-screen repaint. You could process SCROLL_UP and SCROLL_DOWN more
# elegantly, but this is just an example.
#
my $changedrows = {};
$vt->callback_set ('ROWCHANGE', \&vt_rowchange, $changedrows);
$vt->callback_set ('CLEAR', \&vt_changeall, $changedrows);
$vt->callback_set ('SCROLL_UP', \&vt_changeall, $changedrows);
$vt->callback_set ('SCROLL_DOWN', \&vt_changeall, $changedrows);
# Set stdin's terminal to raw mode so we can pass all keypresses straight
# through immediately.
#
system 'stty raw -echo';
$eof = 0;
$prevxy = '';
$died = 0;
while (not $eof) {
my ($rin, $win, $ein, $rout, $wout, $eout, $nr, $didout);
($rin, $win, $ein) = ('', '', '');
vec ($rin, $pty->fileno, 1) = 1;
vec ($rin, $iot->fileno, 1) = 1;
select ($rout=$rin, $wout=$win, $eout=$ein, 1);
# Read from the SSH command if there is anything coming in, and
# pass any data on to the Term::VT102 object.
#
$cmdbuf = '';
$nr = 0;
if (vec ($rout, $pty->fileno, 1)) {
$nr = $pty->sysread ($cmdbuf, 1024);
$eof = 1 if ((defined $nr) && ($nr == 0));
if ((defined $nr) && ($nr > 0)) {
$vt->process ($cmdbuf);
syswrite STDERR, $cmdbuf if (! -t STDERR);
}
}
# End processing if we've gone 1 round after SSH died with no
# output.
#
$eof = 1 if ($died && $cmdbuf eq '');
# Do your stuff here - use $vt->row_plaintext() to see what's on various
# rows of the screen, for instance, or before this main loop you could set
# up a ROWCHANGE callback which checks the changed row, or whatever.
#
# In this example, we just pass standard input to the SSH command, and we
# take the data coming back from SSH and pass it to the Term::VT102 object,
# and then we repeatedly dump the Term::VT102 screen.
# Read key presses from standard input and pass them to the command
# running in the child process.
#
$stdinbuf = '';
if (vec ($rout, $iot->fileno, 1)) {
$nr = $iot->sysread ($stdinbuf, 16);
$eof = 1 if ((defined $nr) && ($nr == 0));
$pty->syswrite ($stdinbuf, $nr) if ((defined $nr) && ($nr > 0));
}
# Dump what Term::VT102 thinks is on the screen. We only output rows
# we know have changed, to avoid generating too much output.
#
$didout = 0;
foreach my $row (sort keys %$changedrows) {
printf "\e[%dH%s\r", $row, $vt->row_sgrtext ($row);
delete $changedrows->{$row};
$didout ++;
}
if (($didout > 0) || ($prevxy != ''.$vt->x.','.$vt->y)) {
printf "\e[%d;%dH", $vt->y, ($vt->x > $vt->cols ? $vt->cols : $vt->x);
}
# Make sure the child process has not died.
#
$died = 1 if (waitpid ($pid, &WNOHANG) > 0);
}
print "\e[24H\r\n";
$pty->close;
# Reset the terminal parameters.
#
system 'stty sane';
# Callback for OUTPUT events - for Term::VT102.
#
sub vt_output {
my ($vtobject, $type, $arg1, $arg2, $private) = @_;
if ($type eq 'OUTPUT') {
$pty->syswrite ($arg1, length $arg1);
}
}
# Callback for ROWCHANGE events. This just sets a time value for the changed
# row using the private data as a hash reference - the time represents the
# earliest that row was changed since the last screen update.
#
sub vt_rowchange {
my ($vtobject, $type, $arg1, $arg2, $private) = @_;
$private->{$arg1} = time if (not exists $private->{$arg1});
}
# Callback to trigger a full-screen repaint.
#
sub vt_changeall {
my ($vtobject, $type, $arg1, $arg2, $private) = @_;
for (my $row = 1; $row <= $vtobject->rows; $row++) {
$private->{$row} = 0;
}
}
# EOF
|