line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
package App::Textcast ; |
3
|
|
|
|
|
|
|
|
4
|
2
|
|
|
2
|
|
76407
|
use strict; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
83
|
|
5
|
2
|
|
|
2
|
|
12
|
use warnings ; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
70
|
|
6
|
2
|
|
|
2
|
|
13
|
use Carp qw(carp croak confess) ; |
|
2
|
|
|
|
|
9
|
|
|
2
|
|
|
|
|
141
|
|
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
2112
|
use English qw( -no_match_vars ) ; |
|
2
|
|
|
|
|
10655
|
|
|
2
|
|
|
|
|
11
|
|
9
|
|
|
|
|
|
|
$OUTPUT_AUTOFLUSH++; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
my $get_terminal_size ; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
BEGIN |
14
|
|
|
|
|
|
|
{ |
15
|
2
|
50
|
|
2
|
|
1217
|
if($OSNAME ne 'MSWin32') |
16
|
|
|
|
|
|
|
{ |
17
|
2
|
|
|
2
|
|
131
|
eval 'use Term::Size;' ; ## no critic (BuiltinFunctions::ProhibitStringyEval) |
|
2
|
|
|
|
|
1728
|
|
|
2
|
|
|
|
|
6177
|
|
|
2
|
|
|
|
|
59
|
|
18
|
2
|
50
|
|
|
|
11
|
croak "Error: $EVAL_ERROR" if $EVAL_ERROR; |
19
|
|
|
|
|
|
|
|
20
|
2
|
|
|
|
|
124
|
$get_terminal_size = eval ' sub { Term::Size::chars *STDOUT{IO} } ' ; ## no critic (BuiltinFunctions::ProhibitStringyEval) |
21
|
2
|
50
|
|
|
|
84
|
croak "Error: $EVAL_ERROR" if $EVAL_ERROR ; |
22
|
|
|
|
|
|
|
} |
23
|
|
|
|
|
|
|
else |
24
|
|
|
|
|
|
|
{ |
25
|
0
|
|
|
|
|
0
|
eval 'use Win32::Console;' ; ## no critic (BuiltinFunctions::ProhibitStringyEval) |
26
|
0
|
0
|
|
|
|
0
|
croak "Error: $EVAL_ERROR" if $EVAL_ERROR ; |
27
|
|
|
|
|
|
|
|
28
|
0
|
|
|
|
|
0
|
my $WIN32_CONSOLE = new Win32::Console; |
29
|
0
|
|
|
|
|
0
|
$get_terminal_size = eval { sub { $WIN32_CONSOLE->Size() } } ; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
30
|
0
|
0
|
|
|
|
0
|
croak "Error: $EVAL_ERROR" if $EVAL_ERROR ; |
31
|
|
|
|
|
|
|
} |
32
|
|
|
|
|
|
|
} |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
BEGIN |
35
|
|
|
|
|
|
|
{ |
36
|
2
|
|
|
|
|
23
|
use Sub::Exporter -setup => |
37
|
|
|
|
|
|
|
{ |
38
|
|
|
|
|
|
|
exports => [ qw(record_textcast play_textcast) ], |
39
|
|
|
|
|
|
|
groups => |
40
|
|
|
|
|
|
|
{ |
41
|
|
|
|
|
|
|
all => [ qw() ], |
42
|
|
|
|
|
|
|
}, |
43
|
2
|
|
|
2
|
|
1760
|
}; |
|
2
|
|
|
|
|
26120
|
|
44
|
|
|
|
|
|
|
|
45
|
2
|
|
|
2
|
|
864
|
use vars qw ($VERSION); |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
88
|
|
46
|
2
|
|
|
2
|
|
45
|
$VERSION = '0.06'; |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
50
|
|
|
|
|
|
|
|
51
|
2
|
|
|
2
|
|
1834
|
use Readonly ; |
|
2
|
|
|
|
|
5789
|
|
|
2
|
|
|
|
|
329
|
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
#~ http://www.termsys.demon.co.uk/vtansi.htm |
54
|
|
|
|
|
|
|
Readonly my $CLEAR => "\e[2J" ; |
55
|
|
|
|
|
|
|
Readonly my $HOME => "\e[1;1H" ; |
56
|
|
|
|
|
|
|
Readonly my $CLEAR_LINE => "\e[2K" ; |
57
|
|
|
|
|
|
|
Readonly my $SAVE_CURSOR_POSITION => "\e7" ; |
58
|
|
|
|
|
|
|
Readonly my $RESTORE_CURSOR_POSITION => "\e8" ; |
59
|
|
|
|
|
|
|
Readonly my $HIDE_CURSOR => "\e[?25l" ; |
60
|
|
|
|
|
|
|
Readonly my $SHOW_CURSOR => "\e[?25h" ; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Readonly my $EMPTY_STRING => q{} ; |
63
|
|
|
|
|
|
|
|
64
|
2
|
|
|
2
|
|
1963
|
use IO::Handle; |
|
2
|
|
|
|
|
13836
|
|
|
2
|
|
|
|
|
89
|
|
65
|
2
|
|
|
2
|
|
1705
|
use POSIX ':sys_wait_h'; |
|
2
|
|
|
|
|
16182
|
|
|
2
|
|
|
|
|
13
|
|
66
|
2
|
|
|
2
|
|
4182
|
use IO::Pty; |
|
2
|
|
|
|
|
19118
|
|
|
2
|
|
|
|
|
93
|
|
67
|
|
|
|
|
|
|
|
68
|
2
|
|
|
2
|
|
2148
|
use Term::VT102; |
|
2
|
|
|
|
|
19764
|
|
|
2
|
|
|
|
|
130
|
|
69
|
2
|
|
|
2
|
|
1922
|
use File::Slurp ; |
|
2
|
|
|
|
|
8745
|
|
|
2
|
|
|
|
|
174
|
|
70
|
2
|
|
|
2
|
|
1893
|
use Time::HiRes qw(gettimeofday tv_interval usleep); |
|
2
|
|
|
|
|
3763
|
|
|
2
|
|
|
|
|
10
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head1 NAME |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
App::Textcast - Light weight text casting |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head1 SYNOPSIS |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
use App::Textcast qw(record_textcast play_textcast) ; |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
record_textcast(COMMAND => 'bash') ; |
83
|
|
|
|
|
|
|
play_textcast(TEXTCAST_DIRECTORY => $input_directory) ; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=head1 DESCRIPTION |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
What's a textcast? |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
It's a screencast of a terminal session. The idea is to record the terminal session and replay |
91
|
|
|
|
|
|
|
it in another terminal without loosing resolution, as screencasts do, nor using much disk space due to |
92
|
|
|
|
|
|
|
conversion from text to video. The terminal session can run a shell or any other program. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Why textcasts? |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=over 2 |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=item * Size, I did a screen cast of a completion script, the size was 1.5 MB and |
99
|
|
|
|
|
|
|
it didn't look as good as the terminal. The same textcast was 10 KB (yes, |
100
|
|
|
|
|
|
|
10 Kilo Bytes) and it looked good. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=item * It is not possible to make a screencast of a real terminal, maybe via |
104
|
|
|
|
|
|
|
vnc but that's already too complicated |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=item * Documentation. I believe it is sometimes better to show "live" documentation |
107
|
|
|
|
|
|
|
than static text. I am planning to write a module that plays a textcast |
108
|
|
|
|
|
|
|
embedded in ones terminal. The text cast being controlled by the application |
109
|
|
|
|
|
|
|
that displays help. I also believe that it could be used as a complement |
110
|
|
|
|
|
|
|
to showing static logs or screenshots; an example is when someone describe |
111
|
|
|
|
|
|
|
a problem on IRC. Seeing what is being done is sometimes very helpful. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=item * Editing. |
114
|
|
|
|
|
|
|
possibility to add message |
115
|
|
|
|
|
|
|
possibility to add sound |
116
|
|
|
|
|
|
|
possibility to extend the time an image or a range of images is displayed |
117
|
|
|
|
|
|
|
concatenate text casts (and their indexes) |
118
|
|
|
|
|
|
|
remove portions of a text cast |
119
|
|
|
|
|
|
|
name part of the text cast (shows in the index) |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=back |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head1 DOCUMENTATION |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
See L and L subbroutines. |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=head1 SCRIPTS |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Two commands, B and B, are installed on your computer when you install this module. Use |
130
|
|
|
|
|
|
|
them to record and replay your text casts. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head2 Output |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
The textcast is a serie of files recorded in a directory. Tar/gzip the files before you send them. the compression ratio averages 95%. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
142
|
|
|
|
|
|
|
# recording |
143
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub record_textcast |
146
|
|
|
|
|
|
|
{ |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head2 record_textcast( %named_arguments ) |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
Records the terminal output of a command. The output is stored as a set of files in a directory. The |
151
|
|
|
|
|
|
|
directory is later passed as argument to L for display. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
use App::Textcast 'record_textcast' ; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
record_textcast |
156
|
|
|
|
|
|
|
( |
157
|
|
|
|
|
|
|
COMMAND => 'bash', |
158
|
|
|
|
|
|
|
OUTPUT_DIRECTORY => shift @ARGV, |
159
|
|
|
|
|
|
|
COMPRESS => $compress, |
160
|
|
|
|
|
|
|
COLUMNS => $columns, |
161
|
|
|
|
|
|
|
ROWS => $rows, |
162
|
|
|
|
|
|
|
) ; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
I |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
The arguments are named, order is not important. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=over 2 |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=item * COMMAND => $string - the name of the command to tun in a terminal. You most probably wan to run |
171
|
|
|
|
|
|
|
I or I |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=item * OUTPUT_DIRECTORY => $directory_path - Optional - the path to the directory where the textcast is to be |
174
|
|
|
|
|
|
|
recorded. This subroutine will create a directory if this option is not set. if this option is set, the directory |
175
|
|
|
|
|
|
|
should not exist. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=item * COMPRESS => $boolean - Not implemented |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=item * COLUMNS => $integer - Optional - Number of columns in the terminal. The current terminal columns |
180
|
|
|
|
|
|
|
number is used if this argument is not set. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=item * ROWS => $integer - Optional - Number of rows in the terminal. The current terminal rows number is |
183
|
|
|
|
|
|
|
used if this argument is not set. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=back |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
I - Nothing |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
I |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=over 2 |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=item * See check_output_directory |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=item * see create_vt102_sub_process |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=item * disk full error |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=back |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
See I. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=cut |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
0
|
1
|
|
my (%arguments) = @_; |
206
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
|
my ($terminal_columns, $terminal_rows) = $get_terminal_size->() ; |
208
|
|
|
|
|
|
|
|
209
|
0
|
|
|
|
|
|
my $output_directory = check_output_directory($arguments{OUTPUT_DIRECTORY}) ; |
210
|
0
|
|
0
|
|
|
|
my $vt_process = create_vt102_sub_process |
|
|
|
0
|
|
|
|
|
211
|
|
|
|
|
|
|
( |
212
|
|
|
|
|
|
|
$arguments{COMMAND}, |
213
|
|
|
|
|
|
|
$arguments{COLUMNS} || $terminal_columns, |
214
|
|
|
|
|
|
|
$arguments{ROWS} || $terminal_rows, |
215
|
|
|
|
|
|
|
) ; |
216
|
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
|
print $CLEAR ; |
218
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
|
my $previous_time = my $start_time = [gettimeofday] ; |
220
|
|
|
|
|
|
|
|
221
|
0
|
|
|
|
|
|
my ($screenshot_index, $sub_process_ended) = (0, 0) ; |
222
|
|
|
|
|
|
|
|
223
|
0
|
|
|
|
|
|
while (not $sub_process_ended) |
224
|
|
|
|
|
|
|
{ |
225
|
0
|
|
|
|
|
|
($sub_process_ended, my $screen_diff, my $cursor_x, my $cursor_y) = check_sub_process_output($vt_process) ; |
226
|
|
|
|
|
|
|
|
227
|
0
|
|
|
|
|
|
my $now = [gettimeofday] ; |
228
|
0
|
|
|
|
|
|
my $elapsed = tv_interval($previous_time, $now); |
229
|
0
|
|
|
|
|
|
$previous_time = $now ; |
230
|
|
|
|
|
|
|
|
231
|
0
|
|
|
|
|
|
my $screenshot_file_name = "$output_directory/$screenshot_index" ; |
232
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
|
write_file($screenshot_file_name, $screen_diff) ; |
234
|
|
|
|
|
|
|
|
235
|
0
|
|
|
|
|
|
my ($terminal_columns, $terminal_rows) = $get_terminal_size->() ; |
236
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
|
append_file |
238
|
|
|
|
|
|
|
( |
239
|
|
|
|
|
|
|
"$output_directory/index", |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
'{' |
242
|
|
|
|
|
|
|
. "file => $screenshot_index, " |
243
|
|
|
|
|
|
|
. sprintf('delay => %0.3f, ', $elapsed) |
244
|
|
|
|
|
|
|
. "cursor_x => $cursor_x, " |
245
|
|
|
|
|
|
|
. "cursor_y => $cursor_y, " |
246
|
|
|
|
|
|
|
. 'size => ' . length($screen_diff) . ', ' |
247
|
|
|
|
|
|
|
. "terminal_rows => $terminal_rows, " |
248
|
|
|
|
|
|
|
. "terminal_columns => $terminal_columns, " |
249
|
|
|
|
|
|
|
. "},\n" |
250
|
|
|
|
|
|
|
) ; |
251
|
|
|
|
|
|
|
|
252
|
0
|
|
|
|
|
|
$screenshot_index++ ; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
0
|
|
|
|
|
|
my $record_time = tv_interval($start_time, [gettimeofday]); |
256
|
0
|
|
|
|
|
|
printf("record_textcast: $screenshot_index frames in %.02f seconds. Textcast is in '$output_directory'.\r\n", $record_time) ; |
257
|
|
|
|
|
|
|
|
258
|
0
|
|
|
|
|
|
close_vt102_sub_process($vt_process) ; |
259
|
|
|
|
|
|
|
|
260
|
0
|
|
|
|
|
|
return ; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub check_output_directory |
266
|
|
|
|
|
|
|
{ |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=head2 [p] check_output_directory( $output_directory) |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
Check that the given output directory does B exist. If B<$output_directory> is not defined, a directory |
271
|
|
|
|
|
|
|
name is generated. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
I |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=over 2 |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=item * $output_directory - The name of the directory where the textcast is recorded |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=back |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
I - The directory where the textcast is recorded. |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
I |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=over 2 |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=item * Textcast directory already exists |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item * Path too long - length must be under 256 characters. |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=item * Invalid path - Path can only contain alphanumerics and path separator. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=back |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=cut |
296
|
|
|
|
|
|
|
|
297
|
0
|
|
|
0
|
1
|
|
my ($directory) = @_ ; |
298
|
|
|
|
|
|
|
|
299
|
0
|
0
|
|
|
|
|
unless(defined $directory) |
300
|
|
|
|
|
|
|
{ |
301
|
0
|
|
|
|
|
|
my $now_string = localtime; # e.g., "Thu Oct 13 04:54:34 1994" |
302
|
0
|
|
|
|
|
|
$now_string=~ s/[^[:digit:][:alpha:]]/_/sxmg ; |
303
|
|
|
|
|
|
|
|
304
|
0
|
|
|
|
|
|
$directory = "textcast_recorded_on_$now_string" ; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
0
|
0
|
|
|
|
|
if(-e $directory) |
308
|
|
|
|
|
|
|
{ |
309
|
0
|
|
|
|
|
|
local $ERRNO = 1 ; |
310
|
0
|
|
|
|
|
|
croak "Error: Textcast directory '$directory' already exists!\n" ; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
else |
313
|
|
|
|
|
|
|
{ |
314
|
|
|
|
|
|
|
#todo: get the max path on this platform |
315
|
0
|
|
|
|
|
|
local $ERRNO = 2 ; |
316
|
|
|
|
|
|
|
|
317
|
0
|
|
|
|
|
|
Readonly my $MAX_PATH_LENGTH => 256 ; |
318
|
0
|
0
|
|
|
|
|
croak 'Error: Path too long' if length($directory) > $MAX_PATH_LENGTH ; |
319
|
|
|
|
|
|
|
|
320
|
0
|
0
|
|
|
|
|
if($directory =~ /([[:alnum:]\/_-]+)/sxm) |
321
|
|
|
|
|
|
|
{ |
322
|
0
|
|
|
|
|
|
$directory = $1 ; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
else |
325
|
|
|
|
|
|
|
{ |
326
|
0
|
|
|
|
|
|
Readonly my $ERRNO_INVALID_PATH => 3 ; |
327
|
0
|
|
|
|
|
|
local $ERRNO = $ERRNO_INVALID_PATH ; |
328
|
0
|
|
|
|
|
|
croak 'Error: Invalid path! Path can only contain alphanumerics and path separator.' |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
0
|
0
|
|
|
|
|
mkdir $directory or croak "Can't create directory '$directory'! $!\n" ; |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
return $directory ; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
338
|
|
|
|
|
|
|
# Playing |
339
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub play_textcast |
342
|
|
|
|
|
|
|
{ |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=head2 play_textcast( %named_arguments) |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
Loads, checks, and initiates the textcast replay. Displays information after the textcast replay. |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
use App::Textcast 'play_textcast' ; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
play_textcast |
351
|
|
|
|
|
|
|
( |
352
|
|
|
|
|
|
|
TEXTCAST_DIRECTORY => $input_directory, |
353
|
|
|
|
|
|
|
OVERLAY_DIRECTORY => $overlay_directory, |
354
|
|
|
|
|
|
|
DISPLAY_STATUS => $display_status, |
355
|
|
|
|
|
|
|
START_PAUSED => $start_paused, |
356
|
|
|
|
|
|
|
) ; |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
I |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=over 2 |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=item * TEXTCAST_DIRECTORY - String - directory containing the textcast |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=item * OVERLAY_DIRECTORY - not implemented |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=item * DISPLAY_STATUS - Boolean - |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=item * START_PAUSED - not implemented |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=back |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
I - Nothing |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
I |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=over 2 |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=item * Terminal too small |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=item * interrupted by user |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=item * load_index |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=back |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=cut |
387
|
|
|
|
|
|
|
|
388
|
0
|
|
|
0
|
1
|
|
my (%arguments) = @_ ; |
389
|
|
|
|
|
|
|
|
390
|
0
|
0
|
|
|
|
|
my $input_directory = $arguments{TEXTCAST_DIRECTORY} or croak 'Error: Expected textcast location!' ; |
391
|
0
|
|
0
|
|
|
|
my $display_status = $arguments{DISPLAY_STATUS} || 0 ; |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
local $SIG{INT} = sub |
394
|
|
|
|
|
|
|
{ |
395
|
0
|
|
|
0
|
|
|
print "\n" ; |
396
|
0
|
|
|
|
|
|
local $ERRNO = 1 ; |
397
|
0
|
|
|
|
|
|
croak "Caught interrupt signal!\n" ; |
398
|
0
|
|
|
|
|
|
} ; |
399
|
|
|
|
|
|
|
|
400
|
0
|
|
|
|
|
|
my $screenshot_information = load_index($input_directory) ; |
401
|
|
|
|
|
|
|
|
402
|
0
|
|
|
|
|
|
my ($max_rows, $max_columns) = (-1, -1) ; |
403
|
|
|
|
|
|
|
|
404
|
0
|
|
|
|
|
|
for my $screenshot_data (@{$screenshot_information}) |
|
0
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
{ |
406
|
|
|
|
|
|
|
#~ print "$screenshot_data->{terminal_rows}, $screenshot_data->{terminal_columns} \n" ; |
407
|
|
|
|
|
|
|
|
408
|
0
|
0
|
|
|
|
|
$max_rows = $screenshot_data->{terminal_rows} if $screenshot_data->{terminal_rows} > $max_rows ; |
409
|
0
|
0
|
|
|
|
|
$max_columns = $screenshot_data->{terminal_columns} if $screenshot_data->{terminal_columns} > $max_columns ; |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
0
|
|
|
|
|
|
my ($terminal_columns, $terminal_rows) = $get_terminal_size->() ; |
413
|
|
|
|
|
|
|
|
414
|
0
|
|
|
|
|
|
my ($status_row,$status_column) = (1, 1) ; |
415
|
|
|
|
|
|
|
|
416
|
0
|
0
|
0
|
|
|
|
if($max_rows + $display_status > $terminal_rows || $max_columns > $terminal_columns) |
417
|
|
|
|
|
|
|
{ |
418
|
0
|
|
|
|
|
|
Readonly my $ERRNO_TERMINAL_TOO_SMALL => 3 ; |
419
|
0
|
|
|
|
|
|
local $ERRNO = $ERRNO_TERMINAL_TOO_SMALL ; |
420
|
0
|
|
|
|
|
|
croak "Error: Terminal too small [$terminal_columns, $terminal_rows] need at least [$max_columns, $max_rows]!\n" ; |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
else |
423
|
|
|
|
|
|
|
{ |
424
|
0
|
|
|
|
|
|
$status_row = $max_rows + 1 ; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
#~ print DumpTree \@screenshot_information ; |
428
|
|
|
|
|
|
|
|
429
|
0
|
|
|
|
|
|
print $CLEAR, $HOME ; |
430
|
|
|
|
|
|
|
|
431
|
0
|
|
|
|
|
|
my ($total_play_time, $played_frames, $skipped_frames) |
432
|
|
|
|
|
|
|
= display_text_cast_data |
433
|
|
|
|
|
|
|
( |
434
|
|
|
|
|
|
|
$input_directory, |
435
|
|
|
|
|
|
|
$screenshot_information, |
436
|
|
|
|
|
|
|
{ |
437
|
|
|
|
|
|
|
DISPLAY => $display_status, |
438
|
|
|
|
|
|
|
ROW => $status_row, |
439
|
|
|
|
|
|
|
COLUMN => $status_column, |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
) ; |
442
|
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
|
print_play_information($total_play_time, $played_frames, $skipped_frames) ; |
444
|
|
|
|
|
|
|
|
445
|
0
|
|
|
|
|
|
return ; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
sub display_text_cast_data |
451
|
|
|
|
|
|
|
{ |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=head2 [p] display_text_cast_data($input_directory, \@screenshot_information, \%display_status ) |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
Plays a screencast. |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
I |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=over 2 |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=item * $input_directory - String - directory containing the textcast |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=item * \@screenshot_information - see L |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
=item * \%display_status - |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
=over 2 |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=item DISPLAY - Boolean - status is displayed during the replay if this is set |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=item ROW - row where the status is displayed |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=item COLUMNS - column where the status is displayed |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=back |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=back |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
I - A list containing |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=over 2 |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=item * $total_play_time |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=item * $played_frames |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=item * \@skipped_frames |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=back |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
I - None |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=cut |
494
|
|
|
|
|
|
|
|
495
|
0
|
|
|
0
|
1
|
|
my ($input_directory, $screenshot_information, $display_status,) = @_ ; |
496
|
|
|
|
|
|
|
|
497
|
0
|
|
|
|
|
|
my $total_frames = scalar(@{$screenshot_information}) ; |
|
0
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
|
499
|
0
|
|
|
|
|
|
my ($total_play_time, $played_frames, @skipped_frames) ; |
500
|
|
|
|
|
|
|
|
501
|
0
|
|
|
|
|
|
my $frame_display_time = 0 ; |
502
|
|
|
|
|
|
|
|
503
|
0
|
|
|
|
|
|
for my $file_information (@{$screenshot_information}) |
|
0
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
{ |
505
|
0
|
|
|
|
|
|
my $file = "$input_directory/$file_information->{file}" ; |
506
|
0
|
|
|
|
|
|
$total_play_time += $file_information->{delay} ; |
507
|
|
|
|
|
|
|
|
508
|
0
|
0
|
|
|
|
|
if(-e $file) |
509
|
|
|
|
|
|
|
{ |
510
|
0
|
|
|
|
|
|
$played_frames++ ; |
511
|
|
|
|
|
|
|
|
512
|
0
|
0
|
|
|
|
|
status |
513
|
|
|
|
|
|
|
( |
514
|
|
|
|
|
|
|
sprintf( "F: $played_frames/$total_frames [%0.2f]", $file_information->{delay}), |
515
|
|
|
|
|
|
|
$display_status->{ROW}, |
516
|
|
|
|
|
|
|
$display_status->{COLUMN}, |
517
|
|
|
|
|
|
|
) if $display_status->{DISPLAY} ; |
518
|
|
|
|
|
|
|
|
519
|
0
|
|
|
|
|
|
my $sleep_time = $file_information->{delay} - $frame_display_time ; |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
# split sleep time in smaller chunks if we want to handle the user input |
522
|
0
|
|
|
|
|
|
Readonly my $ONE_MILLION => 1_000_000 ; |
523
|
|
|
|
|
|
|
|
524
|
0
|
0
|
|
|
|
|
usleep $sleep_time * $ONE_MILLION if($sleep_time > 0) ; |
525
|
|
|
|
|
|
|
|
526
|
0
|
|
|
|
|
|
$frame_display_time = [gettimeofday] ; |
527
|
|
|
|
|
|
|
|
528
|
0
|
|
|
|
|
|
print #$SHOW_CURSOR, |
529
|
|
|
|
|
|
|
read_file($file), |
530
|
|
|
|
|
|
|
position_cursor($file_information->{cursor_y}, $file_information->{cursor_x}) ; |
531
|
|
|
|
|
|
|
|
532
|
0
|
|
|
|
|
|
$frame_display_time = tv_interval($frame_display_time , [gettimeofday]) ; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
else |
535
|
|
|
|
|
|
|
{ |
536
|
0
|
|
|
|
|
|
carp "Error: Can't find '$file'! Skipping.\n" ; |
537
|
0
|
|
|
|
|
|
push @skipped_frames, $file ; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
|
541
|
0
|
|
|
|
|
|
return ($total_play_time, $played_frames, \@skipped_frames) ; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
sub print_play_information |
547
|
|
|
|
|
|
|
{ |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=head2 [p] print_play_information($total_play_time, $played_frames, \@skipped_frames) |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
Displays information about the textcast replay. |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
print_play_information |
554
|
|
|
|
|
|
|
( |
555
|
|
|
|
|
|
|
$total_play_time, |
556
|
|
|
|
|
|
|
$total_frames, |
557
|
|
|
|
|
|
|
$played_frames, |
558
|
|
|
|
|
|
|
\@skipped_frames, |
559
|
|
|
|
|
|
|
) ; |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
I |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=over 2 |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=item * $total_play_time - Float - play time in seconds |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
=item * $played_frames - Integer - number of framed played, maybe less than $total_frames |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=item * \@skipped_frames - Integer - number of frames skipped because they couldn't be found |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
=back |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
I - Nothing |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
I - None |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
=cut |
578
|
|
|
|
|
|
|
|
579
|
0
|
|
|
0
|
1
|
|
my ($total_play_time, $played_frames, $skipped_frames) = @_ ; |
580
|
|
|
|
|
|
|
|
581
|
0
|
|
|
|
|
|
my $play_time = sprintf('%0.2f', $total_play_time) ; |
582
|
|
|
|
|
|
|
|
583
|
0
|
|
|
|
|
|
print "play_textcast: $played_frames frames played in $play_time seconds.\n" ; |
584
|
|
|
|
|
|
|
|
585
|
0
|
0
|
|
|
|
|
if(@{$skipped_frames}) |
|
0
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
{ |
587
|
0
|
|
|
|
|
|
print "Skipped:\n\t" . join("\n\t", @{$skipped_frames}) . "\n" ; |
|
0
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
|
590
|
0
|
|
|
|
|
|
return ; |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
sub status |
596
|
|
|
|
|
|
|
{ |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=head2 [p] status($status, $status_row, $status_column) |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
Displays a status on the status line. |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
I |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=over 2 |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=item * $status - String to be displayed on the terminal |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=item * $status_row - Integer - row position for the status |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=item * $status_column - Integer - column position for the status |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=back |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
I - Nothing |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
I - None |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=cut |
619
|
|
|
|
|
|
|
|
620
|
0
|
|
|
0
|
1
|
|
my ($status, $status_row, $status_column) = @_ ; |
621
|
|
|
|
|
|
|
|
622
|
0
|
|
|
|
|
|
print $SAVE_CURSOR_POSITION, |
623
|
|
|
|
|
|
|
position_cursor($status_row, $status_column), |
624
|
|
|
|
|
|
|
$CLEAR_LINE, |
625
|
|
|
|
|
|
|
$status, |
626
|
|
|
|
|
|
|
$RESTORE_CURSOR_POSITION ; |
627
|
|
|
|
|
|
|
|
628
|
0
|
|
|
|
|
|
return ; |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
sub position_cursor |
634
|
|
|
|
|
|
|
{ |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=head2 [p] position_cursor($row, $column) |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
Create an ANSI command to position the cursor on the terminal. |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
I |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=over 2 |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=item * $row - Integer - row position for the status |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=item * $column - Integer - column position for the status |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
=back |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
I - A string containing the ANSI command. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
I - None |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
See C. |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
=cut |
657
|
|
|
|
|
|
|
|
658
|
0
|
|
|
0
|
1
|
|
my ($row, $column) = @_ ; |
659
|
|
|
|
|
|
|
|
660
|
0
|
|
|
|
|
|
return "\e[${row};${column}H" ; |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
sub load_index |
666
|
|
|
|
|
|
|
{ |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
=head2 [p] load_index($input_directory) |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
Loads the screencast meta-data. |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
I |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
=over 2 |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
=item * $input_directory - The directory containing the textcast |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=back |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
I - The screencast meta-data, see the index file for format information. |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
I |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
=over 2 |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
=item * Index not found |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=item * Invalid data in index |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
=back |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
=cut |
693
|
|
|
|
|
|
|
|
694
|
0
|
|
|
0
|
1
|
|
my ($input_directory) = @_ ; |
695
|
|
|
|
|
|
|
|
696
|
0
|
|
|
|
|
|
my @screenshot_information ; |
697
|
|
|
|
|
|
|
|
698
|
0
|
0
|
|
|
|
|
if(-e "$input_directory/index") |
699
|
|
|
|
|
|
|
{ |
700
|
0
|
|
|
|
|
|
print "Parsing index ...\n" ; |
701
|
0
|
|
|
|
|
|
my @entries = read_file("$input_directory/index") ; |
702
|
|
|
|
|
|
|
|
703
|
0
|
|
|
|
|
|
my $line = 0 ; |
704
|
|
|
|
|
|
|
|
705
|
0
|
|
|
|
|
|
my $regex = '{file => 0, delay => 0.0, cursor_x => 1, cursor_y => 1, size => 1, terminal_rows => 1, terminal_columns => 1, },' ; |
706
|
0
|
|
|
|
|
|
$regex =~ s/^{/^{/sxm ; |
707
|
0
|
|
|
|
|
|
$regex =~ s/([^[:digit:]]+)$/$1\$/sxmg ; |
708
|
0
|
|
|
|
|
|
$regex =~ s/[[:digit:]]+/[[:digit:]]+/sxmg ; |
709
|
|
|
|
|
|
|
|
710
|
0
|
|
|
|
|
|
my @errors ; |
711
|
|
|
|
|
|
|
|
712
|
0
|
|
|
|
|
|
for my $entry (@entries) |
713
|
|
|
|
|
|
|
{ |
714
|
0
|
0
|
|
|
|
|
unless($entry =~ $regex) |
715
|
|
|
|
|
|
|
{ |
716
|
0
|
|
|
|
|
|
push @errors, "\tInvalid index entry at line $line!\n" ; |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
0
|
|
|
|
|
|
$line++ ; |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
|
722
|
0
|
0
|
|
|
|
|
if(@errors) |
723
|
|
|
|
|
|
|
{ |
724
|
0
|
|
|
|
|
|
local $ERRNO = 2 ; |
725
|
0
|
|
|
|
|
|
croak "Error: Invalid index!\n@errors" ; |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
|
728
|
0
|
0
|
|
|
|
|
@screenshot_information = eval "@entries" ## no critic (BuiltinFunctions::ProhibitStringyEval) |
729
|
|
|
|
|
|
|
or croak "Error: Couldn't parse index file! $@ $!\n" ; |
730
|
|
|
|
|
|
|
} |
731
|
|
|
|
|
|
|
else |
732
|
|
|
|
|
|
|
{ |
733
|
0
|
|
|
|
|
|
local $ERRNO = 2 ; |
734
|
0
|
|
|
|
|
|
croak "Error: No index found! $!\n" ; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
|
737
|
0
|
|
|
|
|
|
return \@screenshot_information ; |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
741
|
|
|
|
|
|
|
# VT102 |
742
|
|
|
|
|
|
|
# Everything below is based on the Term::VT102 example |
743
|
|
|
|
|
|
|
# Logs all terminal output to STDERR if STDERR is redirected to a file. |
744
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
sub create_vt102_sub_process |
747
|
|
|
|
|
|
|
{ |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
=head2 [p] create_vt102_sub_process($shell_command, $columns, $rows) |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
I |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=over 2 |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
=item * $shell_command, $columns, $rows - |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=back |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
I - a vt_process handle |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
I |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
=cut |
765
|
|
|
|
|
|
|
|
766
|
0
|
|
|
0
|
1
|
|
my ($shell_command, $columns, $rows) = @_ ; |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
# Create a pty for the command to run. |
769
|
0
|
|
|
|
|
|
my $pty = new IO::Pty; |
770
|
0
|
|
|
|
|
|
$pty->autoflush(); |
771
|
|
|
|
|
|
|
|
772
|
0
|
0
|
|
|
|
|
croak 'Error: Could not assign a pty' if (not defined $pty->ttyname()) ; |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
# Create the terminal object. |
775
|
0
|
|
|
|
|
|
my ($vt, $terminal_change_buffer) = create_vt102_terminal($pty, $columns, $rows) ; |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
# Run the command in a child process. |
778
|
0
|
|
|
|
|
|
my $pid = create_child_process($shell_command, $pty, $vt) ; |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
# IO::Handle for standard input - unbuffered. |
781
|
0
|
|
|
|
|
|
my $iot = new IO::Handle; |
782
|
0
|
|
|
|
|
|
$iot->fdopen (fileno(STDIN), 'r'); |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
return |
785
|
|
|
|
|
|
|
{ |
786
|
0
|
|
|
|
|
|
PTY => $pty, |
787
|
|
|
|
|
|
|
VT => $vt, |
788
|
|
|
|
|
|
|
TERMINAL_CHANGE_BUFFER => $terminal_change_buffer, |
789
|
|
|
|
|
|
|
IOT => $iot, |
790
|
|
|
|
|
|
|
PREVXY => $EMPTY_STRING, |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
PID => $pid, |
793
|
|
|
|
|
|
|
DIED => 0, |
794
|
|
|
|
|
|
|
} ; |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
sub close_vt102_sub_process |
800
|
|
|
|
|
|
|
{ |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
=head2 [p] close_vt102_sub_process( $vt_process) |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
I |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
=over 2 |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
=item * $vt_process - vt_process handle created by L |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
=back |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
I - Nothing |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
I - None |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
=cut |
817
|
|
|
|
|
|
|
|
818
|
0
|
|
|
0
|
1
|
|
my ($vt_process) = @_ ; |
819
|
0
|
|
|
|
|
|
$vt_process->{PTY}->close; |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
# Reset the terminal parameters. |
822
|
0
|
|
|
|
|
|
system 'stty sane'; |
823
|
|
|
|
|
|
|
|
824
|
0
|
|
|
|
|
|
return ; |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub create_vt102_terminal |
830
|
|
|
|
|
|
|
{ |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
=head2 [p] create_vt102_terminal($pty, $columns, $rows) |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
I |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=over 2 |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
=item $pty, $columns, $rows - |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
=back |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
I - $vt, $terminal_change_buffer |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
I - None |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
=cut |
847
|
|
|
|
|
|
|
|
848
|
0
|
|
|
0
|
1
|
|
my ($pty, $columns, $rows) = @_ ; |
849
|
|
|
|
|
|
|
|
850
|
0
|
|
|
|
|
|
my $terminal_change_buffer = {}; |
851
|
0
|
|
|
|
|
|
my $vt = Term::VT102->new (cols => $columns, rows => $rows,); |
852
|
|
|
|
|
|
|
|
853
|
0
|
|
|
|
|
|
$vt->option_set ('LFTOCRLF', 1); # Convert linefeeds to linefeed + carriage return. |
854
|
0
|
|
|
|
|
|
$vt->option_set ('LINEWRAP', 1); # Make sure line wrapping is switched on. |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
# Set up the callback for OUTPUT; this callback function simply sends |
857
|
|
|
|
|
|
|
# whatever the Term::VT102 module wants to send back to the terminal and |
858
|
|
|
|
|
|
|
# sends it to the child process - see its definition below. |
859
|
0
|
|
|
|
|
|
$vt->callback_set ('OUTPUT', \&vt_output, $pty); |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# Set up a callback for row changes, so we can process updates and display |
862
|
|
|
|
|
|
|
# them without having to redraw the whole screen every time. We catch CLEAR, |
863
|
|
|
|
|
|
|
# SCROLL_UP, and SCROLL_DOWN with another function that triggers a |
864
|
|
|
|
|
|
|
# whole-screen repaint. You could process SCROLL_UP and SCROLL_DOWN more |
865
|
|
|
|
|
|
|
# elegantly, but this is just an example. |
866
|
0
|
|
|
|
|
|
$vt->callback_set ('ROWCHANGE', \&vt_rowchange, $terminal_change_buffer ); |
867
|
0
|
|
|
|
|
|
$vt->callback_set ('CLEAR', \&vt_changeall, $terminal_change_buffer ); |
868
|
0
|
|
|
|
|
|
$vt->callback_set ('SCROLL_UP', \&vt_changeall, $terminal_change_buffer ); |
869
|
0
|
|
|
|
|
|
$vt->callback_set ('SCROLL_DOWN', \&vt_changeall, $terminal_change_buffer ); |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
# Set stdin's terminal to raw mode so we can pass all keypresses straight |
872
|
|
|
|
|
|
|
# through immediately. |
873
|
0
|
|
|
|
|
|
system 'stty raw -echo'; |
874
|
|
|
|
|
|
|
|
875
|
0
|
|
|
|
|
|
return ($vt, $terminal_change_buffer ) ; |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
sub vt_output |
881
|
|
|
|
|
|
|
{ |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
=head2 [p] vt_output($vtobject, $type, $arg1, $arg2, $private) |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
Callback for OUTPUT events - for Term::VT102. |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
I |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=over 2 |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
=item $vtobject, $type, $arg1, $arg2, $private - |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
=back |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
I - Nothing |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
I - Nothing |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
See L. |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
=cut |
902
|
|
|
|
|
|
|
|
903
|
0
|
|
|
0
|
1
|
|
my ($vtobject, $type, $arg1, $arg2, $private) = @_; |
904
|
|
|
|
|
|
|
|
905
|
0
|
0
|
|
|
|
|
if ($type eq 'OUTPUT') |
906
|
|
|
|
|
|
|
{ |
907
|
0
|
|
|
|
|
|
$private->syswrite ($arg1, length $arg1); |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
0
|
|
|
|
|
|
return ; |
911
|
|
|
|
|
|
|
} |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
sub vt_rowchange |
916
|
|
|
|
|
|
|
{ |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
=head2 [p] vt_rowchange($vtobject, $type, $arg1, $arg2, $private) |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
Callback for ROWCHANGE events. This just sets a time value for the changed |
921
|
|
|
|
|
|
|
row using the private data as a hash reference - the time represents the |
922
|
|
|
|
|
|
|
earliest that row was changed since the last screen update. |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
I |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
=over 2 |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
=item $vtobject, $type, $arg1, $arg2, $private - |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
=back |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
I - Nothing |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
I - Nothing |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
See L. |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=cut |
939
|
|
|
|
|
|
|
|
940
|
0
|
|
|
0
|
1
|
|
my ($vtobject, $type, $arg1, $arg2, $private) = @_; |
941
|
0
|
0
|
|
|
|
|
$private->{$arg1} = time if (not exists $private->{$arg1}); |
942
|
|
|
|
|
|
|
|
943
|
0
|
|
|
|
|
|
return ; |
944
|
|
|
|
|
|
|
} |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
sub vt_changeall |
949
|
|
|
|
|
|
|
{ |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
=head2 [p] vt_changeall($vtobject, $type, $arg1, $arg2, $private) |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
Callback to trigger a full-screen repaint. |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
I |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=over 2 |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
=item $vtobject, $type, $arg1, $arg2, $private - |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
=back |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
I - Nothing |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
I - None |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
See L. |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
=cut |
970
|
|
|
|
|
|
|
|
971
|
0
|
|
|
0
|
1
|
|
my ($vtobject, $type, $arg1, $arg2, $private) = @_; |
972
|
0
|
|
|
|
|
|
for my $row (1 .. $vtobject->rows) |
973
|
|
|
|
|
|
|
{ |
974
|
0
|
|
|
|
|
|
$private->{$row} = 0; |
975
|
|
|
|
|
|
|
} |
976
|
|
|
|
|
|
|
|
977
|
0
|
|
|
|
|
|
return ; |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
sub create_child_process |
983
|
|
|
|
|
|
|
{ |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
=head2 [p] create_child_process($shell_command, $pty, $vt) |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
Creqtes a child process to run a command in. |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
I |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=over 2 |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
=item $shell_command, $pty, $vt - |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
=back |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
I - Nothing |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
I - Can not fork to run sub process |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
See C. |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
=cut |
1004
|
|
|
|
|
|
|
|
1005
|
0
|
|
|
0
|
1
|
|
my ($shell_command, $pty, $vt) = @_ ; |
1006
|
0
|
|
|
|
|
|
my $pid = fork; |
1007
|
|
|
|
|
|
|
|
1008
|
0
|
0
|
|
|
|
|
croak "Error: Can not fork to run sub process, $!" if (not defined $pid) ; |
1009
|
|
|
|
|
|
|
|
1010
|
0
|
0
|
|
|
|
|
if ($pid == 0) |
1011
|
|
|
|
|
|
|
{ |
1012
|
|
|
|
|
|
|
# never comes back |
1013
|
0
|
|
|
|
|
|
run_child_process($shell_command, $pty, $vt) ; |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
|
1016
|
0
|
|
|
|
|
|
return $pid ; |
1017
|
|
|
|
|
|
|
} |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
sub run_child_process |
1022
|
|
|
|
|
|
|
{ |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=head2 [p] run_child_process($command, $pty, $vt) |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
I |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=over 2 |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=item $command, $pty, $vt - |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
=back |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
I - Nothing |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
I - Error redirecting streams |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
=cut |
1039
|
|
|
|
|
|
|
|
1040
|
0
|
|
|
0
|
1
|
|
my ($command, $pty, $vt) = @_ ; |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
# Child process - set up stdin/out/err and run the command. |
1043
|
|
|
|
|
|
|
# Become process group leader. |
1044
|
0
|
0
|
|
|
|
|
if (not POSIX::setsid ()) |
1045
|
|
|
|
|
|
|
{ |
1046
|
0
|
|
|
|
|
|
carp "Couldn't perform setsid: $!"; |
1047
|
|
|
|
|
|
|
} |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
# Get details of the slave side of the pty. |
1050
|
0
|
|
|
|
|
|
my $tty = $pty->slave (); |
1051
|
0
|
|
|
|
|
|
my $tty_name = $tty->ttyname(); |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
# Linux specific - commented out, we'll just use stty below. |
1054
|
|
|
|
|
|
|
# |
1055
|
|
|
|
|
|
|
# # Set the window size - this may only work on Linux. |
1056
|
|
|
|
|
|
|
# # |
1057
|
|
|
|
|
|
|
# my $winsize = pack ('SSSS', $vt->rows, $vt->cols, 0, 0); |
1058
|
|
|
|
|
|
|
# ioctl ($tty, &IO::Tty::Constant::TIOCSWINSZ, $winsize); |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
# File descriptor shuffling - close the pty master, then close |
1061
|
|
|
|
|
|
|
# stdin/out/err and reopen them to point to the pty slave. |
1062
|
0
|
|
|
|
|
|
close ($pty); |
1063
|
|
|
|
|
|
|
|
1064
|
0
|
|
|
|
|
|
close (STDIN); |
1065
|
0
|
0
|
|
|
|
|
open (STDIN, '<&' . $tty->fileno ()) || croak 'Error: Couldn\'t reopen ' . $tty_name . " for reading: $!"; |
1066
|
|
|
|
|
|
|
|
1067
|
0
|
|
|
|
|
|
close (STDOUT); |
1068
|
0
|
0
|
|
|
|
|
open (STDOUT, '>&' . $tty->fileno()) || croak 'Error: Couldn\'t reopen ' . $tty_name . " for writing: $!"; |
1069
|
|
|
|
|
|
|
|
1070
|
0
|
|
|
|
|
|
close (STDERR); |
1071
|
0
|
0
|
|
|
|
|
open (STDERR, '>&' . $tty->fileno()) || croak "Error: Couldn't redirect STDERR: $!"; |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
# Set sane terminal parameters. |
1074
|
0
|
|
|
|
|
|
system 'stty sane'; |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
# Set the terminal size with stty. |
1077
|
0
|
|
|
|
|
|
system 'stty rows ' . $vt->rows; |
1078
|
0
|
|
|
|
|
|
system 'stty cols ' . $vt->cols; |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
# Finally, run the command, and die if we can't. |
1081
|
0
|
0
|
|
|
|
|
exec $command or croak "Error: Cannot exec '$command': $!"; |
1082
|
|
|
|
|
|
|
} |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
sub check_sub_process_output |
1087
|
|
|
|
|
|
|
{ |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
=head2 [p] check_sub_process_output( $vt_process) |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
Check the sub process output. |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
I |
1094
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
=over 2 |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
=item * $vt_process - |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
=back |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
I - $eof, $screen_data, $cursor_x, $cursor_y |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
I - None |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=cut |
1106
|
|
|
|
|
|
|
|
1107
|
0
|
|
|
0
|
1
|
|
my ($vt_process) = @_; |
1108
|
0
|
|
|
|
|
|
my $vt = $vt_process->{VT} ; |
1109
|
|
|
|
|
|
|
|
1110
|
0
|
|
|
|
|
|
my ($eof, $screen_data) ; |
1111
|
|
|
|
|
|
|
|
1112
|
0
|
|
|
|
|
|
my $rin = $EMPTY_STRING ; |
1113
|
0
|
|
|
|
|
|
vec ($rin, $vt_process->{PTY}->fileno, 1) = 1; |
1114
|
0
|
|
|
|
|
|
vec ($rin, $vt_process->{IOT}->fileno, 1) = 1; |
1115
|
|
|
|
|
|
|
|
1116
|
0
|
|
|
|
|
|
my ($win, $ein) = ($EMPTY_STRING, $EMPTY_STRING) ; |
1117
|
0
|
|
|
|
|
|
my($rout, $wout, $eout) ; |
1118
|
0
|
|
|
|
|
|
select ($rout=$rin, $wout=$win, $eout=$ein, 1); |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
# Read from the command if there is anything coming in, and |
1121
|
|
|
|
|
|
|
# pass any data on to the Term::VT102 object. |
1122
|
0
|
|
|
|
|
|
my $cmdbuf = $EMPTY_STRING ; |
1123
|
|
|
|
|
|
|
|
1124
|
0
|
|
|
|
|
|
Readonly my $BUFFER_READ_SIZE => 1024 ; |
1125
|
|
|
|
|
|
|
|
1126
|
0
|
0
|
|
|
|
|
if (vec($rout, $vt_process->{PTY}->fileno, 1)) |
1127
|
|
|
|
|
|
|
{ |
1128
|
0
|
|
|
|
|
|
my $bytes_read = $vt_process->{PTY}->sysread ($cmdbuf, $BUFFER_READ_SIZE); |
1129
|
0
|
0
|
0
|
|
|
|
$eof = 1 if ((defined $bytes_read) && ($bytes_read == 0)); |
1130
|
|
|
|
|
|
|
|
1131
|
0
|
0
|
0
|
|
|
|
if ((defined $bytes_read) && ($bytes_read > 0)) |
1132
|
|
|
|
|
|
|
{ |
1133
|
0
|
|
|
|
|
|
$vt->process ($cmdbuf); |
1134
|
0
|
0
|
|
|
|
|
syswrite STDERR, $cmdbuf if (! -t STDERR); |
1135
|
|
|
|
|
|
|
} |
1136
|
|
|
|
|
|
|
} |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
# End processing if we've gone 1 round after command died with no output. |
1139
|
0
|
0
|
0
|
|
|
|
$eof = 1 if ($vt_process->{DIED} && $cmdbuf eq $EMPTY_STRING); |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
# Do your stuff here - use $vt->row_plaintext() to see what's on various |
1142
|
|
|
|
|
|
|
# rows of the screen, for instance, or before this main loop you could set |
1143
|
|
|
|
|
|
|
# up a ROWCHANGE callback which checks the changed row, or whatever. |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
# In this example, we just pass standard input to the SSH command, and we |
1146
|
|
|
|
|
|
|
# take the data coming back from SSH and pass it to the Term::VT102 object, |
1147
|
|
|
|
|
|
|
# and then we repeatedly dump the Term::VT102 screen. |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
# Read key presses from standard input and pass them to the command |
1150
|
|
|
|
|
|
|
# running in the child process. |
1151
|
0
|
0
|
|
|
|
|
if (vec ($rout, $vt_process->{IOT}->fileno, 1)) |
1152
|
|
|
|
|
|
|
{ |
1153
|
0
|
|
|
|
|
|
my $stdinbuf = $EMPTY_STRING ; |
1154
|
0
|
|
|
|
|
|
my $bytes_read = $vt_process->{IOT}->sysread ($stdinbuf, $BUFFER_READ_SIZE ); |
1155
|
0
|
0
|
0
|
|
|
|
$eof = 1 if ((defined $bytes_read) && ($bytes_read == 0)); |
1156
|
0
|
0
|
0
|
|
|
|
$vt_process->{PTY}->syswrite ($stdinbuf, $bytes_read) if ((defined $bytes_read) && ($bytes_read > 0)); |
1157
|
|
|
|
|
|
|
} |
1158
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
# Dump what Term::VT102 thinks is on the screen. We only output rows |
1160
|
|
|
|
|
|
|
# we know have changed, to avoid generating too much output. |
1161
|
0
|
|
|
|
|
|
my $didout = 0; |
1162
|
0
|
|
|
|
|
|
foreach my $row (sort keys %{ $vt_process->{TERMINAL_CHANGE_BUFFER} }) |
|
0
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
{ |
1164
|
0
|
|
|
|
|
|
printf "\e[%dH%s\r", $row, $vt->row_sgrtext ($row); |
1165
|
0
|
|
|
|
|
|
$screen_data .= sprintf "\e[%dH%s\r", $row, $vt->row_sgrtext ($row); |
1166
|
|
|
|
|
|
|
|
1167
|
0
|
|
|
|
|
|
delete $vt_process->{TERMINAL_CHANGE_BUFFER}{$row}; |
1168
|
0
|
|
|
|
|
|
$didout ++; |
1169
|
|
|
|
|
|
|
} |
1170
|
|
|
|
|
|
|
|
1171
|
0
|
0
|
0
|
|
|
|
if (($didout > 0) || ($vt_process->{PREVXY} ne $EMPTY_STRING . $vt->x . q{,} . $vt->y)) |
1172
|
|
|
|
|
|
|
{ |
1173
|
0
|
0
|
|
|
|
|
printf "\e[%d;%dH", $vt->y, ($vt->x > $vt->cols ? $vt->cols : $vt->x); |
1174
|
|
|
|
|
|
|
|
1175
|
0
|
0
|
|
|
|
|
$screen_data .= sprintf "\e[%d;%dH", $vt->y, ($vt->x > $vt->cols ? $vt->cols : $vt->x); |
1176
|
|
|
|
|
|
|
#todo: shouldn't prevxy be updated here? |
1177
|
|
|
|
|
|
|
} |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
# Make sure the child process has not died. |
1180
|
0
|
0
|
|
|
|
|
$vt_process->{DIED} = 1 if (waitpid ($vt_process->{PID}, WNOHANG) > 0); |
1181
|
|
|
|
|
|
|
|
1182
|
0
|
|
|
|
|
|
return($eof, $screen_data, $vt->x(), $vt->y()) ; |
1183
|
|
|
|
|
|
|
} |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
#--------------------------------------------------------------------------------------------------------- |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
1 ; |
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
None so far. |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
=head1 AUTHOR |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
Nadim ibn hamouda el Khemir |
1196
|
|
|
|
|
|
|
CPAN ID: NH |
1197
|
|
|
|
|
|
|
mailto: nadim@cpan.org |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
This program is free software; you can redistribute |
1202
|
|
|
|
|
|
|
it and/or modify it under the same terms as Perl itself. |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
=head1 SUPPORT |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
perldoc App::Textcast |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
You can also look for information at: |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
=over 4 |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
L |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
Please report any bugs or feature requests to L . |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
We will be notified, and then you'll automatically be notified of progress on |
1223
|
|
|
|
|
|
|
your bug as we make changes. |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
=item * Search CPAN |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
L |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
=back |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
=head1 SEE ALSO |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
screen (1), script(1), aewan, vte(1), evilvte(1). |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
=cut |