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