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