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