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