xref: /illumos-gate/usr/src/cmd/tput/tput.c (revision 88f8b78a88cbdc6d8c1af5c3e54bc49d25095c98)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 
34 /*
35  *	tput - print terminal attribute
36  *
37  *  return-codes - command line arguments:
38  *	0: ok if boolean capname -> TRUE
39  *	1: for boolean capname -> FALSE
40  *
41  *  return-codes - standard input arguments:
42  *	0: ok; tput for all lines was successful
43  *
44  *  return-codes - both cases:
45  *	2	usage error
46  *	3	bad terminal type given or no terminfo database
47  *	4	unknown capname
48  *	-1	capname is a numeric variable that is not specified in the
49  *		terminfo database(E.g. tpu -T450 lines).
50  *
51  *  tput printfs a value if an INT capname was given; e.g. cols.
52  *	putp's a string if a STRING capname was given; e.g. clear. and
53  *  for BOOLEAN capnames, e.g. hard-copy, just returns the boolean value.
54  */
55 
56 #include <curses.h>
57 #include <term.h>
58 #include <fcntl.h>
59 #include <ctype.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <sys/types.h>
63 #include <unistd.h>
64 #include <locale.h>
65 
66 /* externs from libcurses */
67 extern int tigetnum();
68 
69 static int outputcap(char *cap, int argc, char **argv);
70 static int allnumeric(char *string);
71 static int getpad(char *cap);
72 static void setdelay();
73 static void settabs();
74 static void cat(char *file);
75 static void initterm();
76 static void reset_term();
77 
78 static char *progname;		/* argv[0] */
79 static int CurrentBaudRate;	/* current baud rate */
80 static int reset = 0;		/* called as reset_term */
81 static int fildes = 1;
82 
83 int
84 main(int argc, char **argv)
85 {
86 	int i, std_argc;
87 	char *term = getenv("TERM");
88 	char *cap, std_input = FALSE;
89 	int setuperr;
90 
91 	(void) setlocale(LC_ALL, "");
92 #if !defined(TEXT_DOMAIN)
93 #define	TEXT_DOMAIN "SYS_TEST"
94 #endif
95 	(void) textdomain(TEXT_DOMAIN);
96 
97 	progname = argv[0];
98 
99 	while ((i = getopt(argc, argv, "ST:")) != EOF) {
100 		switch (i) {
101 		case 'T':
102 			fildes = -1;
103 			(void) putenv("LINES=");
104 			(void) putenv("COLUMNS=");
105 			term = optarg;
106 			break;
107 
108 		case 'S':
109 			std_input = TRUE;
110 			break;
111 
112 		case '?':			/* FALLTHROUGH		*/
113 		usage:				/* FALLTHROUGH		*/
114 		default:
115 			(void) fprintf(stderr, gettext(
116 			"usage:\t%s [-T [term]] capname [parm argument...]\n"),
117 				progname);
118 			(void) fprintf(stderr, gettext("OR:\t%s -S <<\n"),
119 					progname);
120 			exit(2);
121 		}
122 	}
123 
124 	if (!term || !*term) {
125 		(void) fprintf(stderr,
126 			gettext("%s: No value for $TERM and no -T specified\n"),
127 			progname);
128 		exit(2);
129 	}
130 
131 	(void) setupterm(term, fildes, &setuperr);
132 
133 	switch (setuperr) {
134 	case -2:
135 		(void) fprintf(stderr,
136 		gettext("%s: unreadable terminal descriptor \"%s\"\n"),
137 			progname, term);
138 		exit(3);
139 		break;
140 
141 	case -1:
142 		(void) fprintf(stderr,
143 			gettext("%s: no terminfo database\n"), progname);
144 		exit(3);
145 		break;
146 
147 	case 0:
148 	    (void) fprintf(stderr,
149 			gettext("%s: unknown terminal \"%s\"\n"),
150 				progname, term);
151 	    exit(3);
152 	}
153 
154 	reset_shell_mode();
155 
156 	/* command line arguments */
157 	if (!std_input) {
158 		if (argc == optind)
159 			goto usage;
160 
161 		cap = argv[optind++];
162 
163 		if (strcmp(cap, "init") == 0)
164 			initterm();
165 		else if (strcmp(cap, "reset") == 0)
166 			reset_term();
167 		else if (strcmp(cap, "longname") == 0)
168 			(void) printf("%s\n", longname());
169 		else
170 			exit(outputcap(cap, argc, argv));
171 		return (0);
172 	} else {			/* standard input argumets	*/
173 		char buff[128];
174 		char **v;
175 
176 		/* allocate storage for the 'faked' argv[] array	*/
177 		v = (char **)malloc(10 * sizeof (char *));
178 		for (i = 0; i < 10; i++)
179 			v[i] = (char *)malloc(32 * sizeof (char));
180 
181 		while (gets(buff) != NULL) {
182 			/* read standard input line; skip over empty lines */
183 			if ((std_argc =
184 			    sscanf(buff, "%s %s %s %s %s %s %s %s %s %s",
185 			    v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7],
186 			    v[8], v[9])) < 1) {
187 				continue;
188 			}
189 
190 			cap = v[0];
191 			optind = 1;
192 
193 			if (strcmp(cap, "init") == 0) {
194 				initterm();
195 			} else if (strcmp(cap, "reset") == 0) {
196 				reset_term();
197 			} else if (strcmp(cap, "longname") == 0) {
198 				(void) printf("%s\n", longname());
199 			} else {
200 				(void) outputcap(cap, std_argc, v);
201 			}
202 			(void) fflush(stdout);
203 		}
204 
205 		return (0);
206 	}
207 }
208 
209 static long parm[9] = {
210     0, 0, 0, 0, 0, 0, 0, 0, 0
211 };
212 
213 static int
214 outputcap(char *cap, int argc, char **argv)
215 {
216 	int parmset = 0;
217 	char *thisstr;
218 	int i;
219 
220 	if ((i = tigetflag(cap)) >= 0)
221 		return (1 - i);
222 
223 	if ((i = tigetnum(cap)) >= -1) {
224 		(void) printf("%d\n", i);
225 		return (0);
226 	}
227 
228 	if ((thisstr = tigetstr(cap)) != (char *)-1) {
229 		if (!thisstr) {
230 			return (1);
231 		}
232 		for (parmset = 0; optind < argc; optind++, parmset++)
233 			if (allnumeric(argv[optind]))
234 				parm[parmset] = atoi(argv[optind]);
235 			else
236 				parm[parmset] = (int)argv[optind];
237 
238 		if (parmset)
239 			putp(tparm(thisstr,
240 			parm[0], parm[1], parm[2], parm[3], parm[4], parm[5],
241 			parm[6], parm[7], parm[8]));
242 		else
243 			putp(thisstr);
244 		return (0);
245 	}
246 
247 	(void) fprintf(stderr,
248 	    gettext("%s: unknown terminfo capability '%s'\n"), progname, cap);
249 
250 	exit(4);
251 	/* NOTREACHED */
252 }
253 
254 /*
255  *  The decision as to whether an argument is a number or not is to simply
256  *  look at whether there are any non-digits in the string.
257  */
258 static int
259 allnumeric(char *string)
260 {
261 	if (*string) {
262 		while (*string) {
263 			if (!isdigit(*string++)) {
264 				return (0);
265 			}
266 		}
267 		return (1);
268 	} else {
269 		return (0);
270 	}
271 }
272 
273 /*
274  *  SYSTEM DEPENDENT TERMINAL DELAY TABLES
275  *
276  *	These tables maintain the correspondence between the delays
277  *	defined in terminfo and the delay algorithms in the tty driver
278  *	on the particular systems. For each type of delay, the bits used
279  *	for that delay must be specified, in XXbits, and a table
280  *	must be defined giving correspondences between delays and
281  *	algorithms. Algorithms which are not fixed delays, such
282  *	as dependent on current column or line number, must be
283  *	kludged in some way at this time.
284  *
285  *	Some of this was taken from tset(1).
286  */
287 
288 struct delay
289 {
290     int d_delay;
291     int d_bits;
292 };
293 
294 /* The appropriate speeds for various termio settings. */
295 static int speeds[] = {
296 		0,	/*  B0,		*/
297 		50,	/*  B50,	*/
298 		75,	/*  B75,	*/
299 		110,	/*  B110,	*/
300 		134,	/*  B134,	*/
301 		150,	/*  B150,	*/
302 		200,	/*  B200,	*/
303 		300,	/*  B300,	*/
304 		600,	/*  B600,	*/
305 		1200,	/*  B1200,	*/
306 		1800,	/*  B1800,	*/
307 		2400,	/*  B2400,	*/
308 		4800,	/*  B4800,	*/
309 		9600,	/*  B9600,	*/
310 		19200,	/*  EXTA,	*/
311 		38400,	/*  EXTB,	*/
312 		57600,	/*  B57600,	*/
313 		76800,	/*  B76800,	*/
314 		115200,	/*  B115200,	*/
315 		153600,	/*  B153600,	*/
316 		230400,	/*  B230400,	*/
317 		307200,	/*  B307200,	*/
318 		460800,	/*  B460800,	*/
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), CRdelay, CRbits, &OFLAG(termmodes));
641 	setdelay(getpad(scroll_forward), NLdelay, NLbits, &OFLAG(termmodes));
642 	setdelay(getpad(cursor_left), BSdelay, BSbits, &OFLAG(termmodes));
643 	setdelay(getpad(form_feed), FFdelay, FFbits, &OFLAG(termmodes));
644 	setdelay(getpad(tab), TBdelay, TBbits, &OFLAG(termmodes));
645 #else	/* SYSV */
646 	setdelay(getpad(carriage_return), CRdelay, CRbits, &OFLAG(termmode));
647 	setdelay(getpad(scroll_forward), NLdelay, NLbits, &OFLAG(termmode));
648 	setdelay(getpad(cursor_left), BSdelay, BSbits, &OFLAG(termmode));
649 	setdelay(getpad(form_feed), FFdelay, FFbits, &OFLAG(termmode));
650 	setdelay(getpad(tab), TBdelay, TBbits, &OFLAG(termmode));
651 #endif	/* SYSV */
652 	}
653 
654 	/* If tabs can be sent to the tty, turn off their expansion. */
655 	if (tab && set_tab || init_tabs == 8) {
656 #ifdef SYSV
657 		OFLAG(termmodes) &= ~(TAB3);
658 #else	/* SYSV */
659 		OFLAG(termmode) &= ~(TAB3);
660 #endif	/* SYSV */
661 	} else {
662 #ifdef SYSV
663 		OFLAG(termmodes) |= TAB3;
664 #else	/* SYSV */
665 		OFLAG(termmode) |= TAB3;
666 #endif	/* SYSV */
667 	}
668 
669 	/* Do the changes to the terminal settings */
670 #ifdef SYSV
671 	if (istermios < 0) {
672 		int i;
673 
674 		termmode.c_lflag = termmodes.c_lflag;
675 		termmode.c_oflag = termmodes.c_oflag;
676 		termmode.c_iflag = termmodes.c_iflag;
677 		termmode.c_cflag = termmodes.c_cflag;
678 		for (i = 0; i < NCC; i++)
679 			termmode.c_cc[i] = termmodes.c_cc[i];
680 		(void) STTY(filedes, &termmode);
681 	} else
682 		(void) STTYS(filedes, &termmodes);
683 
684 #else	/* SYSV */
685 	(void) STTY(filedes, &termmode);
686 #endif	/* SYSV */
687 
688 	/* Send first initialization strings. */
689 	if (init_prog)
690 	(void) system(init_prog);
691 
692 	if (reset && reset_1string) {
693 		putp(reset_1string);
694 	} else if (init_1string) {
695 		putp(init_1string);
696 	}
697 
698 	if (reset && reset_2string) {
699 		putp(reset_2string);
700 	} else if (init_2string) {
701 		putp(init_2string);
702 	}
703 
704 	/* Set up the tabs stops. */
705 	settabs();
706 
707 	/* Send out initializing file. */
708 	if (reset && reset_file) {
709 		cat(reset_file);
710 	} else if (init_file) {
711 		cat(init_file);
712 	}
713 
714 	/* Send final initialization strings. */
715 	if (reset && reset_3string) {
716 		putp(reset_3string);
717 	} else if (init_3string) {
718 		putp(init_3string);
719 	}
720 
721 	if (carriage_return) {
722 		putp(carriage_return);
723 	} else {
724 		(void) putchar('\r');
725 	}
726 
727 	/* Send color initialization strings */
728 
729 	if (orig_colors)
730 		putp(orig_colors);
731 
732 	if (orig_pair)
733 	putp(orig_pair);
734 
735 	/* Let the terminal settle down. */
736 	(void) fflush(stdout);
737 	(void) sleep(1);
738 }
739 
740 static void
741 reset_term()
742 {
743 	reset++;
744 	initterm();
745 }
746