Support [] style prototypes.
[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;
e66fb0c2 171
172 unless (@termcap_path || $entry)
173 {
174 # last resort--fake up a termcap from terminfo
175 local $ENV{TERM} = $term;
bf25f2b5 176 if ($^O ne 'VMS') {
177 $entry = `infocmp -C 2>/dev/null`;
178 } else {
179 $entry = undef;
180 }
e66fb0c2 181 }
182
cb1a09d0 183 croak "Can't find a valid termcap file" unless @termcap_path || $entry;
184
185 $state = 1; # 0 == finished
186 # 1 == next file
187 # 2 == search again
188
189 $first = 0; # first entry (keeps term name)
190
191 $max = 32; # max :tc=...:'s
192
193 if ($entry) {
194 # ok, we're starting with $TERMCAP
195 $first++; # we're the first entry
196 # do we need to continue?
197 if ($entry =~ s/:tc=([^:]+):/:/) {
198 $tmp_term = $1;
199 # protect any pattern metacharacters in $tmp_term
200 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
201 }
202 else {
203 $state = 0; # we're already finished
204 }
205 }
206
207 # This is eval'ed inside the while loop for each file
208 $search = q{
54310121 209 while (<TERMCAP>) {
cb1a09d0 210 next if /^\\t/ || /^#/;
211 if ($_ =~ m/(^|\\|)${termpat}[:|]/o) {
212 chomp;
213 s/^[^:]*:// if $first++;
214 $state = 0;
54310121 215 while ($_ =~ s/\\\\$//) {
216 defined(my $x = <TERMCAP>) or last;
217 $_ .= $x; chomp;
218 }
cb1a09d0 219 last;
748a9306 220 }
cb1a09d0 221 }
55497cff 222 defined $entry or $entry = '';
e14c93b3 223 $entry .= $_ if $_;
cb1a09d0 224 };
748a9306 225
cb1a09d0 226 while ($state != 0) {
227 if ($state == 1) {
228 # get the next TERMCAP
229 $TERMCAP = shift @termcap_path
230 || croak "failed termcap lookup on $tmp_term";
231 }
232 else {
233 # do the same file again
234 # prevent endless recursion
235 $max-- || croak "failed termcap loop at $tmp_term";
236 $state = 1; # ok, maybe do a new file next time
237 }
238
239 open(TERMCAP,"< $TERMCAP\0") || croak "open $TERMCAP: $!";
240 eval $search;
241 die $@ if $@;
242 close TERMCAP;
243
244 # If :tc=...: found then search this file again
245 $entry =~ s/:tc=([^:]+):/:/ && ($tmp_term = $1, $state = 2);
246 # protect any pattern metacharacters in $tmp_term
247 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
a687059c 248 }
cb1a09d0 249
250 croak "Can't find $term" if $entry eq '';
251 $entry =~ s/:+\s*:+/:/g; # cleanup $entry
252 $entry =~ s/:+/:/g; # cleanup $entry
253 $self->{TERMCAP} = $entry; # save it
254 # print STDERR "DEBUG: $entry = ", $entry, "\n";
a687059c 255
748a9306 256 # Precompile $entry into the object
cb1a09d0 257 $entry =~ s/^[^:]*://;
748a9306 258 foreach $field (split(/:[\s:\\]*/,$entry)) {
cb1a09d0 259 if ($field =~ /^(\w\w)$/) {
260 $self->{'_' . $field} = 1 unless defined $self->{'_' . $1};
261 # print STDERR "DEBUG: flag $1\n";
748a9306 262 }
263 elsif ($field =~ /^(\w\w)\@/) {
cb1a09d0 264 $self->{'_' . $1} = "";
265 # print STDERR "DEBUG: unset $1\n";
a687059c 266 }
267 elsif ($field =~ /^(\w\w)#(.*)/) {
cb1a09d0 268 $self->{'_' . $1} = $2 unless defined $self->{'_' . $1};
269 # print STDERR "DEBUG: numeric $1 = $2\n";
a687059c 270 }
271 elsif ($field =~ /^(\w\w)=(.*)/) {
cb1a09d0 272 # print STDERR "DEBUG: string $1 = $2\n";
273 next if defined $self->{'_' . ($cap = $1)};
a687059c 274 $_ = $2;
275 s/\\E/\033/g;
ecfc5424 276 s/\\(\d\d\d)/pack('c',oct($1) & 0177)/eg;
a687059c 277 s/\\n/\n/g;
278 s/\\r/\r/g;
279 s/\\t/\t/g;
280 s/\\b/\b/g;
281 s/\\f/\f/g;
282 s/\\\^/\377/g;
283 s/\^\?/\177/g;
63f2c1e1 284 s/\^(.)/pack('c',ord($1) & 31)/eg;
a687059c 285 s/\\(.)/$1/g;
286 s/\377/^/g;
cb1a09d0 287 $self->{'_' . $cap} = $_;
a687059c 288 }
cb1a09d0 289 # else { carp "junk in $term ignored: $field"; }
a687059c 290 }
cb1a09d0 291 $self->{'_pc'} = "\0" unless defined $self->{'_pc'};
292 $self->{'_bc'} = "\b" unless defined $self->{'_bc'};
293 $self;
a687059c 294}
295
cb1a09d0 296# $terminal->Tpad($string, $cnt, $FH);
297sub Tpad { ## public
298 my $self = shift;
299 my($string, $cnt, $FH) = @_;
300 my($decr, $ms);
a687059c 301
5d88b850 302 if (defined $string && $string =~ /(^[\d.]+)(\*?)(.*)$/) {
a687059c 303 $ms = $1;
748a9306 304 $ms *= $cnt if $2;
a687059c 305 $string = $3;
cb1a09d0 306 $decr = $self->{PADDING};
a687059c 307 if ($decr > .1) {
308 $ms += $decr / 2;
cb1a09d0 309 $string .= $self->{'_pc'} x ($ms / $decr);
a687059c 310 }
311 }
312 print $FH $string if $FH;
313 $string;
314}
315
cb1a09d0 316# $terminal->Tputs($cap, $cnt, $FH);
317sub Tputs { ## public
318 my $self = shift;
319 my($cap, $cnt, $FH) = @_;
320 my $string;
748a9306 321
322 if ($cnt > 1) {
cb1a09d0 323 $string = Tpad($self, $self->{'_' . $cap}, $cnt);
748a9306 324 } else {
cb1a09d0 325 # cache result because Tpad can be slow
e14c93b3 326 unless (exists $self->{$cap}) {
327 $self->{$cap} = exists $self->{"_$cap"} ?
328 Tpad($self, $self->{"_$cap"}, 1) : undef;
329 }
330 $string = $self->{$cap};
748a9306 331 }
332 print $FH $string if $FH;
333 $string;
334}
335
336# %% output `%'
337# %d output value as in printf %d
338# %2 output value as in printf %2d
339# %3 output value as in printf %3d
340# %. output value as in printf %c
341# %+x add x to value, then do %.
342#
343# %>xy if value > x then add y, no output
344# %r reverse order of two parameters, no output
345# %i increment by one, no output
346# %B BCD (16*(value/10)) + (value%10), no output
347#
348# %n exclusive-or all parameters with 0140 (Datamedia 2500)
349# %D Reverse coding (value - 2*(value%16)), no output (Delta Data)
350#
cb1a09d0 351# $terminal->Tgoto($cap, $col, $row, $FH);
352sub Tgoto { ## public
353 my $self = shift;
354 my($cap, $code, $tmp, $FH) = @_;
355 my $string = $self->{'_' . $cap};
356 my $result = '';
357 my $after = '';
358 my $online = 0;
359 my @tmp = ($tmp,$code);
360 my $cnt = $code;
748a9306 361
a687059c 362 while ($string =~ /^([^%]*)%(.)(.*)/) {
363 $result .= $1;
364 $code = $2;
365 $string = $3;
366 if ($code eq 'd') {
9f68db38 367 $result .= sprintf("%d",shift(@tmp));
a687059c 368 }
369 elsif ($code eq '.') {
9f68db38 370 $tmp = shift(@tmp);
a687059c 371 if ($tmp == 0 || $tmp == 4 || $tmp == 10) {
372 if ($online) {
cb1a09d0 373 ++$tmp, $after .= $self->{'_up'} if $self->{'_up'};
a687059c 374 }
375 else {
cb1a09d0 376 ++$tmp, $after .= $self->{'_bc'};
a687059c 377 }
378 }
379 $result .= sprintf("%c",$tmp);
380 $online = !$online;
381 }
382 elsif ($code eq '+') {
9f68db38 383 $result .= sprintf("%c",shift(@tmp)+ord($string));
a687059c 384 $string = substr($string,1,99);
385 $online = !$online;
386 }
387 elsif ($code eq 'r') {
9f68db38 388 ($code,$tmp) = @tmp;
389 @tmp = ($tmp,$code);
a687059c 390 $online = !$online;
391 }
392 elsif ($code eq '>') {
393 ($code,$tmp,$string) = unpack("CCa99",$string);
9f68db38 394 if ($tmp[$[] > $code) {
395 $tmp[$[] += $tmp;
a687059c 396 }
397 }
398 elsif ($code eq '2') {
9f68db38 399 $result .= sprintf("%02d",shift(@tmp));
a687059c 400 $online = !$online;
401 }
402 elsif ($code eq '3') {
9f68db38 403 $result .= sprintf("%03d",shift(@tmp));
a687059c 404 $online = !$online;
405 }
406 elsif ($code eq 'i') {
9f68db38 407 ($code,$tmp) = @tmp;
408 @tmp = ($code+1,$tmp+1);
a687059c 409 }
410 else {
411 return "OOPS";
412 }
413 }
cb1a09d0 414 $string = Tpad($self, $result . $string . $after, $cnt);
748a9306 415 print $FH $string if $FH;
416 $string;
417}
418
cb1a09d0 419# $terminal->Trequire(qw/ce ku kd/);
420sub Trequire { ## public
421 my $self = shift;
422 my($cap,@undefined);
423 foreach $cap (@_) {
424 push(@undefined, $cap)
425 unless defined $self->{'_' . $cap} && $self->{'_' . $cap};
748a9306 426 }
cb1a09d0 427 croak "Terminal does not support: (@undefined)" if @undefined;
a687059c 428}
429
4301;
cb1a09d0 431