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