Avoid -W warnings in Tgetent and Tputs
[p5sagit/p5-mst-13.2.git] / lib / Term / Cap.pm
CommitLineData
a0d0e21e 1package Term::Cap;
cb1a09d0 2use Carp;
85e6fe83 3
e14c93b3 4our $VERSION = '1.01';
b75c8c73 5
e14c93b3 6# Version undef: Thu Dec 14 20:02:42 CST 1995 by sanders@bsdi.com
7# Version 1.00: Thu Nov 30 23:34:29 EST 2000 by schwern@pobox.com
8# [PATCH] $VERSION crusade, strict, tests, etc... all over lib/
9# Version 1.01: Wed May 23 00:00:00 CST 2001 by d-lewart@uiuc.edu
10# Avoid warnings in Tgetent and Tputs
a687059c 11
cb1a09d0 12# TODO:
13# support Berkeley DB termcaps
14# should probably be a .xs module
15# force $FH into callers package?
16# keep $FH in object at Tgetent time?
17
18=head1 NAME
19
20Term::Cap - Perl termcap interface
21
22=head1 SYNOPSIS
23
24 require Term::Cap;
25 $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
26 $terminal->Trequire(qw/ce ku kd/);
27 $terminal->Tgoto('cm', $col, $row, $FH);
28 $terminal->Tputs('dl', $count, $FH);
29 $terminal->Tpad($string, $count, $FH);
30
31=head1 DESCRIPTION
32
33These are low-level functions to extract and use capabilities from
34a terminal capability (termcap) database.
35
36The B<Tgetent> function extracts the entry of the specified terminal
37type I<TERM> (defaults to the environment variable I<TERM>) from the
38database.
39
40It will look in the environment for a I<TERMCAP> variable. If
41found, and the value does not begin with a slash, and the terminal
42type name is the same as the environment string I<TERM>, the
43I<TERMCAP> string is used instead of reading a termcap file. If
44it does begin with a slash, the string is used as a path name of
45the termcap file to search. If I<TERMCAP> does not begin with a
46slash and name is different from I<TERM>, B<Tgetent> searches the
47files F<$HOME/.termcap>, F</etc/termcap>, and F</usr/share/misc/termcap>,
48in that order, unless the environment variable I<TERMPATH> exists,
49in which case it specifies a list of file pathnames (separated by
50spaces or colons) to be searched B<instead>. Whenever multiple
51files are searched and a tc field occurs in the requested entry,
52the entry it names must be found in the same file or one of the
53succeeding files. If there is a C<:tc=...:> in the I<TERMCAP>
54environment variable string it will continue the search in the
55files as above.
56
57I<OSPEED> is the terminal output bit rate (often mistakenly called
58the baud rate). I<OSPEED> can be specified as either a POSIX
59termios/SYSV termio speeds (where 9600 equals 9600) or an old
60BSD-style speeds (where 13 equals 9600).
61
62B<Tgetent> returns a blessed object reference which the user can
63then use to send the control strings to the terminal using B<Tputs>
64and B<Tgoto>. It calls C<croak> on failure.
65
66B<Tgoto> decodes a cursor addressing string with the given parameters.
67
68The output strings for B<Tputs> are cached for counts of 1 for performance.
69B<Tgoto> and B<Tpad> do not cache. C<$self-E<gt>{_xx}> is the raw termcap
70data and C<$self-E<gt>{xx}> is the cached version.
71
72 print $terminal->Tpad($self->{_xx}, 1);
73
74B<Tgoto>, B<Tputs>, and B<Tpad> return the string and will also
75output the string to $FH if specified.
76
77The extracted termcap entry is available in the object
78as C<$self-E<gt>{TERMCAP}>.
79
80=head1 EXAMPLES
81
82 # Get terminal output speed
83 require POSIX;
84 my $termios = new POSIX::Termios;
85 $termios->getattr;
86 my $ospeed = $termios->getospeed;
87
88 # Old-style ioctl code to get ospeed:
89 # require 'ioctl.pl';
90 # ioctl(TTY,$TIOCGETP,$sgtty);
91 # ($ispeed,$ospeed) = unpack('cc',$sgtty);
92
93 # allocate and initialize a terminal structure
94 $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
95
96 # require certain capabilities to be available
97 $terminal->Trequire(qw/ce ku kd/);
98
99 # Output Routines, if $FH is undefined these just return the string
100
101 # Tgoto does the % expansion stuff with the given args
102 $terminal->Tgoto('cm', $col, $row, $FH);
103
104 # Tputs doesn't do any % expansion.
105 $terminal->Tputs('dl', $count = 1, $FH);
106
107=cut
108
109# Returns a list of termcap files to check.
110sub termcap_path { ## private
111 my @termcap_path;
112 # $TERMCAP, if it's a filespec
7a2e2cd6 113 push(@termcap_path, $ENV{TERMCAP})
114 if ((exists $ENV{TERMCAP}) &&
39e571d4 115 (($^O eq 'os2' || $^O eq 'MSWin32' || $^O eq 'dos')
fe6f1558 116 ? $ENV{TERMCAP} =~ /^[a-z]:[\\\/]/is
117 : $ENV{TERMCAP} =~ /^\//s));
c07a80fd 118 if ((exists $ENV{TERMPATH}) && ($ENV{TERMPATH})) {
cb1a09d0 119 # Add the users $TERMPATH
120 push(@termcap_path, split(/(:|\s+)/, $ENV{TERMPATH}))
121 }
122 else {
123 # Defaults
124 push(@termcap_path,
125 $ENV{'HOME'} . '/.termcap',
126 '/etc/termcap',
127 '/usr/share/misc/termcap',
128 );
a687059c 129 }
cb1a09d0 130 # return the list of those termcaps that exist
748a9306 131 grep(-f, @termcap_path);
132}
133
cb1a09d0 134sub Tgetent { ## public -- static method
135 my $class = shift;
136 my $self = bless shift, $class;
137 my($term,$cap,$search,$field,$max,$tmp_term,$TERMCAP);
138 local($termpat,$state,$first,$entry); # used inside eval
139 local $_;
140
141 # Compute PADDING factor from OSPEED (to be used by Tpad)
142 if (! $self->{OSPEED}) {
143 carp "OSPEED was not set, defaulting to 9600";
144 $self->{OSPEED} = 9600;
145 }
146 if ($self->{OSPEED} < 16) {
147 # delays for old style speeds
148 my @pad = (0,200,133.3,90.9,74.3,66.7,50,33.3,16.7,8.3,5.5,4.1,2,1,.5,.2);
149 $self->{PADDING} = $pad[$self->{OSPEED}];
150 }
151 else {
152 $self->{PADDING} = 10000 / $self->{OSPEED};
153 }
154
155 $self->{TERM} = ($self->{TERM} || $ENV{TERM} || croak "TERM not set");
156 $term = $self->{TERM}; # $term is the term type we are looking for
157
158 # $tmp_term is always the next term (possibly :tc=...:) we are looking for
159 $tmp_term = $self->{TERM};
160 # protect any pattern metacharacters in $tmp_term
161 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
162
c07a80fd 163 my $foo = (exists $ENV{TERMCAP} ? $ENV{TERMCAP} : '');
cb1a09d0 164
165 # $entry is the extracted termcap entry
fe6f1558 166 if (($foo !~ m:^/:s) && ($foo =~ m/(^|\|)${termpat}[:|]/s)) {
cb1a09d0 167 $entry = $foo;
168 }
169
170 my @termcap_path = termcap_path;
171 croak "Can't find a valid termcap file" unless @termcap_path || $entry;
172
173 $state = 1; # 0 == finished
174 # 1 == next file
175 # 2 == search again
176
177 $first = 0; # first entry (keeps term name)
178
179 $max = 32; # max :tc=...:'s
180
181 if ($entry) {
182 # ok, we're starting with $TERMCAP
183 $first++; # we're the first entry
184 # do we need to continue?
185 if ($entry =~ s/:tc=([^:]+):/:/) {
186 $tmp_term = $1;
187 # protect any pattern metacharacters in $tmp_term
188 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
189 }
190 else {
191 $state = 0; # we're already finished
192 }
193 }
194
195 # This is eval'ed inside the while loop for each file
196 $search = q{
54310121 197 while (<TERMCAP>) {
cb1a09d0 198 next if /^\\t/ || /^#/;
199 if ($_ =~ m/(^|\\|)${termpat}[:|]/o) {
200 chomp;
201 s/^[^:]*:// if $first++;
202 $state = 0;
54310121 203 while ($_ =~ s/\\\\$//) {
204 defined(my $x = <TERMCAP>) or last;
205 $_ .= $x; chomp;
206 }
cb1a09d0 207 last;
748a9306 208 }
cb1a09d0 209 }
55497cff 210 defined $entry or $entry = '';
e14c93b3 211 $entry .= $_ if $_;
cb1a09d0 212 };
748a9306 213
cb1a09d0 214 while ($state != 0) {
215 if ($state == 1) {
216 # get the next TERMCAP
217 $TERMCAP = shift @termcap_path
218 || croak "failed termcap lookup on $tmp_term";
219 }
220 else {
221 # do the same file again
222 # prevent endless recursion
223 $max-- || croak "failed termcap loop at $tmp_term";
224 $state = 1; # ok, maybe do a new file next time
225 }
226
227 open(TERMCAP,"< $TERMCAP\0") || croak "open $TERMCAP: $!";
228 eval $search;
229 die $@ if $@;
230 close TERMCAP;
231
232 # If :tc=...: found then search this file again
233 $entry =~ s/:tc=([^:]+):/:/ && ($tmp_term = $1, $state = 2);
234 # protect any pattern metacharacters in $tmp_term
235 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
a687059c 236 }
cb1a09d0 237
238 croak "Can't find $term" if $entry eq '';
239 $entry =~ s/:+\s*:+/:/g; # cleanup $entry
240 $entry =~ s/:+/:/g; # cleanup $entry
241 $self->{TERMCAP} = $entry; # save it
242 # print STDERR "DEBUG: $entry = ", $entry, "\n";
a687059c 243
748a9306 244 # Precompile $entry into the object
cb1a09d0 245 $entry =~ s/^[^:]*://;
748a9306 246 foreach $field (split(/:[\s:\\]*/,$entry)) {
cb1a09d0 247 if ($field =~ /^(\w\w)$/) {
248 $self->{'_' . $field} = 1 unless defined $self->{'_' . $1};
249 # print STDERR "DEBUG: flag $1\n";
748a9306 250 }
251 elsif ($field =~ /^(\w\w)\@/) {
cb1a09d0 252 $self->{'_' . $1} = "";
253 # print STDERR "DEBUG: unset $1\n";
a687059c 254 }
255 elsif ($field =~ /^(\w\w)#(.*)/) {
cb1a09d0 256 $self->{'_' . $1} = $2 unless defined $self->{'_' . $1};
257 # print STDERR "DEBUG: numeric $1 = $2\n";
a687059c 258 }
259 elsif ($field =~ /^(\w\w)=(.*)/) {
cb1a09d0 260 # print STDERR "DEBUG: string $1 = $2\n";
261 next if defined $self->{'_' . ($cap = $1)};
a687059c 262 $_ = $2;
263 s/\\E/\033/g;
ecfc5424 264 s/\\(\d\d\d)/pack('c',oct($1) & 0177)/eg;
a687059c 265 s/\\n/\n/g;
266 s/\\r/\r/g;
267 s/\\t/\t/g;
268 s/\\b/\b/g;
269 s/\\f/\f/g;
270 s/\\\^/\377/g;
271 s/\^\?/\177/g;
63f2c1e1 272 s/\^(.)/pack('c',ord($1) & 31)/eg;
a687059c 273 s/\\(.)/$1/g;
274 s/\377/^/g;
cb1a09d0 275 $self->{'_' . $cap} = $_;
a687059c 276 }
cb1a09d0 277 # else { carp "junk in $term ignored: $field"; }
a687059c 278 }
cb1a09d0 279 $self->{'_pc'} = "\0" unless defined $self->{'_pc'};
280 $self->{'_bc'} = "\b" unless defined $self->{'_bc'};
281 $self;
a687059c 282}
283
cb1a09d0 284# $terminal->Tpad($string, $cnt, $FH);
285sub Tpad { ## public
286 my $self = shift;
287 my($string, $cnt, $FH) = @_;
288 my($decr, $ms);
a687059c 289
a687059c 290 if ($string =~ /(^[\d.]+)(\*?)(.*)$/) {
291 $ms = $1;
748a9306 292 $ms *= $cnt if $2;
a687059c 293 $string = $3;
cb1a09d0 294 $decr = $self->{PADDING};
a687059c 295 if ($decr > .1) {
296 $ms += $decr / 2;
cb1a09d0 297 $string .= $self->{'_pc'} x ($ms / $decr);
a687059c 298 }
299 }
300 print $FH $string if $FH;
301 $string;
302}
303
cb1a09d0 304# $terminal->Tputs($cap, $cnt, $FH);
305sub Tputs { ## public
306 my $self = shift;
307 my($cap, $cnt, $FH) = @_;
308 my $string;
748a9306 309
310 if ($cnt > 1) {
cb1a09d0 311 $string = Tpad($self, $self->{'_' . $cap}, $cnt);
748a9306 312 } else {
cb1a09d0 313 # cache result because Tpad can be slow
e14c93b3 314 unless (exists $self->{$cap}) {
315 $self->{$cap} = exists $self->{"_$cap"} ?
316 Tpad($self, $self->{"_$cap"}, 1) : undef;
317 }
318 $string = $self->{$cap};
748a9306 319 }
320 print $FH $string if $FH;
321 $string;
322}
323
324# %% output `%'
325# %d output value as in printf %d
326# %2 output value as in printf %2d
327# %3 output value as in printf %3d
328# %. output value as in printf %c
329# %+x add x to value, then do %.
330#
331# %>xy if value > x then add y, no output
332# %r reverse order of two parameters, no output
333# %i increment by one, no output
334# %B BCD (16*(value/10)) + (value%10), no output
335#
336# %n exclusive-or all parameters with 0140 (Datamedia 2500)
337# %D Reverse coding (value - 2*(value%16)), no output (Delta Data)
338#
cb1a09d0 339# $terminal->Tgoto($cap, $col, $row, $FH);
340sub Tgoto { ## public
341 my $self = shift;
342 my($cap, $code, $tmp, $FH) = @_;
343 my $string = $self->{'_' . $cap};
344 my $result = '';
345 my $after = '';
346 my $online = 0;
347 my @tmp = ($tmp,$code);
348 my $cnt = $code;
748a9306 349
a687059c 350 while ($string =~ /^([^%]*)%(.)(.*)/) {
351 $result .= $1;
352 $code = $2;
353 $string = $3;
354 if ($code eq 'd') {
9f68db38 355 $result .= sprintf("%d",shift(@tmp));
a687059c 356 }
357 elsif ($code eq '.') {
9f68db38 358 $tmp = shift(@tmp);
a687059c 359 if ($tmp == 0 || $tmp == 4 || $tmp == 10) {
360 if ($online) {
cb1a09d0 361 ++$tmp, $after .= $self->{'_up'} if $self->{'_up'};
a687059c 362 }
363 else {
cb1a09d0 364 ++$tmp, $after .= $self->{'_bc'};
a687059c 365 }
366 }
367 $result .= sprintf("%c",$tmp);
368 $online = !$online;
369 }
370 elsif ($code eq '+') {
9f68db38 371 $result .= sprintf("%c",shift(@tmp)+ord($string));
a687059c 372 $string = substr($string,1,99);
373 $online = !$online;
374 }
375 elsif ($code eq 'r') {
9f68db38 376 ($code,$tmp) = @tmp;
377 @tmp = ($tmp,$code);
a687059c 378 $online = !$online;
379 }
380 elsif ($code eq '>') {
381 ($code,$tmp,$string) = unpack("CCa99",$string);
9f68db38 382 if ($tmp[$[] > $code) {
383 $tmp[$[] += $tmp;
a687059c 384 }
385 }
386 elsif ($code eq '2') {
9f68db38 387 $result .= sprintf("%02d",shift(@tmp));
a687059c 388 $online = !$online;
389 }
390 elsif ($code eq '3') {
9f68db38 391 $result .= sprintf("%03d",shift(@tmp));
a687059c 392 $online = !$online;
393 }
394 elsif ($code eq 'i') {
9f68db38 395 ($code,$tmp) = @tmp;
396 @tmp = ($code+1,$tmp+1);
a687059c 397 }
398 else {
399 return "OOPS";
400 }
401 }
cb1a09d0 402 $string = Tpad($self, $result . $string . $after, $cnt);
748a9306 403 print $FH $string if $FH;
404 $string;
405}
406
cb1a09d0 407# $terminal->Trequire(qw/ce ku kd/);
408sub Trequire { ## public
409 my $self = shift;
410 my($cap,@undefined);
411 foreach $cap (@_) {
412 push(@undefined, $cap)
413 unless defined $self->{'_' . $cap} && $self->{'_' . $cap};
748a9306 414 }
cb1a09d0 415 croak "Terminal does not support: (@undefined)" if @undefined;
a687059c 416}
417
4181;
cb1a09d0 419