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