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