(install-arch-indep): Fix previous change.
[bpt/emacs.git] / src / termcap.c
CommitLineData
f02902f7 1/* Work-alike for termcap, plus extra features.
aa42ddbe 2 Copyright (C) 1985, 86, 93, 94, 95 Free Software Foundation, Inc.
f02902f7
RM
3
4This program is free software; you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation; either version 2, or (at your option)
7any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program; see the file COPYING. If not, write to
ba4a8e51
KH
16the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17Boston, MA 02111-1307, USA. */
f02902f7
RM
18
19/* Emacs config.h may rename various library functions such as malloc. */
20#ifdef HAVE_CONFIG_H
21#include <config.h>
c70c2705
DM
22#endif
23
24#ifdef emacs
8fde62c1
RS
25
26/* Get the O_* definitions for open et al. */
27#include <sys/file.h>
28#ifdef USG5
29#include <fcntl.h>
30#endif
31
c70c2705 32#else /* not emacs */
f02902f7 33
f02902f7
RM
34#ifdef STDC_HEADERS
35#include <stdlib.h>
36#include <string.h>
37#else
38char *getenv ();
39char *malloc ();
40char *realloc ();
41#endif
42
aa42ddbe
DM
43/* Do this after the include, in case string.h prototypes bcopy. */
44#if (defined(HAVE_STRING_H) || defined(STDC_HEADERS)) && !defined(bcopy)
45#define bcopy(s, d, n) memcpy ((d), (s), (n))
46#endif
47
f02902f7
RM
48#ifdef HAVE_UNISTD_H
49#include <unistd.h>
50#endif
51#ifdef _POSIX_VERSION
52#include <fcntl.h>
53#endif
54
c70c2705 55#endif /* not emacs */
f02902f7
RM
56
57#ifndef NULL
58#define NULL (char *) 0
59#endif
60
983d99b5
RM
61#ifndef O_RDONLY
62#define O_RDONLY 0
63#endif
64
f02902f7
RM
65/* BUFSIZE is the initial size allocated for the buffer
66 for reading the termcap file.
67 It is not a limit.
68 Make it large normally for speed.
69 Make it variable when debugging, so can exercise
70 increasing the space dynamically. */
71
72#ifndef BUFSIZE
73#ifdef DEBUG
74#define BUFSIZE bufsize
75
76int bufsize = 128;
77#else
78#define BUFSIZE 2048
79#endif
80#endif
81
aa63d440
DM
82#ifndef TERMCAP_FILE
83#define TERMCAP_FILE "/etc/termcap"
f02902f7
RM
84#endif
85
86#ifndef emacs
87static void
88memory_out ()
89{
90 write (2, "virtual memory exhausted\n", 25);
91 exit (1);
92}
93
94static char *
95xmalloc (size)
96 unsigned size;
97{
98 register char *tem = malloc (size);
99
100 if (!tem)
101 memory_out ();
102 return tem;
103}
104
105static char *
106xrealloc (ptr, size)
107 char *ptr;
108 unsigned size;
109{
110 register char *tem = realloc (ptr, size);
111
112 if (!tem)
113 memory_out ();
114 return tem;
115}
116#endif /* not emacs */
117\f
118/* Looking up capabilities in the entry already found. */
119
120/* The pointer to the data made by tgetent is left here
121 for tgetnum, tgetflag and tgetstr to find. */
122static char *term_entry;
123
124static char *tgetst1 ();
125
126/* Search entry BP for capability CAP.
127 Return a pointer to the capability (in BP) if found,
128 0 if not found. */
129
130static char *
131find_capability (bp, cap)
132 register char *bp, *cap;
133{
134 for (; *bp; bp++)
135 if (bp[0] == ':'
136 && bp[1] == cap[0]
137 && bp[2] == cap[1])
138 return &bp[4];
139 return NULL;
140}
141
142int
143tgetnum (cap)
144 char *cap;
145{
146 register char *ptr = find_capability (term_entry, cap);
147 if (!ptr || ptr[-1] != '#')
148 return -1;
149 return atoi (ptr);
150}
151
152int
153tgetflag (cap)
154 char *cap;
155{
156 register char *ptr = find_capability (term_entry, cap);
157 return ptr && ptr[-1] == ':';
158}
159
160/* Look up a string-valued capability CAP.
161 If AREA is non-null, it points to a pointer to a block in which
162 to store the string. That pointer is advanced over the space used.
163 If AREA is null, space is allocated with `malloc'. */
164
165char *
166tgetstr (cap, area)
167 char *cap;
168 char **area;
169{
170 register char *ptr = find_capability (term_entry, cap);
171 if (!ptr || (ptr[-1] != '=' && ptr[-1] != '~'))
172 return NULL;
173 return tgetst1 (ptr, area);
174}
175
176/* Table, indexed by a character in range 0100 to 0140 with 0100 subtracted,
177 gives meaning of character following \, or a space if no special meaning.
178 Eight characters per line within the string. */
179
180static char esctab[]
181 = " \007\010 \033\014 \
182 \012 \
183 \015 \011 \013 \
184 ";
185
186/* PTR points to a string value inside a termcap entry.
187 Copy that value, processing \ and ^ abbreviations,
188 into the block that *AREA points to,
189 or to newly allocated storage if AREA is NULL.
190 Return the address to which we copied the value,
191 or NULL if PTR is NULL. */
192
193static char *
194tgetst1 (ptr, area)
195 char *ptr;
196 char **area;
197{
198 register char *p, *r;
199 register int c;
200 register int size;
201 char *ret;
202 register int c1;
203
204 if (!ptr)
205 return NULL;
206
207 /* `ret' gets address of where to store the string. */
208 if (!area)
209 {
210 /* Compute size of block needed (may overestimate). */
211 p = ptr;
212 while ((c = *p++) && c != ':' && c != '\n')
213 ;
214 ret = (char *) xmalloc (p - ptr + 1);
215 }
216 else
217 ret = *area;
218
219 /* Copy the string value, stopping at null or colon.
220 Also process ^ and \ abbreviations. */
221 p = ptr;
222 r = ret;
223 while ((c = *p++) && c != ':' && c != '\n')
224 {
225 if (c == '^')
d80bd4a0
RS
226 {
227 c = *p++;
228 if (c == '?')
229 c = 0177;
230 else
231 c &= 037;
232 }
f02902f7
RM
233 else if (c == '\\')
234 {
235 c = *p++;
236 if (c >= '0' && c <= '7')
237 {
238 c -= '0';
239 size = 0;
240
241 while (++size < 3 && (c1 = *p) >= '0' && c1 <= '7')
242 {
243 c *= 8;
244 c += c1 - '0';
245 p++;
246 }
247 }
248 else if (c >= 0100 && c < 0200)
249 {
250 c1 = esctab[(c & ~040) - 0100];
251 if (c1 != ' ')
252 c = c1;
253 }
254 }
255 *r++ = c;
256 }
257 *r = '\0';
258 /* Update *AREA. */
259 if (area)
260 *area = r + 1;
261 return ret;
262}
263\f
264/* Outputting a string with padding. */
265
266short ospeed;
267/* If OSPEED is 0, we use this as the actual baud rate. */
268int tputs_baud_rate;
269char PC;
270
271/* Actual baud rate if positive;
272 - baud rate / 100 if negative. */
273
0c0b6c79 274static int speeds[] =
f02902f7
RM
275 {
276#ifdef VMS
277 0, 50, 75, 110, 134, 150, -3, -6, -12, -18,
278 -20, -24, -36, -48, -72, -96, -192
279#else /* not VMS */
280 0, 50, 75, 110, 135, 150, -2, -3, -6, -12,
0c0b6c79 281 -18, -24, -48, -96, -192, -288, -384, -576, -1152
f02902f7
RM
282#endif /* not VMS */
283 };
284
285void
286tputs (str, nlines, outfun)
287 register char *str;
288 int nlines;
289 register int (*outfun) ();
290{
291 register int padcount = 0;
292 register int speed;
293
294#ifdef emacs
295 extern baud_rate;
296 speed = baud_rate;
0c0b6c79
RS
297 /* For quite high speeds, convert to the smaller
298 units to avoid overflow. */
299 if (speed > 10000)
300 speed = - speed / 100;
f02902f7
RM
301#else
302 if (ospeed == 0)
303 speed = tputs_baud_rate;
c7c5360f 304 else if (ospeed > 0 && ospeed < (sizeof speeds / sizeof speeds[0]))
f02902f7 305 speed = speeds[ospeed];
c7c5360f
DM
306 else
307 speed = 0;
f02902f7
RM
308#endif
309
310 if (!str)
311 return;
312
313 while (*str >= '0' && *str <= '9')
314 {
315 padcount += *str++ - '0';
316 padcount *= 10;
317 }
318 if (*str == '.')
319 {
320 str++;
321 padcount += *str++ - '0';
322 }
323 if (*str == '*')
324 {
325 str++;
326 padcount *= nlines;
327 }
328 while (*str)
329 (*outfun) (*str++);
330
0c0b6c79 331 /* PADCOUNT is now in units of tenths of msec.
95c74a10 332 SPEED is measured in characters per 10 seconds
0c0b6c79
RS
333 or in characters per .1 seconds (if negative).
334 We use the smaller units for larger speeds to avoid overflow. */
335 padcount *= speed;
f02902f7
RM
336 padcount += 500;
337 padcount /= 1000;
0c0b6c79 338 if (speed < 0)
f02902f7
RM
339 padcount = -padcount;
340 else
341 {
342 padcount += 50;
343 padcount /= 100;
344 }
345
346 while (padcount-- > 0)
347 (*outfun) (PC);
348}
349\f
350/* Finding the termcap entry in the termcap data base. */
351
352struct buffer
353 {
354 char *beg;
355 int size;
356 char *ptr;
357 int ateof;
358 int full;
359 };
360
361/* Forward declarations of static functions. */
362
363static int scan_file ();
364static char *gobble_line ();
365static int compare_contin ();
366static int name_match ();
367
368#ifdef VMS
369
370#include <rmsdef.h>
371#include <fab.h>
372#include <nam.h>
373
374static int
375valid_filename_p (fn)
376 char *fn;
377{
378 struct FAB fab = cc$rms_fab;
379 struct NAM nam = cc$rms_nam;
380 char esa[NAM$C_MAXRSS];
381
382 fab.fab$l_fna = fn;
383 fab.fab$b_fns = strlen(fn);
384 fab.fab$l_nam = &nam;
385 fab.fab$l_fop = FAB$M_NAM;
386
387 nam.nam$l_esa = esa;
388 nam.nam$b_ess = sizeof esa;
389
390 return SYS$PARSE(&fab, 0, 0) == RMS$_NORMAL;
391}
392
393#else /* !VMS */
394
395#ifdef MSDOS /* MW, May 1993 */
396static int
397valid_filename_p (fn)
398 char *fn;
399{
400 return *fn == '/' || fn[1] == ':';
401}
402#else
403#define valid_filename_p(fn) (*(fn) == '/')
404#endif
405
406#endif /* !VMS */
407
408/* Find the termcap entry data for terminal type NAME
409 and store it in the block that BP points to.
410 Record its address for future use.
411
412 If BP is null, space is dynamically allocated.
413
414 Return -1 if there is some difficulty accessing the data base
415 of terminal types,
416 0 if the data base is accessible but the type NAME is not defined
417 in it, and some other value otherwise. */
418
419int
420tgetent (bp, name)
421 char *bp, *name;
422{
423 register char *termcap_name;
424 register int fd;
425 struct buffer buf;
426 register char *bp1;
f21b7e24 427 char *tc_search_point;
f02902f7
RM
428 char *term;
429 int malloc_size = 0;
430 register int c;
431 char *tcenv; /* TERMCAP value, if it contains :tc=. */
432 char *indirect = NULL; /* Terminal type in :tc= in TERMCAP value. */
433 int filep;
434
435#ifdef INTERNAL_TERMINAL
436 /* For the internal terminal we don't want to read any termcap file,
437 so fake it. */
438 if (!strcmp (name, "internal"))
439 {
440 term = INTERNAL_TERMINAL;
441 if (!bp)
442 {
443 malloc_size = 1 + strlen (term);
444 bp = (char *) xmalloc (malloc_size);
445 }
446 strcpy (bp, term);
447 goto ret;
448 }
449#endif /* INTERNAL_TERMINAL */
450
3e19e687
DM
451 /* For compatibility with programs like `less' that want to
452 put data in the termcap buffer themselves as a fallback. */
453 if (bp)
454 term_entry = bp;
455
f02902f7
RM
456 termcap_name = getenv ("TERMCAP");
457 if (termcap_name && *termcap_name == '\0')
458 termcap_name = NULL;
459#if defined (MSDOS) && !defined (TEST)
460 if (termcap_name && (*termcap_name == '\\'
461 || *termcap_name == '/'
462 || termcap_name[1] == ':'))
463 dostounix_filename(termcap_name);
464#endif
465
466 filep = termcap_name && valid_filename_p (termcap_name);
467
468 /* If termcap_name is non-null and starts with / (in the un*x case, that is),
469 it is a file name to use instead of /etc/termcap.
470 If it is non-null and does not start with /,
471 it is the entry itself, but only if
472 the name the caller requested matches the TERM variable. */
473
474 if (termcap_name && !filep && !strcmp (name, getenv ("TERM")))
475 {
476 indirect = tgetst1 (find_capability (termcap_name, "tc"), (char **) 0);
477 if (!indirect)
478 {
479 if (!bp)
480 bp = termcap_name;
481 else
482 strcpy (bp, termcap_name);
483 goto ret;
484 }
485 else
486 { /* It has tc=. Need to read /etc/termcap. */
487 tcenv = termcap_name;
488 termcap_name = NULL;
489 }
490 }
491
492 if (!termcap_name || !filep)
aa63d440 493 termcap_name = TERMCAP_FILE;
f02902f7
RM
494
495 /* Here we know we must search a file and termcap_name has its name. */
496
497#ifdef MSDOS
983d99b5 498 fd = open (termcap_name, O_RDONLY|O_TEXT, 0);
f02902f7 499#else
983d99b5 500 fd = open (termcap_name, O_RDONLY, 0);
f02902f7
RM
501#endif
502 if (fd < 0)
503 return -1;
504
505 buf.size = BUFSIZE;
506 /* Add 1 to size to ensure room for terminating null. */
507 buf.beg = (char *) xmalloc (buf.size + 1);
508 term = indirect ? indirect : name;
509
510 if (!bp)
511 {
512 malloc_size = indirect ? strlen (tcenv) + 1 : buf.size;
513 bp = (char *) xmalloc (malloc_size);
514 }
f21b7e24 515 tc_search_point = bp1 = bp;
f02902f7
RM
516
517 if (indirect)
518 /* Copy the data from the environment variable. */
519 {
520 strcpy (bp, tcenv);
521 bp1 += strlen (tcenv);
522 }
523
524 while (term)
525 {
526 /* Scan the file, reading it via buf, till find start of main entry. */
527 if (scan_file (term, fd, &buf) == 0)
528 {
529 close (fd);
530 free (buf.beg);
531 if (malloc_size)
532 free (bp);
533 return 0;
534 }
535
536 /* Free old `term' if appropriate. */
537 if (term != name)
538 free (term);
539
540 /* If BP is malloc'd by us, make sure it is big enough. */
541 if (malloc_size)
542 {
543 malloc_size = bp1 - bp + buf.size;
544 termcap_name = (char *) xrealloc (bp, malloc_size);
545 bp1 += termcap_name - bp;
f21b7e24 546 tc_search_point += termcap_name - bp;
f02902f7
RM
547 bp = termcap_name;
548 }
549
f02902f7
RM
550 /* Copy the line of the entry from buf into bp. */
551 termcap_name = buf.ptr;
552 while ((*bp1++ = c = *termcap_name++) && c != '\n')
553 /* Drop out any \ newline sequence. */
554 if (c == '\\' && *termcap_name == '\n')
555 {
556 bp1--;
557 termcap_name++;
558 }
559 *bp1 = '\0';
560
561 /* Does this entry refer to another terminal type's entry?
562 If something is found, copy it into heap and null-terminate it. */
f21b7e24
KH
563 tc_search_point = find_capability (tc_search_point, "tc");
564 term = tgetst1 (tc_search_point, (char **) 0);
f02902f7
RM
565 }
566
567 close (fd);
568 free (buf.beg);
569
570 if (malloc_size)
571 bp = (char *) xrealloc (bp, bp1 - bp + 1);
572
573 ret:
574 term_entry = bp;
f02902f7
RM
575 return 1;
576}
577
578/* Given file open on FD and buffer BUFP,
579 scan the file from the beginning until a line is found
580 that starts the entry for terminal type STR.
581 Return 1 if successful, with that line in BUFP,
582 or 0 if no entry is found in the file. */
583
584static int
585scan_file (str, fd, bufp)
586 char *str;
587 int fd;
588 register struct buffer *bufp;
589{
590 register char *end;
591
592 bufp->ptr = bufp->beg;
593 bufp->full = 0;
594 bufp->ateof = 0;
595 *bufp->ptr = '\0';
596
597 lseek (fd, 0L, 0);
598
599 while (!bufp->ateof)
600 {
601 /* Read a line into the buffer. */
602 end = NULL;
603 do
604 {
605 /* if it is continued, append another line to it,
606 until a non-continued line ends. */
607 end = gobble_line (fd, bufp, end);
608 }
609 while (!bufp->ateof && end[-2] == '\\');
610
611 if (*bufp->ptr != '#'
612 && name_match (bufp->ptr, str))
613 return 1;
614
615 /* Discard the line just processed. */
616 bufp->ptr = end;
617 }
618 return 0;
619}
620
621/* Return nonzero if NAME is one of the names specified
622 by termcap entry LINE. */
623
624static int
625name_match (line, name)
626 char *line, *name;
627{
628 register char *tem;
629
630 if (!compare_contin (line, name))
631 return 1;
632 /* This line starts an entry. Is it the right one? */
633 for (tem = line; *tem && *tem != '\n' && *tem != ':'; tem++)
634 if (*tem == '|' && !compare_contin (tem + 1, name))
635 return 1;
636
637 return 0;
638}
639
640static int
641compare_contin (str1, str2)
642 register char *str1, *str2;
643{
644 register int c1, c2;
645 while (1)
646 {
647 c1 = *str1++;
648 c2 = *str2++;
649 while (c1 == '\\' && *str1 == '\n')
650 {
651 str1++;
652 while ((c1 = *str1++) == ' ' || c1 == '\t');
653 }
654 if (c2 == '\0')
655 {
656 /* End of type being looked up. */
657 if (c1 == '|' || c1 == ':')
658 /* If end of name in data base, we win. */
659 return 0;
660 else
661 return 1;
662 }
663 else if (c1 != c2)
664 return 1;
665 }
666}
667
668/* Make sure that the buffer <- BUFP contains a full line
669 of the file open on FD, starting at the place BUFP->ptr
670 points to. Can read more of the file, discard stuff before
671 BUFP->ptr, or make the buffer bigger.
672
673 Return the pointer to after the newline ending the line,
674 or to the end of the file, if there is no newline to end it.
675
676 Can also merge on continuation lines. If APPEND_END is
677 non-null, it points past the newline of a line that is
678 continued; we add another line onto it and regard the whole
679 thing as one line. The caller decides when a line is continued. */
680
681static char *
682gobble_line (fd, bufp, append_end)
683 int fd;
684 register struct buffer *bufp;
685 char *append_end;
686{
687 register char *end;
688 register int nread;
689 register char *buf = bufp->beg;
690 register char *tem;
691
692 if (!append_end)
693 append_end = bufp->ptr;
694
695 while (1)
696 {
697 end = append_end;
698 while (*end && *end != '\n') end++;
699 if (*end)
700 break;
701 if (bufp->ateof)
702 return buf + bufp->full;
703 if (bufp->ptr == buf)
704 {
705 if (bufp->full == bufp->size)
706 {
707 bufp->size *= 2;
708 /* Add 1 to size to ensure room for terminating null. */
709 tem = (char *) xrealloc (buf, bufp->size + 1);
710 bufp->ptr = (bufp->ptr - buf) + tem;
711 append_end = (append_end - buf) + tem;
712 bufp->beg = buf = tem;
713 }
714 }
715 else
716 {
717 append_end -= bufp->ptr - buf;
718 bcopy (bufp->ptr, buf, bufp->full -= bufp->ptr - buf);
719 bufp->ptr = buf;
720 }
721 if (!(nread = read (fd, buf + bufp->full, bufp->size - bufp->full)))
722 bufp->ateof = 1;
723 bufp->full += nread;
724 buf[bufp->full] = '\0';
725 }
726 return end + 1;
727}
728\f
729#ifdef TEST
730
731#ifdef NULL
732#undef NULL
733#endif
734
735#include <stdio.h>
736
737main (argc, argv)
738 int argc;
739 char **argv;
740{
741 char *term;
742 char *buf;
743
744 term = argv[1];
745 printf ("TERM: %s\n", term);
746
747 buf = (char *) tgetent (0, term);
748 if ((int) buf <= 0)
749 {
750 printf ("No entry.\n");
751 return 0;
752 }
753
754 printf ("Entry: %s\n", buf);
755
756 tprint ("cm");
757 tprint ("AL");
758
759 printf ("co: %d\n", tgetnum ("co"));
760 printf ("am: %d\n", tgetflag ("am"));
761}
762
763tprint (cap)
764 char *cap;
765{
766 char *x = tgetstr (cap, 0);
767 register char *y;
768
769 printf ("%s: ", cap);
770 if (x)
771 {
772 for (y = x; *y; y++)
773 if (*y <= ' ' || *y == 0177)
774 printf ("\\%0o", *y);
775 else
776 putchar (*y);
777 free (x);
778 }
779 else
780 printf ("none");
781 putchar ('\n');
782}
783
784#endif /* TEST */