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