ncurses 6.2 - patch 20210925
[ncurses.git] / progs / tput.c
1 /****************************************************************************
2  * Copyright 2018-2020,2021 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey                        1996-on                 *
34  ****************************************************************************/
35
36 /*
37  * tput.c -- shellscript access to terminal capabilities
38  *
39  * by Eric S. Raymond <esr@snark.thyrsus.com>, portions based on code from
40  * Ross Ridge's mytinfo package.
41  */
42
43 #include <tparm_type.h>
44 #include <clear_cmd.h>
45 #include <reset_cmd.h>
46
47 #include <transform.h>
48 #include <tty_settings.h>
49
50 MODULE_ID("$Id: tput.c,v 1.94 2021/09/19 00:06:44 tom Exp $")
51
52 #define PUTS(s)         fputs(s, stdout)
53
54 const char *_nc_progname = "tput";
55
56 static char *prg_name;
57 static bool is_init = FALSE;
58 static bool is_reset = FALSE;
59 static bool is_clear = FALSE;
60
61 static GCC_NORETURN void
62 quit(int status, const char *fmt, ...)
63 {
64     va_list argp;
65
66     va_start(argp, fmt);
67     fprintf(stderr, "%s: ", prg_name);
68     vfprintf(stderr, fmt, argp);
69     fprintf(stderr, "\n");
70     va_end(argp);
71     ExitProgram(status);
72 }
73
74 static GCC_NORETURN void
75 usage(void)
76 {
77 #define KEEP(s) s "\n"
78     static const char msg[] =
79     {
80         KEEP("")
81         KEEP("Options:")
82         KEEP("  -S <<       read commands from standard input")
83         KEEP("  -T TERM     use this instead of $TERM")
84         KEEP("  -V          print curses-version")
85         KEEP("  -x          do not try to clear scrollback")
86         KEEP("")
87         KEEP("Commands:")
88         KEEP("  clear       clear the screen")
89         KEEP("  init        initialize the terminal")
90         KEEP("  reset       reinitialize the terminal")
91         KEEP("  capname     unlike clear/init/reset, print value for capability \"capname\"")
92     };
93 #undef KEEP
94     (void) fprintf(stderr, "Usage: %s [options] [command]\n", prg_name);
95     fputs(msg, stderr);
96     ExitProgram(ErrUsage);
97 }
98
99 static char *
100 check_aliases(char *name, bool program)
101 {
102     static char my_init[] = "init";
103     static char my_reset[] = "reset";
104     static char my_clear[] = "clear";
105
106     char *result = name;
107     if ((is_init = same_program(name, program ? PROG_INIT : my_init)))
108         result = my_init;
109     if ((is_reset = same_program(name, program ? PROG_RESET : my_reset)))
110         result = my_reset;
111     if ((is_clear = same_program(name, program ? PROG_CLEAR : my_clear)))
112         result = my_clear;
113     return result;
114 }
115
116 static int
117 exit_code(int token, int value)
118 {
119     int result = 99;
120
121     switch (token) {
122     case BOOLEAN:
123         result = !value;        /* TRUE=0, FALSE=1 */
124         break;
125     case NUMBER:
126         result = 0;             /* always zero */
127         break;
128     case STRING:
129         result = value;         /* 0=normal, 1=missing */
130         break;
131     }
132     return result;
133 }
134
135 /*
136  * Returns nonzero on error.
137  */
138 static int
139 tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used)
140 {
141     NCURSES_CONST char *name;
142     char *s;
143     int status;
144 #if !PURE_TERMINFO
145     bool termcap = FALSE;
146 #endif
147
148     name = check_aliases(argv[0], FALSE);
149     *used = 1;
150     if (is_reset || is_init) {
151         TTY oldmode;
152
153         int terasechar = -1;    /* new erase character */
154         int intrchar = -1;      /* new interrupt character */
155         int tkillchar = -1;     /* new kill character */
156
157         if (is_reset) {
158             reset_start(stdout, TRUE, FALSE);
159             reset_tty_settings(fd, settings);
160         } else {
161             reset_start(stdout, FALSE, TRUE);
162         }
163
164 #if HAVE_SIZECHANGE
165         set_window_size(fd, &lines, &columns);
166 #else
167         (void) fd;
168 #endif
169         set_control_chars(settings, terasechar, intrchar, tkillchar);
170         set_conversions(settings);
171         if (send_init_strings(fd, &oldmode)) {
172             reset_flush();
173         }
174
175         update_tty_settings(&oldmode, settings);
176         return 0;
177     }
178
179     if (strcmp(name, "longname") == 0) {
180         PUTS(longname());
181         return 0;
182     }
183 #if !PURE_TERMINFO
184   retry:
185 #endif
186     if (strcmp(name, "clear") == 0) {
187         return (clear_cmd(opt_x) == ERR) ? ErrUsage : 0;
188     } else if ((status = tigetflag(name)) != -1) {
189         return exit_code(BOOLEAN, status);
190     } else if ((status = tigetnum(name)) != CANCELLED_NUMERIC) {
191         (void) printf("%d\n", status);
192         return exit_code(NUMBER, 0);
193     } else if ((s = tigetstr(name)) == CANCELLED_STRING) {
194 #if !PURE_TERMINFO
195         if (!termcap) {
196             const struct name_table_entry *np;
197
198             termcap = TRUE;
199             if ((np = _nc_find_entry(name, _nc_get_hash_table(termcap))) != 0) {
200                 switch (np->nte_type) {
201                 case BOOLEAN:
202                     name = boolnames[np->nte_index];
203                     break;
204
205                 case NUMBER:
206                     name = numnames[np->nte_index];
207                     break;
208
209                 case STRING:
210                     name = strnames[np->nte_index];
211                     break;
212                 }
213                 goto retry;
214             }
215         }
216 #endif
217         quit(ErrCapName, "unknown terminfo capability '%s'", name);
218     } else if (VALID_STRING(s)) {
219         if (argc > 1) {
220             int k;
221             int analyzed;
222             int popcount;
223             long numbers[1 + NUM_PARM];
224             char *strings[1 + NUM_PARM];
225             char *p_is_s[NUM_PARM];
226             TParams paramType;
227
228             /* Nasty hack time. The tparm function needs to see numeric
229              * parameters as numbers, not as pointers to their string
230              * representations
231              */
232
233             for (k = 1; (k < argc) && (k <= NUM_PARM); k++) {
234                 char *tmp = 0;
235                 strings[k] = argv[k];
236                 numbers[k] = strtol(argv[k], &tmp, 0);
237                 if (tmp == 0 || *tmp != 0)
238                     numbers[k] = 0;
239             }
240             for (k = argc; k <= NUM_PARM; k++) {
241                 numbers[k] = 0;
242                 strings[k] = 0;
243             }
244
245             paramType = tparm_type(name);
246 #if NCURSES_XNAMES
247             /*
248              * If the capability is an extended one, analyze the string.
249              */
250             if (paramType == Numbers) {
251                 struct name_table_entry const *entry_ptr;
252                 entry_ptr = _nc_find_type_entry(name, STRING, FALSE);
253                 if (entry_ptr == NULL) {
254                     paramType = Other;
255                 }
256             }
257 #endif
258
259             popcount = 0;
260             _nc_reset_tparm(NULL);
261             switch (paramType) {
262             case Num_Str:
263                 s = TPARM_2(s, numbers[1], strings[2]);
264                 analyzed = 2;
265                 break;
266             case Num_Str_Str:
267                 s = TPARM_3(s, numbers[1], strings[2], strings[3]);
268                 analyzed = 3;
269                 break;
270             case Numbers:
271                 analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount);
272 #define myParam(n) numbers[n]
273                 s = TIPARM_9(s,
274                              myParam(1),
275                              myParam(2),
276                              myParam(3),
277                              myParam(4),
278                              myParam(5),
279                              myParam(6),
280                              myParam(7),
281                              myParam(8),
282                              myParam(9));
283 #undef myParam
284                 break;
285             case Other:
286                 /* FALLTHRU */
287             default:
288                 analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount);
289 #define myParam(n) (p_is_s[n - 1] != 0 ? ((TPARM_ARG) strings[n]) : numbers[n])
290                 s = TPARM_9(s,
291                             myParam(1),
292                             myParam(2),
293                             myParam(3),
294                             myParam(4),
295                             myParam(5),
296                             myParam(6),
297                             myParam(7),
298                             myParam(8),
299                             myParam(9));
300 #undef myParam
301                 break;
302             }
303             if (analyzed < popcount) {
304                 analyzed = popcount;
305             }
306             *used += analyzed;
307         }
308
309         /* use putp() in order to perform padding */
310         putp(s);
311         return exit_code(STRING, 0);
312     }
313     return exit_code(STRING, 1);
314 }
315
316 int
317 main(int argc, char **argv)
318 {
319     char *term;
320     int errret;
321     bool cmdline = TRUE;
322     int c;
323     char buf[BUFSIZ];
324     int result = 0;
325     int fd;
326     int used;
327     TTY tty_settings;
328     bool opt_x = FALSE;         /* clear scrollback if possible */
329     bool is_alias;
330     bool need_tty;
331
332     prg_name = check_aliases(_nc_rootname(argv[0]), TRUE);
333
334     term = getenv("TERM");
335
336     while ((c = getopt(argc, argv, "ST:Vx")) != -1) {
337         switch (c) {
338         case 'S':
339             cmdline = FALSE;
340             break;
341         case 'T':
342             use_env(FALSE);
343             use_tioctl(TRUE);
344             term = optarg;
345             break;
346         case 'V':
347             puts(curses_version());
348             ExitProgram(EXIT_SUCCESS);
349         case 'x':               /* do not try to clear scrollback */
350             opt_x = TRUE;
351             break;
352         default:
353             usage();
354             /* NOTREACHED */
355         }
356     }
357
358     is_alias = (is_clear || is_reset || is_init);
359     need_tty = ((is_reset || is_init) ||
360                 (optind < argc &&
361                  (!strcmp(argv[optind], "reset") ||
362                   !strcmp(argv[optind], "init"))));
363
364     /*
365      * Modify the argument list to omit the options we processed.
366      */
367     if (is_alias) {
368         if (optind-- < argc) {
369             argc -= optind;
370             argv += optind;
371         }
372         argv[0] = prg_name;
373     } else {
374         argc -= optind;
375         argv += optind;
376     }
377
378     if (term == 0 || *term == '\0')
379         quit(ErrUsage, "No value for $TERM and no -T specified");
380
381     fd = save_tty_settings(&tty_settings, need_tty);
382
383     if (setupterm(term, fd, &errret) != OK && errret <= 0)
384         quit(ErrTermType, "unknown terminal \"%s\"", term);
385
386     if (cmdline) {
387         int code = 0;
388         if ((argc <= 0) && !is_alias)
389             usage();
390         while (argc > 0) {
391             code = tput_cmd(fd, &tty_settings, opt_x, argc, argv, &used);
392             if (code != 0)
393                 break;
394             argc -= used;
395             argv += used;
396         }
397         ExitProgram(code);
398     }
399
400     while (fgets(buf, sizeof(buf), stdin) != 0) {
401         size_t need = strlen(buf);
402         char **argvec = typeCalloc(char *, need + 1);
403         char **argnow;
404         int argnum = 0;
405         char *cp;
406
407         if (argvec == NULL) {
408             quit(ErrSystem(1), strerror(errno));
409         }
410
411         /* split the buffer into tokens */
412         for (cp = buf; *cp; cp++) {
413             if (isspace(UChar(*cp))) {
414                 *cp = '\0';
415             } else if (cp == buf || cp[-1] == '\0') {
416                 argvec[argnum++] = cp;
417                 if (argnum >= (int) need)
418                     break;
419             }
420         }
421
422         argnow = argvec;
423         while (argnum > 0) {
424             int code = tput_cmd(fd, &tty_settings, opt_x, argnum, argnow, &used);
425             if (code != 0) {
426                 if (result == 0)
427                     result = ErrSystem(0);      /* will return value >4 */
428                 ++result;
429             }
430             argnum -= used;
431             argnow += used;
432         }
433         free(argvec);
434     }
435
436     ExitProgram(result);
437 }