xref: /illumos-gate/usr/src/cmd/tput/tput.c (revision 1fa2a66491e7d8ae0be84e7da4da8e812480c710)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright (c) 2012 Gary Mills
26  */
27 
28 /*	Copyright (c) 1988 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 /*
32  *	tput - print terminal attribute
33  *
34  *  return-codes - command line arguments:
35  *	0: ok if boolean capname -> TRUE
36  *	1: for boolean capname -> FALSE
37  *
38  *  return-codes - standard input arguments:
39  *	0: ok; tput for all lines was successful
40  *
41  *  return-codes - both cases:
42  *	2	usage error
43  *	3	bad terminal type given or no terminfo database
44  *	4	unknown capname
45  *	-1	capname is a numeric variable that is not specified in the
46  *		terminfo database(E.g. tpu -T450 lines).
47  *
48  *  tput printfs a value if an INT capname was given; e.g. cols.
49  *	putp's a string if a STRING capname was given; e.g. clear. and
50  *  for BOOLEAN capnames, e.g. hard-copy, just returns the boolean value.
51  */
52 
53 #include <curses.h>
54 #include <term.h>
55 #include <fcntl.h>
56 #include <ctype.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <sys/types.h>
60 #include <unistd.h>
61 #include <locale.h>
62 
63 /* externs from libcurses */
64 extern int tigetnum();
65 
66 static int outputcap(char *cap, int argc, char **argv);
67 static int allnumeric(char *string);
68 static int getpad(char *cap);
69 static void setdelay();
70 static void settabs();
71 static void cat(char *file);
72 static void initterm();
73 static void reset_term();
74 
75 static char *progname;		/* argv[0] */
76 static int CurrentBaudRate;	/* current baud rate */
77 static int reset = 0;		/* called as reset_term */
78 static int fildes = 1;
79 
80 int
81 main(int argc, char **argv)
82 {
83 	int i, std_argc;
84 	char *term = getenv("TERM");
85 	char *cap, std_input = FALSE;
86 	int setuperr;
87 
88 	(void) setlocale(LC_ALL, "");
89 #if !defined(TEXT_DOMAIN)
90 #define	TEXT_DOMAIN "SYS_TEST"
91 #endif
92 	(void) textdomain(TEXT_DOMAIN);
93 
94 	progname = argv[0];
95 
96 	while ((i = getopt(argc, argv, "ST:")) != EOF) {
97 		switch (i) {
98 		case 'T':
99 			fildes = -1;
100 			(void) putenv("LINES=");
101 			(void) putenv("COLUMNS=");
102 			term = optarg;
103 			break;
104 
105 		case 'S':
106 			std_input = TRUE;
107 			break;
108 
109 		case '?':			/* FALLTHROUGH		*/
110 		usage:				/* FALLTHROUGH		*/
111 		default:
112 			(void) fprintf(stderr, gettext(
113 			    "usage:\t%s [-T [term]] capname "
114 			    "[parm argument...]\n"), progname);
115 			(void) fprintf(stderr, gettext("OR:\t%s -S <<\n"),
116 			    progname);
117 			exit(2);
118 		}
119 	}
120 
121 	if (!term || !*term) {
122 		(void) fprintf(stderr,
123 		    gettext("%s: No value for $TERM and no -T specified\n"),
124 		    progname);
125 		exit(2);
126 	}
127 
128 	(void) setupterm(term, fildes, &setuperr);
129 
130 	switch (setuperr) {
131 	case -2:
132 		(void) fprintf(stderr,
133 		    gettext("%s: unreadable terminal descriptor \"%s\"\n"),
134 		    progname, term);
135 		exit(3);
136 		break;
137 
138 	case -1:
139 		(void) fprintf(stderr,
140 		    gettext("%s: no terminfo database\n"), progname);
141 		exit(3);
142 		break;
143 
144 	case 0:
145 		(void) fprintf(stderr,
146 		    gettext("%s: unknown terminal \"%s\"\n"),
147 		    progname, term);
148 		exit(3);
149 	}
150 
151 	reset_shell_mode();
152 
153 	/* command line arguments */
154 	if (!std_input) {
155 		if (argc == optind)
156 			goto usage;
157 
158 		cap = argv[optind++];
159 
160 		if (strcmp(cap, "init") == 0)
161 			initterm();
162 		else if (strcmp(cap, "reset") == 0)
163 			reset_term();
164 		else if (strcmp(cap, "longname") == 0)
165 			(void) printf("%s\n", longname());
166 		else
167 			exit(outputcap(cap, argc, argv));
168 		return (0);
169 	} else {			/* standard input argumets	*/
170 		char buff[128];
171 		char **v;
172 
173 		/* allocate storage for the 'faked' argv[] array	*/
174 		v = (char **)malloc(10 * sizeof (char *));
175 		for (i = 0; i < 10; i++)
176 			v[i] = (char *)malloc(32 * sizeof (char));
177 
178 		while (fgets(buff, sizeof (buff), stdin) != NULL) {
179 			/* read standard input line; skip over empty lines */
180 			if ((std_argc =
181 			    sscanf(buff,
182 			    "%31s %31s %31s %31s %31s %31s %31s %31s "
183 			    "%31s %31s",
184 			    v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7],
185 			    v[8], v[9])) < 1) {
186 				continue;
187 			}
188 
189 			cap = v[0];
190 			optind = 1;
191 
192 			if (strcmp(cap, "init") == 0) {
193 				initterm();
194 			} else if (strcmp(cap, "reset") == 0) {
195 				reset_term();
196 			} else if (strcmp(cap, "longname") == 0) {
197 				(void) printf("%s\n", longname());
198 			} else {
199 				(void) outputcap(cap, std_argc, v);
200 			}
201 			(void) fflush(stdout);
202 		}
203 
204 		return (0);
205 	}
206 }
207 
208 static long parm[9] = {
209     0, 0, 0, 0, 0, 0, 0, 0, 0
210 };
211 
212 static int
213 outputcap(char *cap, int argc, char **argv)
214 {
215 	int parmset = 0;
216 	char *thisstr;
217 	int i;
218 
219 	if ((i = tigetflag(cap)) >= 0)
220 		return (1 - i);
221 
222 	if ((i = tigetnum(cap)) >= -1) {
223 		(void) printf("%d\n", i);
224 		return (0);
225 	}
226 
227 	if ((thisstr = tigetstr(cap)) != (char *)-1) {
228 		if (!thisstr) {
229 			return (1);
230 		}
231 		for (parmset = 0; optind < argc; optind++, parmset++)
232 			if (allnumeric(argv[optind]))
233 				parm[parmset] = atoi(argv[optind]);
234 			else
235 				parm[parmset] = (int)argv[optind];
236 
237 		if (parmset)
238 			putp(tparm(thisstr,
239 			    parm[0], parm[1], parm[2], parm[3],
240 			    parm[4], parm[5], parm[6], parm[7], parm[8]));
241 		else
242 			putp(thisstr);
243 		return (0);
244 	}
245 
246 	(void) fprintf(stderr,
247 	    gettext("%s: unknown terminfo capability '%s'\n"), progname, cap);
248 
249 	exit(4);
250 	/* NOTREACHED */
251 }
252 
253 /*
254  *  The decision as to whether an argument is a number or not is to simply
255  *  look at whether there are any non-digits in the string.
256  */
257 static int
258 allnumeric(char *string)
259 {
260 	if (*string) {
261 		while (*string) {
262 			if (!isdigit(*string++)) {
263 				return (0);
264 			}
265 		}
266 		return (1);
267 	} else {
268 		return (0);
269 	}
270 }
271 
272 /*
273  *  SYSTEM DEPENDENT TERMINAL DELAY TABLES
274  *
275  *	These tables maintain the correspondence between the delays
276  *	defined in terminfo and the delay algorithms in the tty driver
277  *	on the particular systems. For each type of delay, the bits used
278  *	for that delay must be specified, in XXbits, and a table
279  *	must be defined giving correspondences between delays and
280  *	algorithms. Algorithms which are not fixed delays, such
281  *	as dependent on current column or line number, must be
282  *	kludged in some way at this time.
283  *
284  *	Some of this was taken from tset(1).
285  */
286 
287 struct delay
288 {
289     int d_delay;
290     int d_bits;
291 };
292 
293 /* The appropriate speeds for various termio settings. */
294 static int speeds[] = {
295 	0,		/* B0		*/
296 	50,		/* B50		*/
297 	75,		/* B75		*/
298 	110,		/* B110		*/
299 	134,		/* B134		*/
300 	150,		/* B150		*/
301 	200,		/* B200		*/
302 	300,		/* B300		*/
303 	600,		/* B600		*/
304 	1200,		/* B1200	*/
305 	1800,		/* B1800	*/
306 	2400,		/* B2400	*/
307 	4800,		/* B4800	*/
308 	9600,		/* B9600	*/
309 	19200,		/* B19200	*/
310 	38400,		/* B38400	*/
311 	57600,		/* B57600	*/
312 	76800,		/* B76800	*/
313 	115200,		/* B115200	*/
314 	153600,		/* B153600	*/
315 	230400,		/* B230400	*/
316 	307200,		/* B307200	*/
317 	460800,		/* B460800	*/
318 	921600,		/* B921600	*/
319 	1000000,	/* B1000000	*/
320 	1152000,	/* B1152000	*/
321 	1500000,	/* B1500000	*/
322 	2000000,	/* B2000000	*/
323 	2500000,	/* B2500000	*/
324 	3000000,	/* B3000000	*/
325 	3500000,	/* B3500000	*/
326 	4000000,	/* B4000000	*/
327 	0
328 };
329 
330 #if defined(SYSV) || defined(USG)
331 /*	Unix 3.0 on up */
332 
333 /*    Carriage Return delays	*/
334 
335 static int	CRbits = CRDLY;
336 static struct delay	CRdelay[] =
337 {
338 	0,	CR0,
339 	80,	CR1,
340 	100,	CR2,
341 	150,	CR3,
342 	-1
343 };
344 
345 /*	New Line delays	*/
346 
347 static int	NLbits = NLDLY;
348 static struct delay	NLdelay[] =
349 {
350 	0,	NL0,
351 	100,	NL1,
352 	-1
353 };
354 
355 /*	Back Space delays	*/
356 
357 static int	BSbits = BSDLY;
358 static struct delay	BSdelay[] =
359 {
360 	0,	BS0,
361 	50,	BS1,
362 	-1
363 };
364 
365 /*	TaB delays	*/
366 
367 static int	TBbits = TABDLY;
368 static struct delay	TBdelay[] =
369 {
370 	0,	TAB0,
371 	11,	TAB1,		/* special M37 delay */
372 	100,	TAB2,
373 				/* TAB3 is XTABS and not a delay */
374 	-1
375 };
376 
377 /*	Form Feed delays	*/
378 
379 static int	FFbits = FFDLY;
380 static struct delay	FFdelay[] =
381 {
382 	0,	FF0,
383 	2000,	FF1,
384 	-1
385 };
386 
387 #else	/* BSD */
388 
389 /*	Carriage Return delays	*/
390 
391 int	CRbits = CRDELAY;
392 struct delay	CRdelay[] =
393 {
394 	0,	CR0,
395 	9,	CR3,
396 	80,	CR1,
397 	160,	CR2,
398 	-1
399 };
400 
401 /*	New Line delays	*/
402 
403 int	NLbits = NLDELAY;
404 struct delay	NLdelay[] =
405 {
406 	0,	NL0,
407 	66,	NL1,		/* special M37 delay */
408 	100,	NL2,
409 	-1
410 };
411 
412 /*	Tab delays	*/
413 
414 int	TBbits = TBDELAY;
415 struct delay	TBdelay[] =
416 {
417 	0,	TAB0,
418 	11,	TAB1,		/* special M37 delay */
419 	-1
420 };
421 
422 /*	Form Feed delays	*/
423 
424 int	FFbits = VTDELAY;
425 struct delay	FFdelay[] =
426 {
427 	0,	FF0,
428 	2000,	FF1,
429 	-1
430 };
431 #endif	/* BSD */
432 
433 /*
434  *  Initterm, a.k.a. reset_term, does terminal specific initialization. In
435  *  particular, the init_strings from terminfo are output and tabs are
436  *  set, if they aren't hardwired in. Much of this stuff was done by
437  *  the tset(1) program.
438  */
439 
440 /*
441  *  Figure out how many milliseconds of padding the capability cap
442  *  needs and return that number. Padding is stored in the string as "$<n>",
443  *  where n is the number of milliseconds of padding. More than one
444  *  padding string is allowed within the string, although this is unlikely.
445  */
446 
447 static int
448 getpad(char *cap)
449 {
450 	int padding = 0;
451 
452 	/* No padding needed at speeds below padding_baud_rate */
453 	if (padding_baud_rate > CurrentBaudRate || cap == NULL)
454 		return (0);
455 
456 	while (*cap) {
457 		if ((cap[0] == '$') && (cap[1] == '<')) {
458 			cap++;
459 			cap++;
460 			padding += atoi(cap);
461 			while (isdigit (*cap))
462 				cap++;
463 			while (*cap == '.' || *cap == '/' || *cap == '*' ||
464 			    isdigit(*cap))
465 				cap++;
466 			while (*cap == '>')
467 				cap++;
468 		} else {
469 			cap++;
470 		}
471 	}
472 
473 	return (padding);
474 }
475 
476 /*
477  *  Set the appropriate delay bits in the termio structure for
478  *  the given delay.
479  */
480 static void
481 setdelay(delay, delaytable, bits, flags)
482 register int delay;
483 struct delay delaytable[];
484 int bits;
485 #ifdef SYSV
486 tcflag_t *flags;
487 #else	/* SYSV */
488 unsigned short *flags;
489 #endif	/* SYSV */
490 {
491 	register struct delay  *p;
492 	register struct delay  *lastdelay;
493 
494 	/* Clear out the bits, replace with new ones */
495 	*flags &= ~bits;
496 
497 	/* Scan the delay table for first entry with adequate delay */
498 	for (lastdelay = p = delaytable;
499 	    (p -> d_delay >= 0) && (p -> d_delay < delay);
500 	    p++) {
501 		lastdelay = p;
502 	}
503 
504 	/* use last entry if none will do */
505 	*flags |= lastdelay -> d_bits;
506 }
507 
508 /*
509  * Set the hardware tabs on the terminal, using clear_all_tabs,
510  * set_tab, and column_address capabilities. Cursor_address and cursor_right
511  * may also be used, if necessary.
512  * This is done before the init_file and init_3string, so they can patch in
513  * case we blow this.
514  */
515 
516 static void
517 settabs()
518 {
519 	register int c;
520 
521 	/* Do not set tabs if they power up properly. */
522 	if (init_tabs == 8)
523 		return;
524 
525 	if (set_tab) {
526 		/* Force the cursor to be at the left margin. */
527 		if (carriage_return)
528 			putp(carriage_return);
529 		else
530 			(void) putchar('\r');
531 
532 		/* Clear any current tab settings. */
533 		if (clear_all_tabs)
534 			putp(clear_all_tabs);
535 
536 		/* Set the tabs. */
537 		for (c = 8; c < columns; c += 8) {
538 			/* Get to that column. */
539 			(void) fputs("        ", stdout);
540 
541 			/* Set the tab. */
542 			putp(set_tab);
543 		}
544 
545 		/* Get back to the left column. */
546 		if (carriage_return)
547 			putp(carriage_return);
548 		else
549 			(void) putchar('\r');
550 
551 	}
552 }
553 
554 /*
555  *  Copy "file" onto standard output.
556  */
557 
558 static void
559 cat(file)
560 char *file;				/* File to copy. */
561 {
562 	register int fd;			/* File descriptor. */
563 	register ssize_t i;			/* Number characters read. */
564 	char buf[BUFSIZ];			/* Buffer to read into. */
565 
566 	fd = open(file, O_RDONLY);
567 
568 	if (fd < 0) {
569 		perror("Cannot open initialization file");
570 	} else {
571 		while ((i = read(fd, buf, BUFSIZ)) > (ssize_t)0)
572 			(void) write(fileno(stdout), buf, (unsigned)i);
573 		(int)close(fd);
574 	}
575 }
576 
577 /*
578  *  Initialize the terminal.
579  *  Send the initialization strings to the terminal.
580  */
581 
582 static void
583 initterm()
584 {
585 	register int filedes;		/* File descriptor for ioctl's. */
586 #if defined(SYSV) || defined(USG)
587 	struct termio termmode;		/* To hold terminal settings. */
588 	struct termios termmodes;	/* To hold terminal settings. */
589 	int i;
590 	int istermios = -1;
591 #define	GTTY(fd, mode)	ioctl(fd, TCGETA, mode)
592 #define	GTTYS(fd, mode) \
593 	(istermios = ioctl(fd, TCGETS, mode))
594 #define	STTY(fd, mode)	ioctl(fd, TCSETAW, mode)
595 #define	STTYS(fd, mode)	ioctl(fd, TCSETSW, mode)
596 #define	SPEED(mode)	(mode.c_cflag & CBAUD)
597 #define	SPEEDS(mode)	(cfgetospeed(&mode))
598 #define	OFLAG(mode)	mode.c_oflag
599 #else	/* BSD */
600 	struct sgttyb termmode;		/* To hold terminal settings. */
601 #define	GTTY(fd, mode)	gtty(fd, mode)
602 #define	STTY(fd, mode)	stty(fd, mode)
603 #define	SPEED(mode)	(mode.sg_ospeed & 017)
604 #define	OFLAG(mode)	mode.sg_flags
605 #define	TAB3		XTABS
606 #endif
607 
608 	/* Get the terminal settings. */
609 	/* First try standard output, then standard error, */
610 	/* then standard input, then /dev/tty. */
611 #ifdef SYSV
612 	if ((filedes = 1, GTTYS(filedes, &termmodes) < 0) ||
613 	    (filedes = 2, GTTYS(filedes, &termmodes) < 0) ||
614 	    (filedes = 0, GTTYS(filedes, &termmodes) < 0) ||
615 	    (filedes = open("/dev/tty", O_RDWR),
616 	    GTTYS(filedes, &termmodes) < 0)) {
617 #endif	/* SYSV */
618 		if ((filedes = 1, GTTY(filedes, &termmode) == -1) ||
619 		    (filedes = 2, GTTY(filedes, &termmode) == -1) ||
620 		    (filedes = 0, GTTY(filedes, &termmode) == -1) ||
621 		    (filedes = open("/dev/tty", O_RDWR),
622 		    GTTY(filedes, &termmode) == -1)) {
623 			filedes = -1;
624 			CurrentBaudRate = speeds[B1200];
625 		} else
626 			CurrentBaudRate = speeds[SPEED(termmode)];
627 #ifdef SYSV
628 		termmodes.c_lflag = termmode.c_lflag;
629 		termmodes.c_oflag = termmode.c_oflag;
630 		termmodes.c_iflag = termmode.c_iflag;
631 		termmodes.c_cflag = termmode.c_cflag;
632 		for (i = 0; i < NCC; i++)
633 			termmodes.c_cc[i] = termmode.c_cc[i];
634 	} else
635 		CurrentBaudRate = speeds[SPEEDS(termmodes)];
636 #endif	/* SYSV */
637 
638 	if (xon_xoff) {
639 #ifdef SYSV
640 		OFLAG(termmodes) &=
641 		    ~(NLbits | CRbits | BSbits | FFbits | TBbits);
642 #else	/* SYSV */
643 		OFLAG(termmode) &=
644 		    ~(NLbits | CRbits | BSbits | FFbits | TBbits);
645 #endif	/* SYSV */
646 	} else {
647 #ifdef SYSV
648 		setdelay(getpad(carriage_return),
649 		    CRdelay, CRbits, &OFLAG(termmodes));
650 		setdelay(getpad(scroll_forward),
651 		    NLdelay, NLbits, &OFLAG(termmodes));
652 		setdelay(getpad(cursor_left),
653 		    BSdelay, BSbits, &OFLAG(termmodes));
654 		setdelay(getpad(form_feed),
655 		    FFdelay, FFbits, &OFLAG(termmodes));
656 		setdelay(getpad(tab),
657 		    TBdelay, TBbits, &OFLAG(termmodes));
658 #else	/* SYSV */
659 		setdelay(getpad(carriage_return),
660 		    CRdelay, CRbits, &OFLAG(termmode));
661 		setdelay(getpad(scroll_forward),
662 		    NLdelay, NLbits, &OFLAG(termmode));
663 		setdelay(getpad(cursor_left),
664 		    BSdelay, BSbits, &OFLAG(termmode));
665 		setdelay(getpad(form_feed),
666 		    FFdelay, FFbits, &OFLAG(termmode));
667 		setdelay(getpad(tab),
668 		    TBdelay, TBbits, &OFLAG(termmode));
669 #endif	/* SYSV */
670 	}
671 
672 	/* If tabs can be sent to the tty, turn off their expansion. */
673 	if (tab && set_tab || init_tabs == 8) {
674 #ifdef SYSV
675 		OFLAG(termmodes) &= ~(TAB3);
676 #else	/* SYSV */
677 		OFLAG(termmode) &= ~(TAB3);
678 #endif	/* SYSV */
679 	} else {
680 #ifdef SYSV
681 		OFLAG(termmodes) |= TAB3;
682 #else	/* SYSV */
683 		OFLAG(termmode) |= TAB3;
684 #endif	/* SYSV */
685 	}
686 
687 	/* Do the changes to the terminal settings */
688 #ifdef SYSV
689 	if (istermios < 0) {
690 		int i;
691 
692 		termmode.c_lflag = termmodes.c_lflag;
693 		termmode.c_oflag = termmodes.c_oflag;
694 		termmode.c_iflag = termmodes.c_iflag;
695 		termmode.c_cflag = termmodes.c_cflag;
696 		for (i = 0; i < NCC; i++)
697 			termmode.c_cc[i] = termmodes.c_cc[i];
698 		(void) STTY(filedes, &termmode);
699 	} else
700 		(void) STTYS(filedes, &termmodes);
701 
702 #else	/* SYSV */
703 	(void) STTY(filedes, &termmode);
704 #endif	/* SYSV */
705 
706 	/* Send first initialization strings. */
707 	if (init_prog)
708 	(void) system(init_prog);
709 
710 	if (reset && reset_1string) {
711 		putp(reset_1string);
712 	} else if (init_1string) {
713 		putp(init_1string);
714 	}
715 
716 	if (reset && reset_2string) {
717 		putp(reset_2string);
718 	} else if (init_2string) {
719 		putp(init_2string);
720 	}
721 
722 	/* Set up the tabs stops. */
723 	settabs();
724 
725 	/* Send out initializing file. */
726 	if (reset && reset_file) {
727 		cat(reset_file);
728 	} else if (init_file) {
729 		cat(init_file);
730 	}
731 
732 	/* Send final initialization strings. */
733 	if (reset && reset_3string) {
734 		putp(reset_3string);
735 	} else if (init_3string) {
736 		putp(init_3string);
737 	}
738 
739 	if (carriage_return) {
740 		putp(carriage_return);
741 	} else {
742 		(void) putchar('\r');
743 	}
744 
745 	/* Send color initialization strings */
746 
747 	if (orig_colors)
748 		putp(orig_colors);
749 
750 	if (orig_pair)
751 	putp(orig_pair);
752 
753 	/* Let the terminal settle down. */
754 	(void) fflush(stdout);
755 	(void) sleep(1);
756 }
757 
758 static void
759 reset_term()
760 {
761 	reset++;
762 	initterm();
763 }
764