]> ncurses.scripts.mit.edu Git - ncurses.git/blob - progs/tset.c
ncurses 5.2
[ncurses.git] / progs / tset.c
1 /****************************************************************************
2  * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33
34 /*
35  * tset.c - terminal initialization utility
36  *
37  * This code was mostly swiped from 4.4BSD tset, with some obsolescent
38  * cruft removed and substantial portions rewritten.  A Regents of the
39  * University of California copyright applies to some portions of the
40  * code, and is reproduced below:
41  */
42 /*-
43  * Copyright (c) 1980, 1991, 1993
44  *      The Regents of the University of California.  All rights reserved.
45  *
46  * Redistribution and use in source and binary forms, with or without
47  * modification, are permitted provided that the following conditions
48  * are met:
49  * 1. Redistributions of source code must retain the above copyright
50  *    notice, this list of conditions and the following disclaimer.
51  * 2. Redistributions in binary form must reproduce the above copyright
52  *    notice, this list of conditions and the following disclaimer in the
53  *    documentation and/or other materials provided with the distribution.
54  * 3. All advertising materials mentioning features or use of this software
55  *    must display the following acknowledgement:
56  *      This product includes software developed by the University of
57  *      California, Berkeley and its contributors.
58  * 4. Neither the name of the University nor the names of its contributors
59  *    may be used to endorse or promote products derived from this software
60  *    without specific prior written permission.
61  *
62  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
63  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
64  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
65  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
66  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
67  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
68  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
69  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
70  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
71  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
72  * SUCH DAMAGE.
73  */
74
75 #define __INTERNAL_CAPS_VISIBLE /* we need to see has_hardware_tabs */
76 #include <progs.priv.h>
77
78 #include <errno.h>
79 #include <stdio.h>
80 #include <termcap.h>
81 #include <fcntl.h>
82
83 #if HAVE_GETTTYNAM && HAVE_TTYENT_H
84 #include <ttyent.h>
85 #endif
86 #ifdef NeXT
87 char *ttyname(int fd);
88 #endif
89
90 /* this is just to stifle a missing-prototype warning */
91 #ifdef linux
92 # include <sys/ioctl.h>
93 #endif
94
95 #if NEED_PTEM_H
96 /* they neglected to define struct winsize in termios.h -- it's only
97    in termio.h  */
98 #include        <sys/stream.h>
99 #include        <sys/ptem.h>
100 #endif
101
102 #include <curses.h>             /* for bool typedef */
103 #include <dump_entry.h>
104 #include <transform.h>
105
106 MODULE_ID("$Id: tset.c,v 0.47 2000/10/08 01:01:08 tom Exp $")
107
108 extern char **environ;
109
110 #undef CTRL
111 #define CTRL(x) ((x) & 0x1f)
112
113 const char *_nc_progname = "tset";
114
115 static TTY mode, oldmode;
116
117 static bool isreset = FALSE;    /* invoked as reset */
118 static int terasechar = -1;     /* new erase character */
119 static int intrchar = -1;       /* new interrupt character */
120 static int tkillchar = -1;      /* new kill character */
121 static int tlines, tcolumns;    /* window size */
122
123 #define LOWERCASE(c) ((isalpha(c) && isupper(c)) ? tolower(c) : (c))
124
125 static int
126 CaselessCmp(const char *a, const char *b)
127 {                               /* strcasecmp isn't portable */
128     while (*a && *b) {
129         int cmp = LOWERCASE(*a) - LOWERCASE(*b);
130         if (cmp != 0)
131             break;
132         a++, b++;
133     }
134     return LOWERCASE(*a) - LOWERCASE(*b);
135 }
136
137 static void
138 err(const char *fmt,...)
139 {
140     va_list ap;
141     va_start(ap, fmt);
142     (void) fprintf(stderr, "tset: ");
143     (void) vfprintf(stderr, fmt, ap);
144     va_end(ap);
145     (void) fprintf(stderr, "\n");
146     exit(EXIT_FAILURE);
147     /* NOTREACHED */
148 }
149
150 static void
151 failed(const char *msg)
152 {
153     char temp[BUFSIZ];
154     perror(strncat(strcpy(temp, "tset: "), msg, sizeof(temp) - 10));
155     exit(EXIT_FAILURE);
156     /* NOTREACHED */
157 }
158
159 static void
160 cat(char *file)
161 {
162     register int fd, nr, nw;
163     char buf[BUFSIZ];
164
165     if ((fd = open(file, O_RDONLY, 0)) < 0)
166         failed(file);
167
168     while ((nr = read(fd, buf, sizeof(buf))) > 0)
169         if ((nw = write(STDERR_FILENO, buf, (size_t) nr)) == -1)
170             failed("write to stderr");
171     if (nr != 0)
172         failed(file);
173     (void) close(fd);
174 }
175
176 static int
177 outc(int c)
178 {
179     return putc(c, stderr);
180 }
181
182 /* Prompt the user for a terminal type. */
183 static const char *
184 askuser(const char *dflt)
185 {
186     static char answer[256];
187     char *p;
188
189     /* We can get recalled; if so, don't continue uselessly. */
190     if (feof(stdin) || ferror(stdin)) {
191         (void) fprintf(stderr, "\n");
192         exit(EXIT_FAILURE);
193     }
194     for (;;) {
195         if (dflt)
196             (void) fprintf(stderr, "Terminal type? [%s] ", dflt);
197         else
198             (void) fprintf(stderr, "Terminal type? ");
199         (void) fflush(stderr);
200
201         if (fgets(answer, sizeof(answer), stdin) == 0) {
202             if (dflt == 0) {
203                 (void) fprintf(stderr, "\n");
204                 exit(EXIT_FAILURE);
205             }
206             return (dflt);
207         }
208
209         if ((p = strchr(answer, '\n')) != 0)
210             *p = '\0';
211         if (answer[0])
212             return (answer);
213         if (dflt != 0)
214             return (dflt);
215     }
216 }
217
218 /**************************************************************************
219  *
220  * Mapping logic begins here
221  *
222  **************************************************************************/
223
224 /* Baud rate conditionals for mapping. */
225 #define GT              0x01
226 #define EQ              0x02
227 #define LT              0x04
228 #define NOT             0x08
229 #define GE              (GT | EQ)
230 #define LE              (LT | EQ)
231
232 typedef struct map {
233     struct map *next;           /* Linked list of maps. */
234     const char *porttype;       /* Port type, or "" for any. */
235     const char *type;           /* Terminal type to select. */
236     int conditional;            /* Baud rate conditionals bitmask. */
237     int speed;                  /* Baud rate to compare against. */
238 } MAP;
239
240 static MAP *cur, *maplist;
241
242 typedef struct speeds {
243     const char *string;
244     int speed;
245 } SPEEDS;
246
247 static const SPEEDS speeds[] =
248 {
249     {"0", B0},
250     {"50", B50},
251     {"75", B75},
252     {"110", B110},
253     {"134", B134},
254     {"134.5", B134},
255     {"150", B150},
256     {"200", B200},
257     {"300", B300},
258     {"600", B600},
259     {"1200", B1200},
260     {"1800", B1800},
261     {"2400", B2400},
262     {"4800", B4800},
263     {"9600", B9600},
264     /* sgttyb may define up to this point */
265 #ifdef B19200
266     {"19200", B19200},
267 #endif
268 #ifdef B38400
269     {"38400", B38400},
270 #endif
271 #ifdef B19200
272     {"19200", B19200},
273 #endif
274 #ifdef B38400
275     {"38400", B38400},
276 #endif
277 #ifdef B19200
278     {"19200", B19200},
279 #else
280 #ifdef EXTA
281     {"19200", EXTA},
282 #endif
283 #endif
284 #ifdef B38400
285     {"38400", B38400},
286 #else
287 #ifdef EXTB
288     {"38400", EXTB},
289 #endif
290 #endif
291 #ifdef B57600
292     {"57600", B57600},
293 #endif
294 #ifdef B115200
295     {"115200", B115200},
296 #endif
297 #ifdef B230400
298     {"230400", B230400},
299 #endif
300 #ifdef B460800
301     {"460800", B460800},
302 #endif
303     {(char *) 0, 0}
304 };
305
306 static int
307 tbaudrate(char *rate)
308 {
309     const SPEEDS *sp;
310     int found = FALSE;
311
312     /* The baudrate number can be preceded by a 'B', which is ignored. */
313     if (*rate == 'B')
314         ++rate;
315
316     for (sp = speeds; sp->string; ++sp) {
317         if (!CaselessCmp(rate, sp->string)) {
318             found = TRUE;
319             break;
320         }
321     }
322     if (!found)
323         err("unknown baud rate %s", rate);
324     return (sp->speed);
325 }
326
327 /*
328  * Syntax for -m:
329  * [port-type][test baudrate]:terminal-type
330  * The baud rate tests are: >, <, @, =, !
331  */
332 static void
333 add_mapping(const char *port, char *arg)
334 {
335     MAP *mapp;
336     char *copy, *p;
337     const char *termp;
338     char *base = 0;
339
340     copy = strdup(arg);
341     mapp = malloc(sizeof(MAP));
342     if (copy == 0 || mapp == 0)
343         failed("malloc");
344     mapp->next = 0;
345     if (maplist == 0)
346         cur = maplist = mapp;
347     else {
348         cur->next = mapp;
349         cur = mapp;
350     }
351
352     mapp->porttype = arg;
353     mapp->conditional = 0;
354
355     arg = strpbrk(arg, "><@=!:");
356
357     if (arg == 0) {             /* [?]term */
358         mapp->type = mapp->porttype;
359         mapp->porttype = 0;
360         goto done;
361     }
362
363     if (arg == mapp->porttype)  /* [><@=! baud]:term */
364         termp = mapp->porttype = 0;
365     else
366         termp = base = arg;
367
368     for (;; ++arg) {            /* Optional conditionals. */
369         switch (*arg) {
370         case '<':
371             if (mapp->conditional & GT)
372                 goto badmopt;
373             mapp->conditional |= LT;
374             break;
375         case '>':
376             if (mapp->conditional & LT)
377                 goto badmopt;
378             mapp->conditional |= GT;
379             break;
380         case '@':
381         case '=':               /* Not documented. */
382             mapp->conditional |= EQ;
383             break;
384         case '!':
385             mapp->conditional |= NOT;
386             break;
387         default:
388             goto next;
389         }
390     }
391
392   next:
393     if (*arg == ':') {
394         if (mapp->conditional)
395             goto badmopt;
396         ++arg;
397     } else {                    /* Optional baudrate. */
398         arg = strchr(p = arg, ':');
399         if (arg == 0)
400             goto badmopt;
401         *arg++ = '\0';
402         mapp->speed = tbaudrate(p);
403     }
404
405     if (arg == (char *) 0)      /* Non-optional type. */
406         goto badmopt;
407
408     mapp->type = arg;
409
410     /* Terminate porttype, if specified. */
411     if (termp != 0)
412         *base = '\0';
413
414     /* If a NOT conditional, reverse the test. */
415     if (mapp->conditional & NOT)
416         mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
417
418     /* If user specified a port with an option flag, set it. */
419   done:if (port) {
420         if (mapp->porttype)
421           badmopt:err("illegal -m option format: %s", copy);
422         mapp->porttype = port;
423     }
424 #ifdef MAPDEBUG
425     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
426     (void) printf("type: %s\n", mapp->type);
427     (void) printf("conditional: ");
428     p = "";
429     if (mapp->conditional & GT) {
430         (void) printf("GT");
431         p = "/";
432     }
433     if (mapp->conditional & EQ) {
434         (void) printf("%sEQ", p);
435         p = "/";
436     }
437     if (mapp->conditional & LT)
438         (void) printf("%sLT", p);
439     (void) printf("\nspeed: %d\n", mapp->speed);
440 #endif
441 }
442
443 /*
444  * Return the type of terminal to use for a port of type 'type', as specified
445  * by the first applicable mapping in 'map'.  If no mappings apply, return
446  * 'type'.
447  */
448 static const char *
449 mapped(const char *type)
450 {
451     MAP *mapp;
452     int match;
453
454     for (mapp = maplist; mapp; mapp = mapp->next)
455         if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
456             switch (mapp->conditional) {
457             case 0:             /* No test specified. */
458                 match = TRUE;
459                 break;
460             case EQ:
461                 match = (ospeed == mapp->speed);
462                 break;
463             case GE:
464                 match = (ospeed >= mapp->speed);
465                 break;
466             case GT:
467                 match = (ospeed > mapp->speed);
468                 break;
469             case LE:
470                 match = (ospeed <= mapp->speed);
471                 break;
472             case LT:
473                 match = (ospeed < mapp->speed);
474                 break;
475             default:
476                 match = FALSE;
477             }
478             if (match)
479                 return (mapp->type);
480         }
481     /* No match found; return given type. */
482     return (type);
483 }
484
485 /**************************************************************************
486  *
487  * Entry fetching
488  *
489  **************************************************************************/
490
491 /*
492  * Figure out what kind of terminal we're dealing with, and then read in
493  * its termcap entry.
494  */
495 static const char *
496 get_termcap_entry(char *userarg)
497 {
498     int rval, errret;
499     char *p;
500     const char *ttype;
501 #if HAVE_GETTTYNAM
502     struct ttyent *t;
503 #else
504     FILE *fp;
505 #endif
506     char *ttypath;
507
508     if (userarg) {
509         ttype = userarg;
510         goto found;
511     }
512
513     /* Try the environment. */
514     if ((ttype = getenv("TERM")) != 0)
515         goto map;
516
517     if ((ttypath = ttyname(STDERR_FILENO)) != 0) {
518         p = _nc_basename(ttypath);
519 #if HAVE_GETTTYNAM
520         /*
521          * We have the 4.3BSD library call getttynam(3); that means
522          * there's an /etc/ttys to look up device-to-type mappings in.
523          * Try ttyname(3); check for dialup or other mapping.
524          */
525         if ((t = getttynam(p))) {
526             ttype = t->ty_type;
527             goto map;
528         }
529 #else
530         if ((fp = fopen("/etc/ttytype", "r")) != 0
531             || (fp = fopen("/etc/ttys", "r")) != 0) {
532             char buffer[BUFSIZ];
533             char *s, *t, *d;
534
535             while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
536                 for (s = buffer, t = d = 0; *s; s++) {
537                     if (isspace(*s))
538                         *s = '\0';
539                     else if (t == 0)
540                         t = s;
541                     else if (d == 0 && s != buffer && s[-1] == '\0')
542                         d = s;
543                 }
544                 if (t != 0 && d != 0 && !strcmp(d, p)) {
545                     ttype = strdup(t);
546                     fclose(fp);
547                     goto map;
548                 }
549             }
550             fclose(fp);
551         }
552 #endif /* HAVE_GETTTYNAM */
553     }
554
555     /* If still undefined, use "unknown". */
556     ttype = "unknown";
557
558   map:ttype = mapped(ttype);
559
560     /*
561      * If not a path, remove TERMCAP from the environment so we get a
562      * real entry from /etc/termcap.  This prevents us from being fooled
563      * by out of date stuff in the environment.
564      */
565   found:if ((p = getenv("TERMCAP")) != 0 && *p != '/') {
566         /* 'unsetenv("TERMCAP")' is not portable.
567          * The 'environ' array is better.
568          */
569         int n;
570         for (n = 0; environ[n] != 0; n++) {
571             if (!strncmp("TERMCAP=", environ[n], 8)) {
572                 while ((environ[n] = environ[n + 1]) != 0) {
573                     n++;
574                 }
575                 break;
576             }
577         }
578     }
579
580     /*
581      * ttype now contains a pointer to the type of the terminal.
582      * If the first character is '?', ask the user.
583      */
584     if (ttype[0] == '?') {
585         if (ttype[1] != '\0')
586             ttype = askuser(ttype + 1);
587         else
588             ttype = askuser(0);
589     }
590     /* Find the terminfo entry.  If it doesn't exist, ask the user. */
591     while ((rval = setupterm((NCURSES_CONST char *) ttype, STDOUT_FILENO,
592                              &errret)) != OK) {
593         if (errret == 0) {
594             (void) fprintf(stderr, "tset: unknown terminal type %s\n",
595                            ttype);
596             ttype = 0;
597         } else {
598             (void) fprintf(stderr,
599                            "tset: can't initialize terminal type %s (error %d)\n",
600                            ttype, errret);
601             ttype = 0;
602         }
603         ttype = askuser(ttype);
604     }
605 #if BROKEN_LINKER
606     tgetflag("am");             /* force lib_termcap.o to be linked for 'ospeed' */
607 #endif
608     return (ttype);
609 }
610
611 /**************************************************************************
612  *
613  * Mode-setting logic
614  *
615  **************************************************************************/
616
617 /* some BSD systems have these built in, some systems are missing
618  * one or more definitions. The safest solution is to override.
619  */
620 #undef CEOF
621 #undef CERASE
622 #undef CINTR
623 #undef CKILL
624 #undef CLNEXT
625 #undef CRPRNT
626 #undef CQUIT
627 #undef CSTART
628 #undef CSTOP
629 #undef CSUSP
630
631 /* control-character defaults */
632 #define CEOF    CTRL('D')
633 #define CERASE  CTRL('H')
634 #define CINTR   127             /* ^? */
635 #define CKILL   CTRL('U')
636 #define CLNEXT  CTRL('v')
637 #define CRPRNT  CTRL('r')
638 #define CQUIT   CTRL('\\')
639 #define CSTART  CTRL('Q')
640 #define CSTOP   CTRL('S')
641 #define CSUSP   CTRL('Z')
642
643 #define CHK(val, dft)   ((int)val <= 0 ? dft : val)
644
645 static bool set_tabs(void);
646
647 /*
648  * Reset the terminal mode bits to a sensible state.  Very useful after
649  * a child program dies in raw mode.
650  */
651 static void
652 reset_mode(void)
653 {
654 #ifdef TERMIOS
655     tcgetattr(STDERR_FILENO, &mode);
656 #else
657     stty(STDERR_FILENO, &mode);
658 #endif
659
660 #ifdef TERMIOS
661 #if defined(VDISCARD) && defined(CDISCARD)
662     mode.c_cc[VDISCARD] = CHK(mode.c_cc[VDISCARD], CDISCARD);
663 #endif
664     mode.c_cc[VEOF] = CHK(mode.c_cc[VEOF], CEOF);
665     mode.c_cc[VERASE] = CHK(mode.c_cc[VERASE], CERASE);
666 #if defined(VFLUSH) && defined(CFLUSH)
667     mode.c_cc[VFLUSH] = CHK(mode.c_cc[VFLUSH], CFLUSH);
668 #endif
669     mode.c_cc[VINTR] = CHK(mode.c_cc[VINTR], CINTR);
670     mode.c_cc[VKILL] = CHK(mode.c_cc[VKILL], CKILL);
671 #if defined(VLNEXT) && defined(CLNEXT)
672     mode.c_cc[VLNEXT] = CHK(mode.c_cc[VLNEXT], CLNEXT);
673 #endif
674     mode.c_cc[VQUIT] = CHK(mode.c_cc[VQUIT], CQUIT);
675 #if defined(VREPRINT) && defined(CRPRNT)
676     mode.c_cc[VREPRINT] = CHK(mode.c_cc[VREPRINT], CRPRNT);
677 #endif
678 #if defined(VSTART) && defined(CSTART)
679     mode.c_cc[VSTART] = CHK(mode.c_cc[VSTART], CSTART);
680 #endif
681 #if defined(VSTOP) && defined(CSTOP)
682     mode.c_cc[VSTOP] = CHK(mode.c_cc[VSTOP], CSTOP);
683 #endif
684 #if defined(VSUSP) && defined(CSUSP)
685     mode.c_cc[VSUSP] = CHK(mode.c_cc[VSUSP], CSUSP);
686 #endif
687 #if defined(VWERASE) && defined(CWERASE)
688     mode.c_cc[VWERASE] = CHK(mode.c_cc[VWERASE], CWERASE);
689 #endif
690
691     mode.c_iflag &= ~(IGNBRK | PARMRK | INPCK | ISTRIP | INLCR | IGNCR
692 #ifdef IUCLC
693                       | IUCLC
694 #endif
695 #ifdef IXANY
696                       | IXANY
697 #endif
698                       | IXOFF);
699
700     mode.c_iflag |= (BRKINT | IGNPAR | ICRNL | IXON
701 #ifdef IMAXBEL
702                      | IMAXBEL
703 #endif
704         );
705
706     mode.c_oflag &= ~(0
707 #ifdef OLCUC
708                       | OLCUC
709 #endif
710 #ifdef OCRNL
711                       | OCRNL
712 #endif
713 #ifdef ONOCR
714                       | ONOCR
715 #endif
716 #ifdef ONLRET
717                       | ONLRET
718 #endif
719 #ifdef OFILL
720                       | OFILL
721 #endif
722 #ifdef OFDEL
723                       | OFDEL
724 #endif
725 #ifdef NLDLY
726                       | NLDLY | CRDLY | TABDLY | BSDLY | VTDLY | FFDLY
727 #endif
728         );
729
730     mode.c_oflag |= (OPOST
731 #ifdef ONLCR
732                      | ONLCR
733 #endif
734         );
735
736     mode.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | CLOCAL);
737     mode.c_cflag |= (CS8 | CREAD);
738     mode.c_lflag &= ~(ECHONL | NOFLSH
739 #ifdef TOSTOP
740                       | TOSTOP
741 #endif
742 #ifdef ECHOPTR
743                       | ECHOPRT
744 #endif
745 #ifdef XCASE
746                       | XCASE
747 #endif
748         );
749
750     mode.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK
751 #ifdef ECHOCTL
752                      | ECHOCTL
753 #endif
754 #ifdef ECHOKE
755                      | ECHOKE
756 #endif
757         );
758 #endif
759
760 #ifdef TERMIOS
761     tcsetattr(STDERR_FILENO, TCSADRAIN, &mode);
762 #else
763     stty(STDERR_FILENO, &mode);
764 #endif
765 }
766
767 /*
768  * Returns a "good" value for the erase character.  This is loosely based on
769  * the BSD4.4 logic.
770  */
771 #ifdef TERMIOS
772 static int
773 default_erase(void)
774 {
775     int result;
776
777     if (over_strike
778         && key_backspace != 0
779         && strlen(key_backspace) == 1)
780         result = key_backspace[0];
781     else
782         result = CERASE;
783
784     return result;
785 }
786 #endif
787
788 /*
789  * Update the values of the erase, interrupt, and kill characters in 'mode'.
790  *
791  * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase
792  * characters if they're unset, or if we specify them as options.  This differs
793  * from BSD 4.4 tset, which always sets erase.
794  */
795 static void
796 set_control_chars(void)
797 {
798 #ifdef TERMIOS
799     if (mode.c_cc[VERASE] == 0 || terasechar >= 0)
800         mode.c_cc[VERASE] = terasechar >= 0 ? terasechar : default_erase();
801
802     if (mode.c_cc[VINTR] == 0 || intrchar >= 0)
803         mode.c_cc[VINTR] = intrchar >= 0 ? intrchar : CINTR;
804
805     if (mode.c_cc[VKILL] == 0 || tkillchar >= 0)
806         mode.c_cc[VKILL] = tkillchar >= 0 ? tkillchar : CKILL;
807 #endif
808 }
809
810 /*
811  * Set up various conversions in 'mode', including parity, tabs, returns,
812  * echo, and case, according to the termcap entry.  If the program we're
813  * running was named with a leading upper-case character, map external
814  * uppercase to internal lowercase.
815  */
816 static void
817 set_conversions(void)
818 {
819 #ifdef __OBSOLETE__
820     /*
821      * Conversion logic for some *really* ancient terminal glitches,
822      * not supported in terminfo.  Left here for succeeding generations
823      * to marvel at.
824      */
825     if (tgetflag("UC")) {
826 #ifdef IUCLC
827         mode.c_iflag |= IUCLC;
828         mode.c_oflag |= OLCUC;
829 #endif
830     } else if (tgetflag("LC")) {
831 #ifdef IUCLC
832         mode.c_iflag &= ~IUCLC;
833         mode.c_oflag &= ~OLCUC;
834 #endif
835     }
836     mode.c_iflag &= ~(PARMRK | INPCK);
837     mode.c_lflag |= ICANON;
838     if (tgetflag("EP")) {
839         mode.c_cflag |= PARENB;
840         mode.c_cflag &= ~PARODD;
841     }
842     if (tgetflag("OP")) {
843         mode.c_cflag |= PARENB;
844         mode.c_cflag |= PARODD;
845     }
846 #endif /* __OBSOLETE__ */
847
848 #ifdef TERMIOS
849 #ifdef ONLCR
850     mode.c_oflag |= ONLCR;
851 #endif
852     mode.c_iflag |= ICRNL;
853     mode.c_lflag |= ECHO;
854 #ifdef OXTABS
855     mode.c_oflag |= OXTABS;
856 #endif /* OXTABS */
857
858     /* test used to be tgetflag("NL") */
859     if (newline != (char *) 0 && newline[0] == '\n' && !newline[1]) {
860         /* Newline, not linefeed. */
861 #ifdef ONLCR
862         mode.c_oflag &= ~ONLCR;
863 #endif
864         mode.c_iflag &= ~ICRNL;
865     }
866 #ifdef __OBSOLETE__
867     if (tgetflag("HD"))         /* Half duplex. */
868         mode.c_lflag &= ~ECHO;
869 #endif /* __OBSOLETE__ */
870 #ifdef OXTABS
871     /* test used to be tgetflag("pt") */
872     if (has_hardware_tabs)      /* Print tabs. */
873         mode.c_oflag &= ~OXTABS;
874 #endif /* OXTABS */
875     mode.c_lflag |= (ECHOE | ECHOK);
876 #endif
877 }
878
879 /* Output startup string. */
880 static void
881 set_init(void)
882 {
883     char *p;
884     bool settle;
885
886 #ifdef __OBSOLETE__
887     if (pad_char != (char *) 0) /* Get/set pad character. */
888         PC = pad_char[0];
889 #endif /* OBSOLETE */
890
891 #ifdef TAB3
892     if (oldmode.c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
893         oldmode.c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
894         tcsetattr(STDERR_FILENO, TCSADRAIN, &oldmode);
895     }
896 #endif
897     settle = set_tabs();
898
899     if (isreset) {
900         if ((p = reset_1string) != 0) {
901             tputs(p, 0, outc);
902             settle = TRUE;
903         }
904         if ((p = reset_2string) != 0) {
905             tputs(p, 0, outc);
906             settle = TRUE;
907         }
908         /* What about rf, rs3, as per terminfo man page? */
909         /* also might be nice to send rmacs, rmul, rmm */
910         if ((p = reset_file) != 0
911             || (p = init_file) != 0) {
912             cat(p);
913             settle = TRUE;
914         }
915     }
916
917     if (settle) {
918         (void) putc('\r', stderr);
919         (void) fflush(stderr);
920         (void) napms(1000);     /* Settle the terminal. */
921     }
922 }
923
924 /*
925  * Set the hardware tabs on the terminal, using the ct (clear all tabs),
926  * st (set one tab) and ch (horizontal cursor addressing) capabilities.
927  * This is done before if and is, so they can patch in case we blow this.
928  * Return TRUE if we set any tab stops, FALSE if not.
929  */
930 static bool
931 set_tabs()
932 {
933     if (set_tab && clear_all_tabs) {
934         int c;
935
936         (void) putc('\r', stderr);      /* Force to left margin. */
937         tputs(clear_all_tabs, 0, outc);
938
939         for (c = 8; c < tcolumns; c += 8) {
940             /* Get to the right column.  In BSD tset, this
941              * used to try a bunch of half-clever things
942              * with cup and hpa, for an average saving of
943              * somewhat less than two character times per
944              * tab stop, less that .01 sec at 2400cps. We
945              * lost all this cruft because it seemed to be
946              * introducing some odd bugs.
947              * ----------12345678----------- */
948             (void) fputs("        ", stderr);
949             tputs(set_tab, 0, outc);
950         }
951         putc('\r', stderr);
952         return (TRUE);
953     }
954     return (FALSE);
955 }
956
957 /**************************************************************************
958  *
959  * Main sequence
960  *
961  **************************************************************************/
962
963 /*
964  * Tell the user if a control key has been changed from the default value.
965  */
966 #ifdef TERMIOS
967 static void
968 report(const char *name, int which, unsigned def)
969 {
970     unsigned older, newer;
971     char *p;
972
973     newer = mode.c_cc[which];
974     older = oldmode.c_cc[which];
975
976     if (older == newer && older == def)
977         return;
978
979     (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
980
981     /*
982      * Check 'delete' before 'backspace', since the key_backspace value
983      * is ambiguous.
984      */
985     if (newer == 0177)
986         (void) fprintf(stderr, "delete.\n");
987     else if ((p = key_backspace) != 0
988              && newer == (unsigned char) p[0]
989              && p[1] == '\0')
990         (void) fprintf(stderr, "backspace.\n");
991     else if (newer < 040) {
992         newer ^= 0100;
993         (void) fprintf(stderr, "control-%c (^%c).\n", newer, newer);
994     } else
995         (void) fprintf(stderr, "%c.\n", newer);
996 }
997 #endif
998
999 /*
1000  * Convert the obsolete argument forms into something that getopt can handle.
1001  * This means that -e, -i and -k get default arguments supplied for them.
1002  */
1003 static void
1004 obsolete(char **argv)
1005 {
1006     for (; *argv; ++argv) {
1007         char *parm = argv[0];
1008
1009         if (parm[0] == '-' && parm[1] == '\0') {
1010             argv[0] = strdup("-q");
1011             continue;
1012         }
1013
1014         if ((parm[0] != '-')
1015             || (argv[1] && argv[1][0] != '-')
1016             || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
1017             || (parm[2] != '\0'))
1018             continue;
1019         switch (argv[0][1]) {
1020         case 'e':
1021             argv[0] = strdup("-e^H");
1022             break;
1023         case 'i':
1024             argv[0] = strdup("-i^C");
1025             break;
1026         case 'k':
1027             argv[0] = strdup("-k^U");
1028             break;
1029         }
1030     }
1031 }
1032
1033 static void
1034 usage(const char *pname)
1035 {
1036     (void) fprintf(stderr,
1037                    "usage: %s [-IQVrs] [-] [-e ch] [-i ch] [-k ch] [-m mapping] [terminal]\n", pname);
1038     exit(EXIT_FAILURE);
1039 }
1040
1041 static char
1042 arg_to_char(void)
1043 {
1044     return (optarg[0] == '^' && optarg[1] != '\0')
1045         ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
1046         : optarg[0];
1047 }
1048
1049 int
1050 main(int argc, char **argv)
1051 {
1052 #if defined(TIOCGWINSZ) && defined(TIOCSWINSZ)
1053     struct winsize win;
1054 #endif
1055     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
1056     const char *p;
1057     const char *ttype;
1058
1059     if (GET_TTY(STDERR_FILENO, &mode) < 0)
1060         failed("standard error");
1061     oldmode = mode;
1062 #ifdef TERMIOS
1063     ospeed = cfgetospeed(&mode);
1064 #else
1065     ospeed = mode.sg_ospeed;
1066 #endif
1067
1068     p = _nc_basename(*argv);
1069     if (!strcmp(p, PROG_RESET)) {
1070         isreset = TRUE;
1071         reset_mode();
1072     }
1073
1074     obsolete(argv);
1075     noinit = noset = quiet = Sflag = sflag = showterm = 0;
1076     while ((ch = getopt(argc, argv, "a:d:e:Ii:k:m:np:qQSrsV")) != EOF) {
1077         switch (ch) {
1078         case 'q':               /* display term only */
1079             noset = 1;
1080             break;
1081         case 'a':               /* OBSOLETE: map identifier to type */
1082             add_mapping("arpanet", optarg);
1083             break;
1084         case 'd':               /* OBSOLETE: map identifier to type */
1085             add_mapping("dialup", optarg);
1086             break;
1087         case 'e':               /* erase character */
1088             terasechar = arg_to_char();
1089             break;
1090         case 'I':               /* no initialization strings */
1091             noinit = 1;
1092             break;
1093         case 'i':               /* interrupt character */
1094             intrchar = arg_to_char();
1095             break;
1096         case 'k':               /* kill character */
1097             tkillchar = arg_to_char();
1098             break;
1099         case 'm':               /* map identifier to type */
1100             add_mapping(0, optarg);
1101             break;
1102         case 'n':               /* OBSOLETE: set new tty driver */
1103             break;
1104         case 'p':               /* OBSOLETE: map identifier to type */
1105             add_mapping("plugboard", optarg);
1106             break;
1107         case 'Q':               /* don't output control key settings */
1108             quiet = 1;
1109             break;
1110         case 'S':               /* OBSOLETE: output TERM & TERMCAP */
1111             Sflag = 1;
1112             break;
1113         case 'r':               /* display term on stderr */
1114             showterm = 1;
1115             break;
1116         case 's':               /* output TERM set command */
1117             sflag = 1;
1118             break;
1119         case 'V':
1120             puts(curses_version());
1121             return EXIT_SUCCESS;
1122         case '?':
1123         default:
1124             usage(*argv);
1125         }
1126     }
1127     argc -= optind;
1128     argv += optind;
1129
1130     if (argc > 1)
1131         usage(*argv);
1132
1133     ttype = get_termcap_entry(*argv);
1134
1135     if (!noset) {
1136         tcolumns = columns;
1137         tlines = lines;
1138
1139 #if defined(TIOCGWINSZ) && defined(TIOCSWINSZ)
1140         /* Set window size */
1141         (void) ioctl(STDERR_FILENO, TIOCGWINSZ, &win);
1142         if (win.ws_row == 0 && win.ws_col == 0 &&
1143             tlines > 0 && tcolumns > 0) {
1144             win.ws_row = tlines;
1145             win.ws_col = tcolumns;
1146             (void) ioctl(STDERR_FILENO, TIOCSWINSZ, &win);
1147         }
1148 #endif
1149         set_control_chars();
1150         set_conversions();
1151
1152         if (!noinit)
1153             set_init();
1154
1155         /* Set the modes if they've changed. */
1156         if (memcmp(&mode, &oldmode, sizeof(mode))) {
1157 #ifdef TERMIOS
1158             tcsetattr(STDERR_FILENO, TCSADRAIN, &mode);
1159 #else
1160             stty(STDERR_FILENO, &mode);
1161 #endif
1162         }
1163     }
1164
1165     /* Get the terminal name from the entry. */
1166     ttype = _nc_first_name(cur_term->type.term_names);
1167
1168     if (noset)
1169         (void) printf("%s\n", ttype);
1170     else {
1171         if (showterm)
1172             (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
1173         /*
1174          * If erase, kill and interrupt characters could have been
1175          * modified and not -Q, display the changes.
1176          */
1177 #ifdef TERMIOS
1178         if (!quiet) {
1179             report("Erase", VERASE, CERASE);
1180             report("Kill", VKILL, CINTR);
1181             report("Interrupt", VINTR, CKILL);
1182         }
1183 #endif
1184     }
1185
1186     if (Sflag)
1187         err("The -S option is not supported under terminfo.");
1188
1189     if (sflag) {
1190         /*
1191          * Figure out what shell we're using.  A hack, we look for an
1192          * environmental variable SHELL ending in "csh".
1193          */
1194         if ((p = getenv("SHELL")) != 0
1195             && !strcmp(p + strlen(p) - 3, "csh"))
1196             p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
1197         else
1198             p = "TERM=%s;\n";
1199         (void) printf(p, ttype);
1200     }
1201
1202     return EXIT_SUCCESS;
1203 }
1204
1205 /* tset.c ends here */