1 /*
2  * Copyright (c) 1988 Mark Nudleman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by the University of
17  *	California, Berkeley and its contributors.
18  * 4. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #ifndef lint
36 static char sccsid[] = "@(#)screen.c	8.2 (Berkeley) 4/20/94";
37 #endif /* not lint */
38 
39 #ifndef lint
40 static const char rcsid[] =
41   "$FreeBSD$";
42 #endif /* not lint */
43 
44 /*
45  * Routines which deal with the characteristics of the terminal.
46  * Uses termcap to be as terminal-independent as possible.
47  *
48  * {{ Someday this should be rewritten to use curses. }}
49  */
50 
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <termcap.h>
55 #include <unistd.h>
56 
57 #include "less.h"
58 
59 #define TERMIOS 1
60 
61 #if TERMIO
62 #include <termio.h>
63 #else
64 #if TERMIOS
65 #include <termios.h>
66 #define TAB3 0
67 #include <sys/ioctl.h>
68 #else
69 #include <sgtty.h>
70 #endif
71 #endif
72 
73 #ifdef TIOCGWINSZ
74 #include <sys/ioctl.h>
75 #else
76 /*
77  * For the Unix PC (ATT 7300 & 3B1):
78  * Since WIOCGETD is defined in sys/window.h, we can't use that to decide
79  * whether to include sys/window.h.  Use SIGPHONE from sys/signal.h instead.
80  */
81 #include <sys/signal.h>
82 #ifdef SIGPHONE
83 #include <sys/window.h>
84 #endif
85 #endif
86 
87 /*
88  * Strings passed to tputs() to do various terminal functions.
89  */
90 static char
91 	*sc_pad,		/* Pad string */
92 	*sc_home,		/* Cursor home */
93 	*sc_addline,		/* Add line, scroll down following lines */
94 	*sc_lower_left,		/* Cursor to last line, first column */
95 	*sc_move,		/* General cursor positioning */
96 	*sc_clear,		/* Clear screen */
97 	*sc_eol_clear,		/* Clear to end of line */
98 	*sc_s_in,		/* Enter standout (highlighted) mode */
99 	*sc_s_out,		/* Exit standout mode */
100 	*sc_u_in,		/* Enter underline mode */
101 	*sc_u_out,		/* Exit underline mode */
102 	*sc_b_in,		/* Enter bold mode */
103 	*sc_b_out,		/* Exit bold mode */
104 	*sc_backspace,		/* Backspace cursor */
105 	*sc_init,		/* Startup terminal initialization */
106 	*sc_deinit,		/* Exit terminal de-intialization */
107 	*sc_keypad_on,		/* Enter keypad mode */
108 	*sc_keypad_off;		/* Exit keypad mode */
109 
110 int auto_wrap;			/* Terminal does \r\n when write past margin */
111 int ignaw;			/* Terminal ignores \n immediately after wrap */
112 				/* The user's erase and line-kill chars */
113 int retain_below;		/* Terminal retains text below the screen */
114 int erase_char, kill_char, werase_char;
115 int sc_width, sc_height = -1;	/* Height & width of screen */
116 int bo_width, be_width;		/* Printing width of boldface sequences */
117 int ul_width, ue_width;		/* Printing width of underline sequences */
118 int so_width, se_width;		/* Printing width of standout sequences */
119 
120 int mode_flags = 0;
121 #define M_SO 1
122 #define M_UL 2
123 #define M_BO 4
124 
125 /*
126  * These two variables are sometimes defined in,
127  * and needed by, the termcap library.
128  * It may be necessary on some systems to declare them extern here.
129  */
130 #if 0
131 /*extern*/ speed_t ospeed;	/* Terminal output baud rate */
132 #endif
133 /*extern*/ char PC;		/* Pad character */
134 
135 extern int back_scroll;
136 
137 /*
138  * Change terminal to "raw mode", or restore to "normal" mode.
139  * "Raw mode" means
140  *	1. An outstanding read will complete on receipt of a single keystroke.
141  *	2. Input is not echoed.
142  *	3. On output, \n is mapped to \r\n.
143  *	4. \t is NOT expanded into spaces.
144  *	5. Signal-causing characters such as ctrl-C (interrupt),
145  *	   etc. are NOT disabled.
146  * It doesn't matter whether an input \n is mapped to \r, or vice versa.
147  */
raw_mode(on)148 raw_mode(on)
149 	int on;
150 {
151 #if TERMIO || TERMIOS
152 
153 #if TERMIO
154 	struct termio s;
155 	static struct termio save_term;
156 #else
157 	struct termios s;
158 	static struct termios save_term;
159 #endif
160 
161 	if (on)
162 	{
163 		/*
164 		 * Get terminal modes.
165 		 */
166 #if TERMIO
167 		(void)ioctl(2, TCGETA, &s);
168 #else
169 		tcgetattr(2, &s);
170 #endif
171 
172 		/*
173 		 * Save modes and set certain variables dependent on modes.
174 		 */
175 		save_term = s;
176 #if TERMIO
177 		ospeed = s.c_cflag & CBAUD;
178 #else
179 		/* more work needed here */
180 #endif
181 		erase_char = s.c_cc[VERASE];
182 		kill_char = s.c_cc[VKILL];
183 		werase_char = s.c_cc[VWERASE];
184 
185 		/*
186 		 * Set the modes to the way we want them.
187 		 */
188 		s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
189 		s.c_oflag |=  (OPOST|ONLCR|TAB3);
190 #if TERMIO
191 		s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
192 #endif
193 		s.c_cc[VMIN] = 1;
194 		s.c_cc[VTIME] = 0;
195 	} else
196 	{
197 		/*
198 		 * Restore saved modes.
199 		 */
200 		s = save_term;
201 	}
202 #if TERMIO
203 	(void)ioctl(STDERR_FILENO, TCSETAW, &s);
204 #else
205 	tcsetattr(STDERR_FILENO, TCSADRAIN, &s);
206 #endif
207 #else
208 	struct sgttyb s;
209 	struct ltchars l;
210 	static struct sgttyb save_term;
211 
212 	if (on)
213 	{
214 		/*
215 		 * Get terminal modes.
216 		 */
217 		(void)ioctl(STDERR_FILENO, TIOCGETP, &s);
218 		(void)ioctl(STDERR_FILENO, TIOCGLTC, &l);
219 
220 		/*
221 		 * Save modes and set certain variables dependent on modes.
222 		 */
223 		save_term = s;
224 		ospeed = s.sg_ospeed;
225 		erase_char = s.sg_erase;
226 		kill_char = s.sg_kill;
227 		werase_char = l.t_werasc;
228 
229 		/*
230 		 * Set the modes to the way we want them.
231 		 */
232 		s.sg_flags |= CBREAK;
233 		s.sg_flags &= ~(ECHO|XTABS);
234 	} else
235 	{
236 		/*
237 		 * Restore saved modes.
238 		 */
239 		s = save_term;
240 	}
241 	(void)ioctl(STDERR_FILENO, TIOCSETN, &s);
242 #endif
243 }
244 
245 /*
246  * Get terminal capabilities via termcap.
247  */
get_term()248 get_term()
249 {
250 	char termbuf[2048];
251 	char *sp;
252 	char *term;
253 	int hard;
254 #ifdef TIOCGWINSZ
255 	struct winsize w;
256 #else
257 #ifdef WIOCGETD
258 	struct uwdata w;
259 #endif
260 #endif
261 	static char sbuf[1024];
262 
263 	/*
264 	 * Find out what kind of terminal this is.
265 	 */
266  	if ((term = getenv("TERM")) == NULL)
267  		term = "unknown";
268  	if (tgetent(termbuf, term) <= 0)
269  		(void)strcpy(termbuf, "dumb:co#80:hc:");
270 
271 	/*
272 	 * Get size of the screen.
273 	 */
274 #ifdef TIOCGWINSZ
275 	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
276 		sc_height = w.ws_row;
277 	else
278 #else
279 #ifdef WIOCGETD
280 	if (ioctl(STDERR_FILENO, WIOCGETD, &w) == 0 && w.uw_height > 0)
281 		sc_height = w.uw_height/w.uw_vs;
282 	else
283 #endif
284 #endif
285 		sc_height = tgetnum("li");
286 	hard = (sc_height < 0 || tgetflag("hc"));
287 	if (hard) {
288 		/* Oh no, this is a hardcopy terminal. */
289 		sc_height = 24;
290 	}
291 
292 #ifdef TIOCGWINSZ
293  	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0 && w.ws_col > 0)
294 		sc_width = w.ws_col;
295 	else
296 #ifdef WIOCGETD
297 	if (ioctl(STDERR_FILENO, WIOCGETD, &w) == 0 && w.uw_width > 0)
298 		sc_width = w.uw_width/w.uw_hs;
299 	else
300 #endif
301 #endif
302  		sc_width = tgetnum("co");
303  	if (sc_width < 0)
304 		sc_width = 80;
305 	(void) setvari("_sc_height", (long) sc_height - 1);
306 	(void) setvari("_sc_width", (long) sc_width);
307 
308 	auto_wrap = tgetflag("am");
309 	ignaw = tgetflag("xn");
310 	retain_below = tgetflag("db");
311 
312 	/*
313 	 * Assumes termcap variable "sg" is the printing width of
314 	 * the standout sequence, the end standout sequence,
315 	 * the underline sequence, the end underline sequence,
316 	 * the boldface sequence, and the end boldface sequence.
317 	 */
318 	if ((so_width = tgetnum("sg")) < 0)
319 		so_width = 0;
320 	be_width = bo_width = ue_width = ul_width = se_width = so_width;
321 
322 	/*
323 	 * Get various string-valued capabilities.
324 	 */
325 	sp = sbuf;
326 
327 	sc_pad = tgetstr("pc", &sp);
328 	if (sc_pad != NULL)
329 		PC = *sc_pad;
330 
331 	sc_init = tgetstr("ti", &sp);
332 	if (sc_init == NULL)
333 		sc_init = "";
334 
335 	sc_deinit = tgetstr("te", &sp);
336 	if (sc_deinit == NULL)
337 		sc_deinit = "";
338 
339 	sc_keypad_on = tgetstr("ks", &sp);
340 	if (sc_keypad_on == NULL)
341 		sc_keypad_on = "";
342 
343 	sc_keypad_off = tgetstr("ke", &sp);
344 	if (sc_keypad_off == NULL)
345 		sc_keypad_off = "";
346 
347 	sc_eol_clear = tgetstr("ce", &sp);
348 	if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0')
349 	{
350 		sc_eol_clear = "";
351 	}
352 
353 	sc_clear = tgetstr("cl", &sp);
354 	if (hard || sc_clear == NULL || *sc_clear == '\0')
355 	{
356 		sc_clear = "\n\n";
357 	}
358 
359 	sc_move = tgetstr("cm", &sp);
360 	if (hard || sc_move == NULL || *sc_move == '\0')
361 	{
362 		/*
363 		 * This is not an error here, because we don't
364 		 * always need sc_move.
365 		 * We need it only if we don't have home or lower-left.
366 		 */
367 		sc_move = "";
368 	}
369 
370 	sc_s_in = tgetstr("so", &sp);
371 	if (hard || sc_s_in == NULL)
372 		sc_s_in = "";
373 
374 	sc_s_out = tgetstr("se", &sp);
375 	if (hard || sc_s_out == NULL)
376 		sc_s_out = "";
377 
378 	sc_u_in = tgetstr("us", &sp);
379 	if (hard || sc_u_in == NULL)
380 		sc_u_in = sc_s_in;
381 
382 	sc_u_out = tgetstr("ue", &sp);
383 	if (hard || sc_u_out == NULL)
384 		sc_u_out = sc_s_out;
385 
386 	sc_b_in = tgetstr("md", &sp);
387 	if (hard || sc_b_in == NULL)
388 	{
389 		sc_b_in = sc_s_in;
390 		sc_b_out = sc_s_out;
391 	} else
392 	{
393 		sc_b_out = tgetstr("me", &sp);
394 		if (hard || sc_b_out == NULL)
395 			sc_b_out = "";
396 	}
397 
398 	sc_home = tgetstr("ho", &sp);
399 	if (hard || sc_home == NULL || *sc_home == '\0')
400 	{
401 		if (*sc_move == '\0')
402 		{
403 			/*
404 			 * This last resort for sc_home is supposed to
405 			 * be an up-arrow suggesting moving to the
406 			 * top of the "virtual screen". (The one in
407 			 * your imagination as you try to use this on
408 			 * a hard copy terminal.)
409 			 */
410 			sc_home = "|\b^";
411 		} else
412 		{
413 			/*
414 			 * No "home" string,
415 			 * but we can use "move(0,0)".
416 			 */
417 			(void)strcpy(sp, tgoto(sc_move, 0, 0));
418 			sc_home = sp;
419 			sp += strlen(sp) + 1;
420 		}
421 	}
422 
423 	sc_lower_left = tgetstr("ll", &sp);
424 	if (hard || sc_lower_left == NULL || *sc_lower_left == '\0')
425 	{
426 		if (*sc_move == '\0')
427 		{
428 			sc_lower_left = "\r";
429 		} else
430 		{
431 			/*
432 			 * No "lower-left" string,
433 			 * but we can use "move(0,last-line)".
434 			 */
435 			(void)strcpy(sp, tgoto(sc_move, 0, sc_height-1));
436 			sc_lower_left = sp;
437 			sp += strlen(sp) + 1;
438 		}
439 	}
440 
441 	/*
442 	 * To add a line at top of screen and scroll the display down,
443 	 * we use "al" (add line) or "sr" (scroll reverse).
444 	 */
445 	if ((sc_addline = tgetstr("al", &sp)) == NULL ||
446 		 *sc_addline == '\0')
447 		sc_addline = tgetstr("sr", &sp);
448 
449 	if (hard || sc_addline == NULL || *sc_addline == '\0')
450 	{
451 		sc_addline = "";
452 		/* Force repaint on any backward movement */
453 		back_scroll = 0;
454 	}
455 
456 	if (tgetflag("bs"))
457 		sc_backspace = "\b";
458 	else
459 	{
460 		sc_backspace = tgetstr("bc", &sp);
461 		if (sc_backspace == NULL || *sc_backspace == '\0')
462 			sc_backspace = "\b";
463 	}
464 }
465 
466 /*
467  * Retrieves string (if any) associated with tcap from the termcap.  If the
468  * capability is not a string capability, an ascii approximation of the
469  * capability will be returned.  Returns NULL if tcap cannot be found.
470  * The returned string is valid until the next call to gettermcap().
471  */
472 const char *
gettermcap(tcap)473 gettermcap(tcap)
474 	char *tcap;
475 {
476 	char termbuf[2048];
477 	char *term;
478 	static char sbuf[1024];
479 	char *sp = sbuf;
480 	int i;
481 
482 	/*
483 	 * Find out what kind of terminal this is.
484 	 */
485  	if ((term = getenv("TERM")) == NULL)
486  		term = "unknown";
487  	if (tgetent(termbuf, term) <= 0)
488  		(void)strcpy(termbuf, "dumb:co#80:hc:");
489 
490 	sp = tgetstr(tcap, &sp);
491 	if (sp == NULL || !*sp) {
492 		sprintf (sbuf, "%d", i=tgetnum(tcap));
493 		if (i == -1)
494 			sprintf (sbuf, "%d", tgetflag(tcap));
495 	}
496 	return sbuf;
497 }
498 
499 /*
500  * Below are the functions which perform all the
501  * terminal-specific screen manipulation.
502  */
503 
504 int putchr();
505 
506 /*
507  * Initialize terminal
508  */
init()509 init()
510 {
511 	tputs(sc_init, sc_height, putchr);
512 	tputs(sc_keypad_on, 1, putchr);
513 }
514 
515 /*
516  * Deinitialize terminal
517  */
deinit()518 deinit()
519 {
520 	tputs(sc_deinit, sc_height, putchr);
521 	tputs(sc_keypad_off, 1, putchr);
522 }
523 
524 /*
525  * Home cursor (move to upper left corner of screen).
526  */
home()527 home()
528 {
529 	tputs(sc_home, 1, putchr);
530 }
531 
532 /*
533  * Add a blank line (called with cursor at home).
534  * Should scroll the display down.
535  */
add_line()536 add_line()
537 {
538 	tputs(sc_addline, sc_height, putchr);
539 }
540 
541 int short_file;				/* if file less than a screen */
lower_left()542 lower_left()
543 {
544 	if (short_file) {
545 		putchr('\r');
546 		flush();
547 	}
548 	else
549 		tputs(sc_lower_left, 1, putchr);
550 }
551 
552 /*
553  * Ring the terminal bell.
554  */
bell()555 bell()
556 {
557 	putchr('\7');
558 }
559 
560 /*
561  * Clear the screen.
562  */
clear()563 clear()
564 {
565 	if (mode_flags & M_SO)
566 		so_exit();
567 	if (mode_flags & M_UL)
568 		ul_exit();
569 	if (mode_flags & M_BO)
570 		bo_exit();
571 	tputs(sc_clear, sc_height, putchr);
572 }
573 
574 /*
575  * Clear from the cursor to the end of the cursor's line.
576  * {{ This must not move the cursor. }}
577  */
clear_eol()578 clear_eol()
579 {
580 	if (mode_flags & M_SO)
581 		so_exit();
582 	if (mode_flags & M_UL)
583 		ul_exit();
584 	if (mode_flags & M_BO)
585 		bo_exit();
586 	tputs(sc_eol_clear, 1, putchr);
587 }
588 
589 /*
590  * Begin "standout" (bold, underline, or whatever).
591  */
so_enter()592 so_enter()
593 {
594 	tputs(sc_s_in, 1, putchr);
595 	mode_flags |= M_SO;
596 }
597 
598 /*
599  * End "standout".
600  */
so_exit()601 so_exit()
602 {
603 	tputs(sc_s_out, 1, putchr);
604 	mode_flags &= ~M_SO;
605 }
606 
607 /*
608  * Begin "underline" (hopefully real underlining,
609  * otherwise whatever the terminal provides).
610  */
ul_enter()611 ul_enter()
612 {
613 	tputs(sc_u_in, 1, putchr);
614 	mode_flags |= M_UL;
615 }
616 
617 /*
618  * End "underline".
619  */
ul_exit()620 ul_exit()
621 {
622 	tputs(sc_u_out, 1, putchr);
623 	mode_flags &= ~M_UL;
624 }
625 
626 /*
627  * Begin "bold"
628  */
bo_enter()629 bo_enter()
630 {
631 	tputs(sc_b_in, 1, putchr);
632 	mode_flags |= M_BO;
633 }
634 
635 /*
636  * End "bold".
637  */
bo_exit()638 bo_exit()
639 {
640 	tputs(sc_b_out, 1, putchr);
641 	mode_flags &= ~M_BO;
642 }
643 
644 /*
645  * Erase the character to the left of the cursor
646  * and move the cursor left.
647  */
backspace()648 backspace()
649 {
650 	/*
651 	 * Try to erase the previous character by overstriking with a space.
652 	 */
653 	tputs(sc_backspace, 1, putchr);
654 	putchr(' ');
655 	tputs(sc_backspace, 1, putchr);
656 }
657 
658 /*
659  * Output a plain backspace, without erasing the previous char.
660  */
putbs()661 putbs()
662 {
663 	tputs(sc_backspace, 1, putchr);
664 }
665