1 /**************************************************************************** 2 * Copyright 2018-2023,2024 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.104 2024/04/20 22:20:51 tom Exp $") 51 52 #define PUTS(s) fputs(s, stdout) 53 54 const char *_nc_progname = "tput"; 55 56 static bool opt_v = FALSE; /* quiet, do not show warnings */ 57 static bool opt_x = FALSE; /* clear scrollback if possible */ 58 59 static bool is_init = FALSE; 60 static bool is_reset = FALSE; 61 static bool is_clear = FALSE; 62 63 static GCC_NORETURN void 64 quit(int status, const char *fmt, ...) 65 { 66 va_list argp; 67 68 va_start(argp, fmt); 69 fprintf(stderr, "%s: ", _nc_progname); 70 vfprintf(stderr, fmt, argp); 71 fprintf(stderr, "\n"); 72 va_end(argp); 73 ExitProgram(status); 74 } 75 76 static GCC_NORETURN void 77 usage(const char *optstring) 78 { 79 #define KEEP(s) s "\n" 80 static const char msg[] = 81 { 82 KEEP("") 83 KEEP("Options:") 84 KEEP(" -S << read commands from standard input") 85 KEEP(" -T TERM use this instead of $TERM") 86 KEEP(" -V print curses-version") 87 KEEP(" -v verbose, show warnings") 88 KEEP(" -x do not try to clear scrollback") 89 KEEP("") 90 KEEP("Commands:") 91 KEEP(" clear clear the screen") 92 KEEP(" init initialize the terminal") 93 KEEP(" reset reinitialize the terminal") 94 KEEP(" capname unlike clear/init/reset, print value for capability \"capname\"") 95 }; 96 #undef KEEP 97 (void) fprintf(stderr, "Usage: %s [options] [command]\n", _nc_progname); 98 if (optstring != NULL) { 99 const char *s = msg; 100 while (*s != '\0') { 101 fputc(UChar(*s), stderr); 102 if (!strncmp(s, " -", 3)) { 103 if (strchr(optstring, s[3]) == NULL) 104 s = strchr(s, '\n') + 1; 105 } else if (!strncmp(s, "\n\nC", 3)) 106 break; 107 ++s; 108 } 109 } else { 110 fputs(msg, stderr); 111 } 112 ExitProgram(ErrUsage); 113 } 114 115 static char * 116 check_aliases(char *name, bool program) 117 { 118 static char my_init[] = "init"; 119 static char my_reset[] = "reset"; 120 static char my_clear[] = "clear"; 121 122 char *result = name; 123 if ((is_init = same_program(name, program ? PROG_INIT : my_init))) 124 result = my_init; 125 if ((is_reset = same_program(name, program ? PROG_RESET : my_reset))) 126 result = my_reset; 127 if ((is_clear = same_program(name, program ? PROG_CLEAR : my_clear))) 128 result = my_clear; 129 return result; 130 } 131 132 static int 133 exit_code(int token, int value) 134 { 135 int result = 99; 136 137 switch (token) { 138 case BOOLEAN: 139 result = !value; /* TRUE=0, FALSE=1 */ 140 break; 141 case NUMBER: 142 result = 0; /* always zero */ 143 break; 144 case STRING: 145 result = value; /* 0=normal, 1=missing */ 146 break; 147 } 148 return result; 149 } 150 151 /* 152 * Returns nonzero on error. 153 */ 154 static int 155 tput_cmd(int fd, TTY * settings, int argc, char **argv, int *used) 156 { 157 NCURSES_CONST char *name; 158 char *s; 159 int status; 160 #if !PURE_TERMINFO 161 bool termcap = FALSE; 162 #endif 163 164 name = check_aliases(argv[0], FALSE); 165 *used = 1; 166 if (is_reset || is_init) { 167 TTY oldmode = *settings; 168 169 int terasechar = -1; /* new erase character */ 170 int intrchar = -1; /* new interrupt character */ 171 int tkillchar = -1; /* new kill character */ 172 173 if (is_reset) { 174 reset_start(stdout, TRUE, FALSE); 175 reset_tty_settings(fd, settings, FALSE); 176 } else { 177 reset_start(stdout, FALSE, TRUE); 178 } 179 180 #if HAVE_SIZECHANGE 181 { 182 NCURSES_INT2 my_rows = lines; 183 NCURSES_INT2 my_cols = columns; 184 set_window_size(fd, &my_rows, &my_cols); 185 lines = my_rows; 186 columns = my_cols; 187 } 188 #else 189 (void) fd; 190 #endif 191 set_control_chars(settings, terasechar, intrchar, tkillchar); 192 set_conversions(settings); 193 194 if (send_init_strings(fd, &oldmode)) { 195 reset_flush(); 196 } 197 198 update_tty_settings(&oldmode, settings); 199 return 0; 200 } 201 202 if (strcmp(name, "longname") == 0) { 203 PUTS(longname()); 204 return 0; 205 } 206 #if !PURE_TERMINFO 207 retry: 208 #endif 209 if (strcmp(name, "clear") == 0) { 210 return (clear_cmd(opt_x) == ERR) ? ErrUsage : 0; 211 } else if ((status = tigetflag(name)) != -1) { 212 return exit_code(BOOLEAN, status); 213 } else if ((status = tigetnum(name)) != CANCELLED_NUMERIC) { 214 (void) printf("%d\n", status); 215 return exit_code(NUMBER, 0); 216 } else if ((s = tigetstr(name)) == CANCELLED_STRING) { 217 #if !PURE_TERMINFO 218 if (!termcap) { 219 const struct name_table_entry *np; 220 221 termcap = TRUE; 222 if ((np = _nc_find_entry(name, _nc_get_hash_table(termcap))) != 0) { 223 switch (np->nte_type) { 224 case BOOLEAN: 225 name = boolnames[np->nte_index]; 226 break; 227 228 case NUMBER: 229 name = numnames[np->nte_index]; 230 break; 231 232 case STRING: 233 name = strnames[np->nte_index]; 234 break; 235 } 236 goto retry; 237 } 238 } 239 #endif 240 quit(ErrCapName, "unknown terminfo capability '%s'", name); 241 } else if (VALID_STRING(s)) { 242 if (argc > 1) { 243 int k; 244 int narg; 245 int analyzed; 246 int provided; 247 int popcount; 248 long numbers[1 + NUM_PARM]; 249 char *strings[1 + NUM_PARM]; 250 char *p_is_s[NUM_PARM]; 251 TParams paramType; 252 253 /* Nasty hack time. The tparm function needs to see numeric 254 * parameters as numbers, not as pointers to their string 255 * representations 256 */ 257 258 for (k = 1; (k < argc) && (k <= NUM_PARM); k++) { 259 char *tmp = 0; 260 strings[k] = argv[k]; 261 numbers[k] = strtol(argv[k], &tmp, 0); 262 if (tmp == 0 || *tmp != 0) 263 numbers[k] = 0; 264 } 265 for (k = argc; k <= NUM_PARM; k++) { 266 numbers[k] = 0; 267 strings[k] = 0; 268 } 269 270 paramType = tparm_type(name); 271 #if NCURSES_XNAMES 272 /* 273 * If the capability is an extended one, analyze the string. 274 */ 275 if (paramType == Numbers) { 276 struct name_table_entry const *entry_ptr; 277 entry_ptr = _nc_find_type_entry(name, STRING, FALSE); 278 if (entry_ptr == NULL) { 279 paramType = Other; 280 } 281 } 282 #endif 283 284 popcount = 0; 285 _nc_reset_tparm(NULL); 286 /* 287 * Count the number of numeric parameters which are provided. 288 */ 289 provided = 0; 290 for (narg = 1; narg < argc; ++narg) { 291 char *ending = NULL; 292 long check = strtol(argv[narg], &ending, 0); 293 if (check < 0 || ending == argv[narg] || *ending != '\0') 294 break; 295 provided = narg; 296 } 297 switch (paramType) { 298 case Str: 299 s = TPARM_1(s, strings[1]); 300 analyzed = 1; 301 if (provided == 0 && argc >= 1) 302 provided++; 303 break; 304 case Str_Str: 305 s = TPARM_2(s, strings[1], strings[2]); 306 analyzed = 2; 307 if (provided == 0 && argc >= 1) 308 provided++; 309 if (provided == 1 && argc >= 2) 310 provided++; 311 break; 312 case Num_Str: 313 s = TPARM_2(s, numbers[1], strings[2]); 314 analyzed = 2; 315 if (provided == 1 && argc >= 2) 316 provided++; 317 break; 318 case Num_Str_Str: 319 s = TPARM_3(s, numbers[1], strings[2], strings[3]); 320 analyzed = 3; 321 if (provided == 1 && argc >= 2) 322 provided++; 323 if (provided == 2 && argc >= 3) 324 provided++; 325 break; 326 case Numbers: 327 analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount); 328 #define myParam(n) numbers[n] 329 s = TIPARM_9(s, 330 myParam(1), 331 myParam(2), 332 myParam(3), 333 myParam(4), 334 myParam(5), 335 myParam(6), 336 myParam(7), 337 myParam(8), 338 myParam(9)); 339 #undef myParam 340 break; 341 case Other: 342 /* FALLTHRU */ 343 default: 344 analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount); 345 #define myParam(n) (p_is_s[n - 1] != 0 ? ((TPARM_ARG) strings[n]) : numbers[n]) 346 s = TPARM_9(s, 347 myParam(1), 348 myParam(2), 349 myParam(3), 350 myParam(4), 351 myParam(5), 352 myParam(6), 353 myParam(7), 354 myParam(8), 355 myParam(9)); 356 #undef myParam 357 break; 358 } 359 if (analyzed < popcount) { 360 analyzed = popcount; 361 } 362 if (opt_v && (analyzed != provided)) { 363 fprintf(stderr, "%s: %s parameters for \"%s\"\n", 364 _nc_progname, 365 (analyzed < provided ? "extra" : "missing"), 366 argv[0]); 367 } 368 *used += provided; 369 } 370 371 /* use putp() in order to perform padding */ 372 putp(s); 373 return exit_code(STRING, 0); 374 } 375 return exit_code(STRING, 1); 376 } 377 378 int 379 main(int argc, char **argv) 380 { 381 char *term; 382 int errret; 383 bool cmdline = TRUE; 384 int c; 385 char buf[BUFSIZ]; 386 int result = 0; 387 int fd; 388 int used; 389 TTY old_settings; 390 TTY tty_settings; 391 bool is_alias; 392 bool need_tty; 393 394 _nc_progname = check_aliases(_nc_rootname(argv[0]), TRUE); 395 is_alias = (is_clear || is_reset || is_init); 396 397 term = getenv("TERM"); 398 399 while ((c = getopt(argc, argv, is_alias ? "T:Vvx" : "ST:Vvx")) != -1) { 400 switch (c) { 401 case 'S': 402 cmdline = FALSE; 403 break; 404 case 'T': 405 use_env(FALSE); 406 use_tioctl(TRUE); 407 term = optarg; 408 break; 409 case 'V': 410 puts(curses_version()); 411 ExitProgram(EXIT_SUCCESS); 412 case 'v': /* verbose */ 413 opt_v = TRUE; 414 break; 415 case 'x': /* do not try to clear scrollback */ 416 opt_x = TRUE; 417 break; 418 default: 419 usage(is_alias ? "TVx" : NULL); 420 /* NOTREACHED */ 421 } 422 } 423 424 need_tty = ((is_reset || is_init) || 425 (optind < argc && 426 (!strcmp(argv[optind], "reset") || 427 !strcmp(argv[optind], "init")))); 428 429 /* 430 * Modify the argument list to omit the options we processed. 431 */ 432 if (is_alias) { 433 if (optind-- < argc) { 434 argc -= optind; 435 argv += optind; 436 } 437 argv[0] = strdup(_nc_progname); 438 } else { 439 argc -= optind; 440 argv += optind; 441 } 442 443 if (term == 0 || *term == '\0') 444 quit(ErrUsage, "No value for $TERM and no -T specified"); 445 446 fd = save_tty_settings(&tty_settings, need_tty); 447 old_settings = tty_settings; 448 449 if (setupterm(term, fd, &errret) != OK && errret <= 0) 450 quit(ErrTermType, "unknown terminal \"%s\"", term); 451 452 if (cmdline) { 453 int code = 0; 454 if ((argc <= 0) && !is_alias) 455 usage(NULL); 456 while (argc > 0) { 457 tty_settings = old_settings; 458 code = tput_cmd(fd, &tty_settings, argc, argv, &used); 459 if (code != 0) 460 break; 461 argc -= used; 462 argv += used; 463 } 464 ExitProgram(code); 465 } 466 467 while (fgets(buf, sizeof(buf), stdin) != 0) { 468 size_t need = strlen(buf); 469 char **argvec = typeCalloc(char *, need + 1); 470 char **argnow; 471 int argnum = 0; 472 char *cp; 473 474 if (argvec == NULL) { 475 quit(ErrSystem(1), strerror(errno)); 476 } 477 478 /* split the buffer into tokens */ 479 for (cp = buf; *cp; cp++) { 480 if (isspace(UChar(*cp))) { 481 *cp = '\0'; 482 } else if (cp == buf || cp[-1] == '\0') { 483 argvec[argnum++] = cp; 484 if (argnum >= (int) need) 485 break; 486 } 487 } 488 489 argnow = argvec; 490 while (argnum > 0) { 491 int code; 492 tty_settings = old_settings; 493 code = tput_cmd(fd, &tty_settings, argnum, argnow, &used); 494 if (code != 0) { 495 if (result == 0) 496 result = ErrSystem(0); /* will return value >4 */ 497 ++result; 498 } 499 argnum -= used; 500 argnow += used; 501 } 502 free(argvec); 503 } 504 505 ExitProgram(result); 506 } 507