Added all of the MH sources, including RCS files, in
[mmh] / docs / historical / mh-6.8.5 / miscellany / less-177 / screen.c
1 /*
2  * Routines which deal with the characteristics of the terminal.
3  * Uses termcap to be as terminal-independent as possible.
4  *
5  * {{ Someday this should be rewritten to use curses. }}
6  */
7
8 #include "less.h"
9 #if XENIX
10 #include <sys/types.h>
11 #include <sys/ioctl.h>
12 #endif
13
14 #if TERMIO
15 #include <termio.h>
16 #else
17 #include <sgtty.h>
18 #endif
19
20 #if !TERMIO && defined(TIOCGWINSZ)
21 #include <sys/ioctl.h>
22 #else
23 /*
24  * For the Unix PC (ATT 7300 & 3B1):
25  * Since WIOCGETD is defined in sys/window.h, we can't use that to decide
26  * whether to include sys/window.h.  Use SIGPHONE from signal.h instead.
27  */
28 #include <signal.h>
29 #ifdef SIGPHONE
30 #include <sys/window.h>
31 #endif
32 #endif
33
34 #if NEED_PTEM_H && defined(TIOCGWINSZ)
35 /*
36  * All this just to get struct winsize.  Sigh.
37  */
38 #include <sys/types.h>
39 #include <sys/stream.h>
40 #include <sys/ptem.h>
41 #endif
42
43 /*
44  * Strings passed to tputs() to do various terminal functions.
45  */
46 static char
47         *sc_pad,                /* Pad string */
48         *sc_home,               /* Cursor home */
49         *sc_addline,            /* Add line, scroll down following lines */
50         *sc_lower_left,         /* Cursor to last line, first column */
51         *sc_move,               /* General cursor positioning */
52         *sc_clear,              /* Clear screen */
53         *sc_eol_clear,          /* Clear to end of line */
54         *sc_s_in,               /* Enter standout (highlighted) mode */
55         *sc_s_out,              /* Exit standout mode */
56         *sc_u_in,               /* Enter underline mode */
57         *sc_u_out,              /* Exit underline mode */
58         *sc_b_in,               /* Enter bold mode */
59         *sc_b_out,              /* Exit bold mode */
60         *sc_bl_in,              /* Enter blink mode */
61         *sc_bl_out,             /* Exit blink mode */
62         *sc_visual_bell,        /* Visual bell (flash screen) sequence */
63         *sc_backspace,          /* Backspace cursor */
64         *sc_init,               /* Startup terminal initialization */
65         *sc_deinit;             /* Exit terminal de-initialization */
66
67 static int init_done = 0;
68
69 public int auto_wrap;           /* Terminal does \r\n when write past margin */
70 public int ignaw;               /* Terminal ignores \n immediately after wrap */
71 public int erase_char, kill_char; /* The user's erase and line-kill chars */
72 public int sc_width, sc_height; /* Height & width of screen */
73 public int bo_s_width, bo_e_width;      /* Printing width of boldface seq */
74 public int ul_s_width, ul_e_width;      /* Printing width of underline seq */
75 public int so_s_width, so_e_width;      /* Printing width of standout seq */
76 public int bl_s_width, bl_e_width;      /* Printing width of blink seq */
77
78 static char *cheaper();
79
80 /*
81  * These two variables are sometimes defined in,
82  * and needed by, the termcap library.
83  * It may be necessary on some systems to declare them extern here.
84  */
85 /*extern*/ short ospeed;        /* Terminal output baud rate */
86 /*extern*/ char PC;             /* Pad character */
87
88 extern int quiet;               /* If VERY_QUIET, use visual bell for bell */
89 extern int know_dumb;           /* Don't complain about a dumb terminal */
90 extern int back_scroll;
91 extern int swindow;
92 extern char *tgetstr();
93 extern char *tgoto();
94 extern char *getenv();
95
96
97 /*
98  * Change terminal to "raw mode", or restore to "normal" mode.
99  * "Raw mode" means 
100  *      1. An outstanding read will complete on receipt of a single keystroke.
101  *      2. Input is not echoed.  
102  *      3. On output, \n is mapped to \r\n.
103  *      4. \t is NOT expanded into spaces.
104  *      5. Signal-causing characters such as ctrl-C (interrupt),
105  *         etc. are NOT disabled.
106  * It doesn't matter whether an input \n is mapped to \r, or vice versa.
107  */
108         public void
109 raw_mode(on)
110         int on;
111 {
112         static int curr_on = 0;
113
114         if (on == curr_on)
115                 return;
116 #if TERMIO
117     {
118         struct termio s;
119         static struct termio save_term;
120
121         if (on)
122         {
123                 /*
124                  * Get terminal modes.
125                  */
126                 ioctl(2, TCGETA, &s);
127
128                 /*
129                  * Save modes and set certain variables dependent on modes.
130                  */
131                 save_term = s;
132                 ospeed = s.c_cflag & CBAUD;
133                 erase_char = s.c_cc[VERASE];
134                 kill_char = s.c_cc[VKILL];
135
136                 /*
137                  * Set the modes to the way we want them.
138                  */
139                 s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
140                 s.c_oflag |=  (OPOST|ONLCR|TAB3);
141                 s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
142                 s.c_cc[VMIN] = 1;
143                 s.c_cc[VTIME] = 0;
144         } else
145         {
146                 /*
147                  * Restore saved modes.
148                  */
149                 s = save_term;
150         }
151         ioctl(2, TCSETAW, &s);
152     }
153 #else
154     {
155         struct sgttyb s;
156         static struct sgttyb save_term;
157
158         if (on)
159         {
160                 /*
161                  * Get terminal modes.
162                  */
163                 ioctl(2, TIOCGETP, &s);
164
165                 /*
166                  * Save modes and set certain variables dependent on modes.
167                  */
168                 save_term = s;
169                 ospeed = s.sg_ospeed;
170                 erase_char = s.sg_erase;
171                 kill_char = s.sg_kill;
172
173                 /*
174                  * Set the modes to the way we want them.
175                  */
176                 s.sg_flags |= CBREAK;
177                 s.sg_flags &= ~(ECHO|XTABS);
178         } else
179         {
180                 /*
181                  * Restore saved modes.
182                  */
183                 s = save_term;
184         }
185         ioctl(2, TIOCSETN, &s);
186     }
187 #endif
188         curr_on = on;
189 }
190
191         static void
192 cannot(s)
193         char *s;
194 {
195         PARG parg;
196
197         if (know_dumb)
198                 /* 
199                  * User knows this is a dumb terminal, so don't tell him.
200                  */
201                 return;
202
203         parg.p_string = s;
204         error("WARNING: terminal cannot %s", &parg);
205 }
206
207 /*
208  * Get size of the output screen.
209  */
210         public void
211 scrsize(p_height, p_width)
212         int *p_height;
213         int *p_width;
214 {
215         register char *s;
216 #ifdef TIOCGWINSZ
217         struct winsize w;
218 #else
219 #ifdef WIOCGETD
220         struct uwdata w;
221 #endif
222 #endif
223
224 #ifdef TIOCGWINSZ
225         if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
226                 *p_height = w.ws_row;
227         else
228 #else
229 #ifdef WIOCGETD
230         if (ioctl(2, WIOCGETD, &w) == 0 && w.uw_height > 0)
231                 *p_height = w.uw_height/w.uw_vs;
232         else
233 #endif
234 #endif
235         if ((s = getenv("LINES")) != NULL)
236                 *p_height = atoi(s);
237         else
238                 *p_height = tgetnum("li");
239
240         if (*p_height <= 0)
241                 *p_height = 24;
242
243 #ifdef TIOCGWINSZ
244         if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_col > 0)
245                 *p_width = w.ws_col;
246         else
247 #ifdef WIOCGETD
248         if (ioctl(2, WIOCGETD, &w) == 0 && w.uw_width > 0)
249                 *p_width = w.uw_width/w.uw_hs;
250         else
251 #endif
252 #endif
253         if ((s = getenv("COLUMNS")) != NULL)
254                 *p_width = atoi(s);
255         else
256                 *p_width = tgetnum("co");
257
258         if (*p_width <= 0)
259                 *p_width = 80;
260 }
261
262 /*
263  * Get terminal capabilities via termcap.
264  */
265         public void
266 get_term()
267 {
268         char *sp;
269         register char *t1, *t2;
270         register int hard;
271         char *term;
272         char termbuf[2048];
273
274         static char sbuf[1024];
275
276         /*
277          * Find out what kind of terminal this is.
278          */
279         if ((term = getenv("TERM")) == NULL)
280                 term = "unknown";
281         if (tgetent(termbuf, term) <= 0)
282                 strcpy(termbuf, "dumb:hc:");
283
284         hard = tgetflag("hc");
285
286         /*
287          * Get size of the screen.
288          */
289         scrsize(&sc_height, &sc_width);
290         pos_init();
291         if (swindow < 0)
292                 swindow = sc_height - 1;
293
294         auto_wrap = tgetflag("am");
295         ignaw = tgetflag("xn");
296
297         /*
298          * Assumes termcap variable "sg" is the printing width of:
299          * the standout sequence, the end standout sequence,
300          * the underline sequence, the end underline sequence,
301          * the boldface sequence, and the end boldface sequence.
302          */
303         if ((so_s_width = tgetnum("sg")) < 0)
304                 so_s_width = 0;
305         so_e_width = so_s_width;
306
307         bo_s_width = bo_e_width = so_s_width;
308         ul_s_width = ul_e_width = so_s_width;
309         bl_s_width = bl_e_width = so_s_width;
310
311         /*
312          * Get various string-valued capabilities.
313          */
314         sp = sbuf;
315
316         sc_pad = tgetstr("pc", &sp);
317         if (sc_pad != NULL)
318                 PC = *sc_pad;
319
320         sc_init = tgetstr("ti", &sp);
321         if (sc_init == NULL)
322                 sc_init = "";
323
324         sc_deinit= tgetstr("te", &sp);
325         if (sc_deinit == NULL)
326                 sc_deinit = "";
327
328         sc_eol_clear = tgetstr("ce", &sp);
329         if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0')
330         {
331                 cannot("clear to end of line");
332                 sc_eol_clear = "";
333         }
334
335         sc_clear = tgetstr("cl", &sp);
336         if (hard || sc_clear == NULL || *sc_clear == '\0')
337         {
338                 cannot("clear screen");
339                 sc_clear = "\n\n";
340         }
341
342         sc_move = tgetstr("cm", &sp);
343         if (hard || sc_move == NULL || *sc_move == '\0')
344         {
345                 /*
346                  * This is not an error here, because we don't 
347                  * always need sc_move.
348                  * We need it only if we don't have home or lower-left.
349                  */
350                 sc_move = "";
351         }
352
353         sc_s_in = tgetstr("so", &sp);
354         if (hard || sc_s_in == NULL)
355                 sc_s_in = "";
356
357         sc_s_out = tgetstr("se", &sp);
358         if (hard || sc_s_out == NULL)
359                 sc_s_out = "";
360
361         sc_u_in = tgetstr("us", &sp);
362         if (hard || sc_u_in == NULL)
363                 sc_u_in = sc_s_in;
364
365         sc_u_out = tgetstr("ue", &sp);
366         if (hard || sc_u_out == NULL)
367                 sc_u_out = sc_s_out;
368
369         sc_b_in = tgetstr("md", &sp);
370         if (hard || sc_b_in == NULL)
371         {
372                 sc_b_in = sc_s_in;
373                 sc_b_out = sc_s_out;
374         } else
375         {
376                 sc_b_out = tgetstr("me", &sp);
377                 if (hard || sc_b_out == NULL)
378                         sc_b_out = "";
379         }
380
381         sc_bl_in = tgetstr("mb", &sp);
382         if (hard || sc_bl_in == NULL)
383         {
384                 sc_bl_in = sc_s_in;
385                 sc_bl_out = sc_s_out;
386         } else
387         {
388                 sc_bl_out = sc_b_out;
389         }
390
391         sc_visual_bell = tgetstr("vb", &sp);
392         if (hard || sc_visual_bell == NULL)
393                 sc_visual_bell = "";
394
395         if (tgetflag("bs"))
396                 sc_backspace = "\b";
397         else
398         {
399                 sc_backspace = tgetstr("bc", &sp);
400                 if (sc_backspace == NULL || *sc_backspace == '\0')
401                         sc_backspace = "\b";
402         }
403
404         /*
405          * Choose between using "ho" and "cm" ("home" and "cursor move")
406          * to move the cursor to the upper left corner of the screen.
407          */
408         t1 = tgetstr("ho", &sp);
409         if (hard || t1 == NULL)
410                 t1 = "";
411         if (*sc_move == '\0')
412                 t2 = "";
413         else
414         {
415                 strcpy(sp, tgoto(sc_move, 0, 0));
416                 t2 = sp;
417                 sp += strlen(sp) + 1;
418         }
419         sc_home = cheaper(t1, t2, "home cursor", "|\b^");
420
421         /*
422          * Choose between using "ll" and "cm"  ("lower left" and "cursor move")
423          * to move the cursor to the lower left corner of the screen.
424          */
425         t1 = tgetstr("ll", &sp);
426         if (hard || t1 == NULL)
427                 t1 = "";
428         if (*sc_move == '\0')
429                 t2 = "";
430         else
431         {
432                 strcpy(sp, tgoto(sc_move, 0, sc_height-1));
433                 t2 = sp;
434                 sp += strlen(sp) + 1;
435         }
436         sc_lower_left = cheaper(t1, t2,
437                 "move cursor to lower left of screen", "\r");
438
439         /*
440          * Choose between using "al" or "sr" ("add line" or "scroll reverse")
441          * to add a line at the top of the screen.
442          */
443         t1 = tgetstr("al", &sp);
444         if (hard || t1 == NULL)
445                 t1 = "";
446         t2 = tgetstr("sr", &sp);
447         if (hard || t2 == NULL)
448                 t2 = "";
449         sc_addline = cheaper(t1, t2, "scroll backwards", "");
450         if (*sc_addline == '\0')
451         {
452                 /*
453                  * Force repaint on any backward movement.
454                  */
455                 back_scroll = 0;
456         }
457 }
458
459 /*
460  * Return the cost of displaying a termcap string.
461  * We use the trick of calling tputs, but as a char printing function
462  * we give it inc_costcount, which just increments "costcount".
463  * This tells us how many chars would be printed by using this string.
464  * {{ Couldn't we just use strlen? }}
465  */
466 static int costcount;
467
468 /*ARGSUSED*/
469         static void
470 inc_costcount(c)
471         int c;
472 {
473         costcount++;
474 }
475
476         static int
477 cost(t)
478         char *t;
479 {
480         costcount = 0;
481         tputs(t, sc_height, inc_costcount);
482         return (costcount);
483 }
484
485 /*
486  * Return the "best" of the two given termcap strings.
487  * The best, if both exist, is the one with the lower 
488  * cost (see cost() function).
489  */
490         static char *
491 cheaper(t1, t2, doit, def)
492         char *t1, *t2;
493         char *doit;
494         char *def;
495 {
496         if (*t1 == '\0' && *t2 == '\0')
497         {
498                 cannot(doit);
499                 return (def);
500         }
501         if (*t1 == '\0')
502                 return (t2);
503         if (*t2 == '\0')
504                 return (t1);
505         if (cost(t1) < cost(t2))
506                 return (t1);
507         return (t2);
508 }
509
510
511 /*
512  * Below are the functions which perform all the 
513  * terminal-specific screen manipulation.
514  */
515
516
517 /*
518  * Initialize terminal
519  */
520         public void
521 init()
522 {
523         tputs(sc_init, sc_height, putchr);
524         init_done = 1;
525 }
526
527 /*
528  * Deinitialize terminal
529  */
530         public void
531 deinit()
532 {
533         if (!init_done)
534                 return;
535         tputs(sc_deinit, sc_height, putchr);
536         init_done = 0;
537 }
538
539 /*
540  * Home cursor (move to upper left corner of screen).
541  */
542         public void
543 home()
544 {
545         tputs(sc_home, 1, putchr);
546 }
547
548 /*
549  * Add a blank line (called with cursor at home).
550  * Should scroll the display down.
551  */
552         public void
553 add_line()
554 {
555         tputs(sc_addline, sc_height, putchr);
556 }
557
558 /*
559  * Move cursor to lower left corner of screen.
560  */
561         public void
562 lower_left()
563 {
564         tputs(sc_lower_left, 1, putchr);
565 }
566
567 /*
568  * Ring the terminal bell.
569  */
570         public void
571 bell()
572 {
573         if (quiet == VERY_QUIET)
574                 vbell();
575         else
576                 putchr('\7');
577 }
578
579 /*
580  * Output the "visual bell", if there is one.
581  */
582         public void
583 vbell()
584 {
585         if (*sc_visual_bell == '\0')
586                 return;
587         tputs(sc_visual_bell, sc_height, putchr);
588 }
589
590 /*
591  * Clear the screen.
592  */
593         public void
594 clear()
595 {
596         tputs(sc_clear, sc_height, putchr);
597 }
598
599 /*
600  * Clear from the cursor to the end of the cursor's line.
601  * {{ This must not move the cursor. }}
602  */
603         public void
604 clear_eol()
605 {
606         tputs(sc_eol_clear, 1, putchr);
607 }
608
609 /*
610  * Begin "standout" (bold, underline, or whatever).
611  */
612         public void
613 so_enter()
614 {
615         tputs(sc_s_in, 1, putchr);
616 }
617
618 /*
619  * End "standout".
620  */
621         public void
622 so_exit()
623 {
624         tputs(sc_s_out, 1, putchr);
625 }
626
627 /*
628  * Begin "underline" (hopefully real underlining, 
629  * otherwise whatever the terminal provides).
630  */
631         public void
632 ul_enter()
633 {
634         tputs(sc_u_in, 1, putchr);
635 }
636
637 /*
638  * End "underline".
639  */
640         public void
641 ul_exit()
642 {
643         tputs(sc_u_out, 1, putchr);
644 }
645
646 /*
647  * Begin "bold"
648  */
649         public void
650 bo_enter()
651 {
652         tputs(sc_b_in, 1, putchr);
653 }
654
655 /*
656  * End "bold".
657  */
658         public void
659 bo_exit()
660 {
661         tputs(sc_b_out, 1, putchr);
662 }
663
664 /*
665  * Begin "blink"
666  */
667         public void
668 bl_enter()
669 {
670         tputs(sc_bl_in, 1, putchr);
671 }
672
673 /*
674  * End "blink".
675  */
676         public void
677 bl_exit()
678 {
679         tputs(sc_bl_out, 1, putchr);
680 }
681
682 /*
683  * Erase the character to the left of the cursor 
684  * and move the cursor left.
685  */
686         public void
687 backspace()
688 {
689         /* 
690          * Try to erase the previous character by overstriking with a space.
691          */
692         tputs(sc_backspace, 1, putchr);
693         putchr(' ');
694         tputs(sc_backspace, 1, putchr);
695 }
696
697 /*
698  * Output a plain backspace, without erasing the previous char.
699  */
700         public void
701 putbs()
702 {
703         tputs(sc_backspace, 1, putchr);
704 }