Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
12 KiB

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