xref: /illumos-gate/usr/src/cmd/tput/tput.c (revision b6805bf78d2bbbeeaea8909a05623587b42d58b3)
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,	/*  EXTA,	*/
310 		38400,	/*  EXTB,	*/
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 		0,
320 };
321 
322 #if defined(SYSV) || defined(USG)
323 /*	Unix 3.0 on up */
324 
325 /*    Carriage Return delays	*/
326 
327 static int	CRbits = CRDLY;
328 static struct delay	CRdelay[] =
329 {
330 	0,	CR0,
331 	80,	CR1,
332 	100,	CR2,
333 	150,	CR3,
334 	-1
335 };
336 
337 /*	New Line delays	*/
338 
339 static int	NLbits = NLDLY;
340 static struct delay	NLdelay[] =
341 {
342 	0,	NL0,
343 	100,	NL1,
344 	-1
345 };
346 
347 /*	Back Space delays	*/
348 
349 static int	BSbits = BSDLY;
350 static struct delay	BSdelay[] =
351 {
352 	0,	BS0,
353 	50,	BS1,
354 	-1
355 };
356 
357 /*	TaB delays	*/
358 
359 static int	TBbits = TABDLY;
360 static struct delay	TBdelay[] =
361 {
362 	0,	TAB0,
363 	11,	TAB1,		/* special M37 delay */
364 	100,	TAB2,
365 				/* TAB3 is XTABS and not a delay */
366 	-1
367 };
368 
369 /*	Form Feed delays	*/
370 
371 static int	FFbits = FFDLY;
372 static struct delay	FFdelay[] =
373 {
374 	0,	FF0,
375 	2000,	FF1,
376 	-1
377 };
378 
379 #else	/* BSD */
380 
381 /*	Carriage Return delays	*/
382 
383 int	CRbits = CRDELAY;
384 struct delay	CRdelay[] =
385 {
386 	0,	CR0,
387 	9,	CR3,
388 	80,	CR1,
389 	160,	CR2,
390 	-1
391 };
392 
393 /*	New Line delays	*/
394 
395 int	NLbits = NLDELAY;
396 struct delay	NLdelay[] =
397 {
398 	0,	NL0,
399 	66,	NL1,		/* special M37 delay */
400 	100,	NL2,
401 	-1
402 };
403 
404 /*	Tab delays	*/
405 
406 int	TBbits = TBDELAY;
407 struct delay	TBdelay[] =
408 {
409 	0,	TAB0,
410 	11,	TAB1,		/* special M37 delay */
411 	-1
412 };
413 
414 /*	Form Feed delays	*/
415 
416 int	FFbits = VTDELAY;
417 struct delay	FFdelay[] =
418 {
419 	0,	FF0,
420 	2000,	FF1,
421 	-1
422 };
423 #endif	/* BSD */
424 
425 /*
426  *  Initterm, a.k.a. reset_term, does terminal specific initialization. In
427  *  particular, the init_strings from terminfo are output and tabs are
428  *  set, if they aren't hardwired in. Much of this stuff was done by
429  *  the tset(1) program.
430  */
431 
432 /*
433  *  Figure out how many milliseconds of padding the capability cap
434  *  needs and return that number. Padding is stored in the string as "$<n>",
435  *  where n is the number of milliseconds of padding. More than one
436  *  padding string is allowed within the string, although this is unlikely.
437  */
438 
439 static int
440 getpad(char *cap)
441 {
442 	int padding = 0;
443 
444 	/* No padding needed at speeds below padding_baud_rate */
445 	if (padding_baud_rate > CurrentBaudRate || cap == NULL)
446 		return (0);
447 
448 	while (*cap) {
449 		if ((cap[0] == '$') && (cap[1] == '<')) {
450 			cap++;
451 			cap++;
452 			padding += atoi(cap);
453 			while (isdigit (*cap))
454 				cap++;
455 			while (*cap == '.' || *cap == '/' || *cap == '*' ||
456 			    isdigit(*cap))
457 				cap++;
458 			while (*cap == '>')
459 				cap++;
460 		} else {
461 			cap++;
462 		}
463 	}
464 
465 	return (padding);
466 }
467 
468 /*
469  *  Set the appropriate delay bits in the termio structure for
470  *  the given delay.
471  */
472 static void
473 setdelay(delay, delaytable, bits, flags)
474 register int delay;
475 struct delay delaytable[];
476 int bits;
477 #ifdef SYSV
478 tcflag_t *flags;
479 #else	/* SYSV */
480 unsigned short *flags;
481 #endif	/* SYSV */
482 {
483 	register struct delay  *p;
484 	register struct delay  *lastdelay;
485 
486 	/* Clear out the bits, replace with new ones */
487 	*flags &= ~bits;
488 
489 	/* Scan the delay table for first entry with adequate delay */
490 	for (lastdelay = p = delaytable;
491 	    (p -> d_delay >= 0) && (p -> d_delay < delay);
492 	    p++) {
493 		lastdelay = p;
494 	}
495 
496 	/* use last entry if none will do */
497 	*flags |= lastdelay -> d_bits;
498 }
499 
500 /*
501  * Set the hardware tabs on the terminal, using clear_all_tabs,
502  * set_tab, and column_address capabilities. Cursor_address and cursor_right
503  * may also be used, if necessary.
504  * This is done before the init_file and init_3string, so they can patch in
505  * case we blow this.
506  */
507 
508 static void
509 settabs()
510 {
511 	register int c;
512 
513 	/* Do not set tabs if they power up properly. */
514 	if (init_tabs == 8)
515 		return;
516 
517 	if (set_tab) {
518 		/* Force the cursor to be at the left margin. */
519 		if (carriage_return)
520 			putp(carriage_return);
521 		else
522 			(void) putchar('\r');
523 
524 		/* Clear any current tab settings. */
525 		if (clear_all_tabs)
526 			putp(clear_all_tabs);
527 
528 		/* Set the tabs. */
529 		for (c = 8; c < columns; c += 8) {
530 			/* Get to that column. */
531 			(void) fputs("        ", stdout);
532 
533 			/* Set the tab. */
534 			putp(set_tab);
535 		}
536 
537 		/* Get back to the left column. */
538 		if (carriage_return)
539 			putp(carriage_return);
540 		else
541 			(void) putchar('\r');
542 
543 	}
544 }
545 
546 /*
547  *  Copy "file" onto standard output.
548  */
549 
550 static void
551 cat(file)
552 char *file;				/* File to copy. */
553 {
554 	register int fd;			/* File descriptor. */
555 	register ssize_t i;			/* Number characters read. */
556 	char buf[BUFSIZ];			/* Buffer to read into. */
557 
558 	fd = open(file, O_RDONLY);
559 
560 	if (fd < 0) {
561 		perror("Cannot open initialization file");
562 	} else {
563 		while ((i = read(fd, buf, BUFSIZ)) > (ssize_t)0)
564 			(void) write(fileno(stdout), buf, (unsigned)i);
565 		(int)close(fd);
566 	}
567 }
568 
569 /*
570  *  Initialize the terminal.
571  *  Send the initialization strings to the terminal.
572  */
573 
574 static void
575 initterm()
576 {
577 	register int filedes;		/* File descriptor for ioctl's. */
578 #if defined(SYSV) || defined(USG)
579 	struct termio termmode;		/* To hold terminal settings. */
580 	struct termios termmodes;	/* To hold terminal settings. */
581 	int i;
582 	int istermios = -1;
583 #define	GTTY(fd, mode)	ioctl(fd, TCGETA, mode)
584 #define	GTTYS(fd, mode) \
585 	(istermios = ioctl(fd, TCGETS, mode))
586 #define	STTY(fd, mode)	ioctl(fd, TCSETAW, mode)
587 #define	STTYS(fd, mode)	ioctl(fd, TCSETSW, mode)
588 #define	SPEED(mode)	(mode.c_cflag & CBAUD)
589 #define	SPEEDS(mode)	(cfgetospeed(&mode))
590 #define	OFLAG(mode)	mode.c_oflag
591 #else	/* BSD */
592 	struct sgttyb termmode;		/* To hold terminal settings. */
593 #define	GTTY(fd, mode)	gtty(fd, mode)
594 #define	STTY(fd, mode)	stty(fd, mode)
595 #define	SPEED(mode)	(mode.sg_ospeed & 017)
596 #define	OFLAG(mode)	mode.sg_flags
597 #define	TAB3		XTABS
598 #endif
599 
600 	/* Get the terminal settings. */
601 	/* First try standard output, then standard error, */
602 	/* then standard input, then /dev/tty. */
603 #ifdef SYSV
604 	if ((filedes = 1, GTTYS(filedes, &termmodes) < 0) ||
605 	    (filedes = 2, GTTYS(filedes, &termmodes) < 0) ||
606 	    (filedes = 0, GTTYS(filedes, &termmodes) < 0) ||
607 	    (filedes = open("/dev/tty", O_RDWR),
608 	    GTTYS(filedes, &termmodes) < 0)) {
609 #endif	/* SYSV */
610 		if ((filedes = 1, GTTY(filedes, &termmode) == -1) ||
611 		    (filedes = 2, GTTY(filedes, &termmode) == -1) ||
612 		    (filedes = 0, GTTY(filedes, &termmode) == -1) ||
613 		    (filedes = open("/dev/tty", O_RDWR),
614 		    GTTY(filedes, &termmode) == -1)) {
615 			filedes = -1;
616 			CurrentBaudRate = speeds[B1200];
617 		} else
618 			CurrentBaudRate = speeds[SPEED(termmode)];
619 #ifdef SYSV
620 		termmodes.c_lflag = termmode.c_lflag;
621 		termmodes.c_oflag = termmode.c_oflag;
622 		termmodes.c_iflag = termmode.c_iflag;
623 		termmodes.c_cflag = termmode.c_cflag;
624 		for (i = 0; i < NCC; i++)
625 			termmodes.c_cc[i] = termmode.c_cc[i];
626 	} else
627 		CurrentBaudRate = speeds[SPEEDS(termmodes)];
628 #endif	/* SYSV */
629 
630 	if (xon_xoff) {
631 #ifdef SYSV
632 		OFLAG(termmodes) &=
633 		    ~(NLbits | CRbits | BSbits | FFbits | TBbits);
634 #else	/* SYSV */
635 		OFLAG(termmode) &=
636 		    ~(NLbits | CRbits | BSbits | FFbits | TBbits);
637 #endif	/* SYSV */
638 	} else {
639 #ifdef SYSV
640 		setdelay(getpad(carriage_return),
641 		    CRdelay, CRbits, &OFLAG(termmodes));
642 		setdelay(getpad(scroll_forward),
643 		    NLdelay, NLbits, &OFLAG(termmodes));
644 		setdelay(getpad(cursor_left),
645 		    BSdelay, BSbits, &OFLAG(termmodes));
646 		setdelay(getpad(form_feed),
647 		    FFdelay, FFbits, &OFLAG(termmodes));
648 		setdelay(getpad(tab),
649 		    TBdelay, TBbits, &OFLAG(termmodes));
650 #else	/* SYSV */
651 		setdelay(getpad(carriage_return),
652 		    CRdelay, CRbits, &OFLAG(termmode));
653 		setdelay(getpad(scroll_forward),
654 		    NLdelay, NLbits, &OFLAG(termmode));
655 		setdelay(getpad(cursor_left),
656 		    BSdelay, BSbits, &OFLAG(termmode));
657 		setdelay(getpad(form_feed),
658 		    FFdelay, FFbits, &OFLAG(termmode));
659 		setdelay(getpad(tab),
660 		    TBdelay, TBbits, &OFLAG(termmode));
661 #endif	/* SYSV */
662 	}
663 
664 	/* If tabs can be sent to the tty, turn off their expansion. */
665 	if (tab && set_tab || init_tabs == 8) {
666 #ifdef SYSV
667 		OFLAG(termmodes) &= ~(TAB3);
668 #else	/* SYSV */
669 		OFLAG(termmode) &= ~(TAB3);
670 #endif	/* SYSV */
671 	} else {
672 #ifdef SYSV
673 		OFLAG(termmodes) |= TAB3;
674 #else	/* SYSV */
675 		OFLAG(termmode) |= TAB3;
676 #endif	/* SYSV */
677 	}
678 
679 	/* Do the changes to the terminal settings */
680 #ifdef SYSV
681 	if (istermios < 0) {
682 		int i;
683 
684 		termmode.c_lflag = termmodes.c_lflag;
685 		termmode.c_oflag = termmodes.c_oflag;
686 		termmode.c_iflag = termmodes.c_iflag;
687 		termmode.c_cflag = termmodes.c_cflag;
688 		for (i = 0; i < NCC; i++)
689 			termmode.c_cc[i] = termmodes.c_cc[i];
690 		(void) STTY(filedes, &termmode);
691 	} else
692 		(void) STTYS(filedes, &termmodes);
693 
694 #else	/* SYSV */
695 	(void) STTY(filedes, &termmode);
696 #endif	/* SYSV */
697 
698 	/* Send first initialization strings. */
699 	if (init_prog)
700 	(void) system(init_prog);
701 
702 	if (reset && reset_1string) {
703 		putp(reset_1string);
704 	} else if (init_1string) {
705 		putp(init_1string);
706 	}
707 
708 	if (reset && reset_2string) {
709 		putp(reset_2string);
710 	} else if (init_2string) {
711 		putp(init_2string);
712 	}
713 
714 	/* Set up the tabs stops. */
715 	settabs();
716 
717 	/* Send out initializing file. */
718 	if (reset && reset_file) {
719 		cat(reset_file);
720 	} else if (init_file) {
721 		cat(init_file);
722 	}
723 
724 	/* Send final initialization strings. */
725 	if (reset && reset_3string) {
726 		putp(reset_3string);
727 	} else if (init_3string) {
728 		putp(init_3string);
729 	}
730 
731 	if (carriage_return) {
732 		putp(carriage_return);
733 	} else {
734 		(void) putchar('\r');
735 	}
736 
737 	/* Send color initialization strings */
738 
739 	if (orig_colors)
740 		putp(orig_colors);
741 
742 	if (orig_pair)
743 	putp(orig_pair);
744 
745 	/* Let the terminal settle down. */
746 	(void) fflush(stdout);
747 	(void) sleep(1);
748 }
749 
750 static void
751 reset_term()
752 {
753 	reset++;
754 	initterm();
755 }
756