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