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