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