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