1 
2 static char rcsid[] = "@(#)$Id: curses.c,v 1.6 1999/03/24 14:03:58 wfp5p Exp $";
3 
4 /*******************************************************************************
5  *  The Elm Mail System  -  $Revision: 1.6 $   $State: Exp $
6  *
7  *                      Copyright (c) 1988-1995 USENET Community Trust
8  * 			Copyright (c) 1986,1987 Dave Taylor
9  *******************************************************************************
10  * Bug reports, patches, comments, suggestions should be sent to:
11  *
12  *      Bill Pemberton, Elm Coordinator
13  *      flash@virginia.edu
14  *
15  *******************************************************************************
16  * $Log: curses.c,v $
17  * Revision 1.6  1999/03/24  14:03:58  wfp5p
18  * elm 2.5PL0
19  *
20  * Revision 1.5  1996/05/09  15:51:18  wfp5p
21  * Alpha 10
22  *
23  * Revision 1.4  1996/03/14  17:27:56  wfp5p
24  * Alpha 9
25  *
26  * Revision 1.3  1995/09/29  17:42:01  wfp5p
27  * Alpha 8 (Chip's big changes)
28  *
29  * Revision 1.2  1995/09/11  15:19:03  wfp5p
30  * Alpha 7
31  *
32  * Revision 1.1.1.1  1995/04/19  20:38:35  wfp5p
33  * Initial import of elm 2.4 PL0 as base for elm 2.5.
34  *
35  ******************************************************************************/
36 
37 /**  This library gives programs the ability to easily access the
38      termcap information and write screen oriented and raw input
39      programs.  The routines can be called as needed, except that
40      to use the cursor / screen routines there must be a call to
41      InitScreen() first.
42 **/
43 
44 #include "elm_defs.h"
45 #include "elm_globals.h"
46 #include "port_termios.h"
47 #include "s_elm.h"
48 
49 #include <assert.h>
50 #ifdef I_STDARG
51 # include <stdarg.h>
52 #else
53 # include <varargs.h>
54 #endif
55 
56 #define S_(sel, str)	catgets(elm_msg_cat, ElmSet, (sel), (str))
57 
58 #define DEFAULT_LINES 24
59 #define DEFAULT_COLS 80
60 
61 static struct termios TC_raw_tty;	/* tty settings for raw mode	*/
62 static struct termios TC_original_tty;	/* tty settings for cooked mode	*/
63 static char TC_tcapbuf[1024];		/* storage for terminal entry	*/
64 static char TC_tcapstrs[1024];		/* storage for extracted strings*/
65 static int TC_curr_line, TC_curr_col;	/* current cursor position	*/
66 
67 /* termcap capabilities */
68 static int TC_errors;
69 static char *TC_start_termcap, *TC_end_termcap;
70 static char *TC_clearscreen, *TC_cleartoeoln, *TC_cleartoeos;
71 static char *TC_moveto, *TC_up, *TC_down, *TC_right, *TC_left;
72 static char *TC_start_insert, *TC_end_insert, *TC_char_insert, *TC_pad_insert;
73 static char *TC_start_delete, *TC_end_delete, *TC_char_delete;
74 static char *TC_start_standout, *TC_end_standout;
75 static char *TC_transmit_on, *TC_transmit_off;
76 static int TC_lines, TC_columns, TC_tabspacing;
77 static int TC_automargin, TC_eatnewlineglitch;
78 
79 static void tget_complain P_((const char *, const char *));
80 
81 
82 /*
83  * Multi-byte special function keys are parsed by traversing
84  * a tree constructed of (struct knode) elements.
85  */
86 #define KCHUNK 8
87 struct knode {
88 	int kcode;		/* return value for terminal nodes	*/
89 	char *kval;		/* list of "next byte" values		*/
90 	struct knode **knext;	/* nodes corresponding to those values	*/
91 	int keylist_len;	/* number entries in above lists	*/
92 	int keylist_alloc;	/* allocated size of these lists	*/
93 };
94 
95 /* root of the multi-byte parsing tree */
96 static struct knode *Knode_root;
97 
98 static struct knode *knode_new P_((void));
99 static int knode_addkey P_((const char *, char **, int));
100 
101 static void SetScreenSize P_((void));
102 static int outchar P_((int));
103 
104 extern char *tgetstr(), *tgoto();
105 
106 
InitScreen()107 PUBLIC int InitScreen()
108 {
109     int i;
110     char *tp, *cp;
111 
112     assert(OPMODE_IS_INTERACTIVE(opmode));
113 
114     if ((cp = getenv("TERM")) == NULL) {
115 	fprintf(stderr, S_(ElmInitScreenNoTerm,
116 "You must set the \"TERM\" environment parameter so that I know what\n\
117 kind of terminal you are using.\n"));
118 	return -1;
119     }
120 
121     i = tgetent(TC_tcapbuf, cp);
122     switch (i) {
123     case 1:
124 	/* ok! */
125 	break;
126     case 0:
127 	fprintf(stderr, S_(ElmInitScreenUnknownTerm,
128 "I cannot find any information on terminal type \"%s\" in your\n\
129 termcap/terminfo database.  Please check your \"TERM\" setting.\n"), cp);
130 	return -1;
131     case -1:
132 	fprintf(stderr, S_(ElmInitScreenNoTcap,
133 "I cannot access your termcap/terminfo database to load the\n\
134 information on your terminal.\n"));
135 	return -1;
136     default:
137 	fprintf(stderr, S_(ElmInitScreenGetentFailed,
138 "Unable to retrieve termcap/terminfo database entry for your\n\
139 terminal (tgetent return code %d).\n"), i);
140 	return -1;
141     }
142 
143     /* load in termcap capabilities we need */
144     TC_errors = 0;
145     tp = TC_tcapstrs;
146 
147     TC_start_termcap = tgetstr("ti", &tp);
148     TC_end_termcap = tgetstr("te", &tp);
149 
150     TC_clearscreen = tgetstr("cl", &tp);
151     if (!TC_clearscreen)
152 	tget_complain("cl", S_(ElmInitScreenTcapcl, "clear screen"));
153     TC_cleartoeoln = tgetstr("ce", &tp);
154     if (!TC_cleartoeoln)
155 	tget_complain("ce", S_(ElmInitScreenTcapce, "clear to end of line"));
156     TC_cleartoeos = tgetstr("cd", &tp);
157     if (!TC_cleartoeos)
158 	tget_complain("cd", S_(ElmInitScreenTcapcd, "clear to end of display"));
159 
160     TC_moveto = tgetstr("cm", &tp);
161     if (!TC_moveto)
162 	tget_complain("cm", S_(ElmInitScreenTcapcm, "cursor motion"));
163 
164     TC_up = tgetstr("up", &tp);
165     if (!TC_up)
166 	tget_complain("cm", S_(ElmInitScreenTcapcu, "move cursor up"));
167 
168     TC_down = tgetstr("do", &tp);
169     if (TC_down && streq(TC_down, "\n"))
170 	TC_down = NULL;
171 
172     TC_right = tgetstr("nd", &tp);
173     if (!TC_right)
174 	tget_complain("nd", S_(ElmInitScreenTcapnd, "move cursor right"));
175 
176     TC_left = tgetstr("le", &tp);
177     if (!TC_left) {
178 	if (tgetflag("bs")) {
179 	    if ((TC_left = tgetstr("bc", &tp)) == NULL)
180 		TC_left = "\b";
181 	} else {
182 #ifdef notdef
183 	    tget_complain("le", S_(ElmInitScreenTcaple, "move cursor left"));
184 #else
185 	    /*
186 	     * In theory, we ought to be bailing out here.  If the system
187 	     * doesn't enable the "bs" flag, then it ought to define
188 	     * a "bc" capability.  In practice, some termcap entries are
189 	     * busted, and do neither when backspace should be used.
190 	     * So rather than complain, just presume backspace is ok.
191 	     */
192 	    TC_left = "\b";
193 #endif
194 	}
195     }
196 
197     TC_start_insert = tgetstr("im", &tp);
198     TC_end_insert = tgetstr("ei", &tp);
199     TC_char_insert = tgetstr("ic", &tp);
200     TC_pad_insert = tgetstr("ip", &tp);
201 
202     TC_start_delete = tgetstr("dm", &tp);
203     TC_end_delete = tgetstr("ed", &tp);
204     TC_char_delete = tgetstr("dc", &tp);
205 
206     TC_start_standout = tgetstr("so", &tp);
207     TC_end_standout = tgetstr("se", &tp);
208     TC_transmit_on = tgetstr("ks", &tp);
209     TC_transmit_off = tgetstr("ke", &tp);
210 
211     if ((TC_lines = tgetnum("li")) < 0)
212 	TC_lines = DEFAULT_LINES;
213     if ((TC_columns = tgetnum("co")) < 0)
214 	TC_columns = DEFAULT_COLS;
215     if ((TC_tabspacing = tgetnum("it")) < 0)
216 	TC_tabspacing = 8;
217 
218     TC_automargin = tgetflag("am");
219     TC_eatnewlineglitch = tgetflag("xn");
220 
221     if (TC_errors)
222 	return -1;
223 
224     /* see if environment overrides termcap screen size */
225     if ((cp = getenv("LINES")) != NULL && sscanf(cp, "%d", &i) == 1)
226 	TC_lines = i;
227     if ((cp = getenv("COLS")) != NULL && sscanf(cp, "%d", &i) == 1)
228 	TC_columns = i;
229 
230     /* get the cursor control keys... */
231     Knode_root = knode_new();
232     (void) knode_addkey("kd", &tp, KEY_DOWN);
233     (void) knode_addkey("ku", &tp, KEY_UP);
234     (void) knode_addkey("kl", &tp, KEY_LEFT);
235     (void) knode_addkey("kr", &tp, KEY_RIGHT);
236     (void) knode_addkey("kh", &tp, KEY_HOME);
237     (void) knode_addkey("kD", &tp, KEY_DC);
238     (void) knode_addkey("kI", &tp, KEY_IC);
239     (void) knode_addkey("kN", &tp, KEY_NPAGE);
240     (void) knode_addkey("kP", &tp, KEY_PPAGE);
241     (void) knode_addkey("kB", &tp, KEY_BTAB);
242     (void) knode_addkey("kH", &tp, KEY_END);	/* termcap-ish */
243     (void) knode_addkey("@7", &tp, KEY_END);	/* terminfo-ish */
244 
245     /* retrieve tty line settings */
246     if (tcgetattr(STDIN_FILENO, &TC_original_tty) < 0) {
247 	fprintf(stderr, S_(ElmInitScreenGetattrFailed,
248 	      "Cannot retrieve attributes for your terminal line. [%s]\n"),
249 	      strerror(errno));
250 	return -1;
251     }
252 
253     /* setup configuration for raw mode */
254     bcopy((char *)&TC_original_tty, (char *)&TC_raw_tty,
255 		sizeof(struct termios));
256 #if defined(TERMIO) || defined(TERMIOS)
257 #ifdef notdef /*FOO - I would like to run raw - but that breaks builtin editor*/
258     TC_raw_tty.c_lflag &= ~(ICANON|ISIG|ECHO);
259 #else
260     TC_raw_tty.c_lflag &= ~(ICANON|ECHO);
261 #endif
262     TC_raw_tty.c_cc[VMIN] = 1;
263     TC_raw_tty.c_cc[VTIME] = 0;
264 #else
265     TC_raw_tty.sg_flags &= ~(ECHO);
266 #ifdef notdef /*FOO - I would like to run raw - but that breaks builtin editor*/
267     TC_raw_tty.sg_flags |= RAW;
268 #else
269     TC_raw_tty.sg_flags |= CBREAK;
270 #endif
271 #endif
272 
273     /* setup terminal information structure */
274     Term.status = TERM_IS_INIT;
275     if (TC_start_standout && TC_end_standout)
276 	Term.status |= TERM_CAN_SO;
277     if (TC_char_delete)
278 	Term.status |= TERM_CAN_DC;
279     if (TC_start_insert || TC_char_insert)
280 	Term.status |= TERM_CAN_IC;
281 
282 #if defined(TERMIOS) || defined(TERMIO)
283     Term.erase_char = TC_original_tty.c_cc[VERASE];
284     Term.kill_char = TC_original_tty.c_cc[VKILL];
285 #ifdef VWERASE
286     Term.werase_char = TC_original_tty.c_cc[VWERASE];
287 #else
288     Term.werase_char = ctrl('W');
289 #endif
290     Term.intr_char = TC_original_tty.c_cc[VINTR];
291 #else
292     Term.erase_char = TC_original_tty.sg_erase;
293     Term.kill_char = TC_original_tty.sg_kill;
294     Term.werase_char = ctrl('W');
295     Term.intr_char = ctrl('C');
296 #endif
297 
298     /* haven't a clue where the cursor is */
299     InvalidateCursor();
300 
301     /* initialize window size */
302     SetScreenSize();
303 
304     /* force "->" if screen cannot do standout */
305     /* assuming elmrc has been processed already... */
306     if (!(Term.status & TERM_CAN_SO))
307 	arrow_cursor = TRUE;
308 
309     return 0;
310 }
311 
312 
tget_complain(capname,descrip)313 static void tget_complain(capname, descrip)
314 const char *capname;
315 const char *descrip;
316 {
317     fprintf(stderr, S_(ElmInitScreenTcapMissing,
318 		"Your terminal does not support the \"%s\" function (%s).\n"),
319 		descrip, capname);
320     ++TC_errors;
321 }
322 
323 
ShutdownTerm()324 PUBLIC void ShutdownTerm()
325 {
326     int err;
327 
328     /*
329      * This routine commonly is called on the way out due to an error.
330      * Preserve the errno status across the shutdown so that diagnostics
331      * can be produced.
332      */
333     err = errno;
334 
335     if (Term.status & TERM_IS_INIT) {
336 	softkeys_off();
337 	EnableFkeys(OFF);
338 	MoveCursor(LINES, 0);
339 	NewLine();
340 	Raw(OFF);
341     }
342     Term.status &= ~TERM_IS_INIT;
343 
344     errno = err;
345 }
346 
347 
SetScreenSize()348 static void SetScreenSize()
349 {
350     LINES = TC_lines - 1;  /* I'm sure I don't understand this lines-1 jazz */
351     COLS = TC_columns;
352 #ifdef TIOCGWINSZ
353     {
354 	struct winsize w;
355 	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
356 	    if (w.ws_row > 0)
357 		LINES = w.ws_row - 1;
358 	    if (w.ws_col > 0)
359 		COLS = w.ws_col;
360 	}
361     }
362 #endif
363 }
364 
365 
366 /*
367  * Allocate an empty (struct knode).
368  */
knode_new()369 static struct knode *knode_new()
370 {
371     struct knode *knode;
372 
373     knode = (struct knode *) safe_malloc(sizeof(struct knode));
374     knode->kcode = 0;
375     knode->kval = (char *) safe_malloc(KCHUNK);
376     knode->knext = (struct knode **) safe_malloc(KCHUNK*sizeof(struct knode *));
377     knode->keylist_len = 0;
378     knode->keylist_alloc = KCHUNK;
379    return(knode);
380 }
381 
382 
383 /*
384  * Place key rooted at "knode".  The "kstr" is the string that
385  * corresponds to the value when the key is struck, and the "kcode"
386  * is the code that should be assigned to this key.
387  */
knode_addkey(capname,tp,kcode)388 static int knode_addkey(capname, tp, kcode)
389 const char *capname;
390 char **tp;
391 int kcode;
392 {
393     int i;
394     char *kstr;
395     struct knode *knode;
396 
397     if ((kstr = tgetstr(capname, tp)) == NULL || strlen(kstr) < 2)
398 	return -1;
399 
400     knode = Knode_root;
401     while (*kstr != '\0') {
402 
403 	/* see if this character already is in the tree */
404 	for (i = 0 ; i < knode->keylist_len ; ++i) {
405 	    if (knode->kval[i] == *kstr)
406 		    break;
407 	}
408 
409 	/* nope ... need to add it */
410 	if (i >= knode->keylist_len) {
411 	    if (knode->keylist_len >= knode->keylist_alloc) {
412 		knode->keylist_alloc += KCHUNK;
413 		knode->kval = (char *) safe_realloc((malloc_t)knode->kval,
414 			    knode->keylist_alloc);
415 		knode->knext = (struct knode **) safe_realloc(
416 			    (malloc_t)knode->knext,
417 			    knode->keylist_alloc * sizeof(struct knode *));
418 	    }
419 	    assert(i == knode->keylist_len);
420 	    knode->kval[i] = *kstr;
421 	    knode->knext[i] = knode_new();
422 	    ++knode->keylist_len;
423 	}
424 
425 	/* descend into the tree */
426 	knode = knode->knext[i];
427 	++kstr;
428 
429     }
430 
431     /* place this key code at the terminal node */
432     assert(knode->keylist_len == 0);
433     knode->kcode = kcode;
434 
435     return 0;
436 }
437 
438 
439 /*
440  * Iteratively parse a multi-byte special function key.
441  *
442  * This routine should be called once with a "kval" of zero.
443  * Then it is called iteratively for each byte in the key sequence.
444  * Returns a positive value (the key code) once the key sequence is resolved.
445  * Returns 0 if more bytes are needed to resolve this key.
446  * Returns -1 if the sequence cannot be mapped to a key.
447  */
knode_parse(kval)448 PUBLIC int knode_parse(kval)
449 int kval;
450 {
451     static struct knode *knode = NULL;
452     int code, i;
453 
454     /* initialize for new key sequence */
455     if (kval == 0) {
456 	knode = Knode_root;
457 	return 0;
458     }
459 
460     assert(knode != NULL);
461 
462     /* locate this byte in the tree */
463     code = -1;
464     for (i = 0 ; i < knode->keylist_len ; ++i) {
465 	if (knode->kval[i] == (char)kval) {
466 	    knode = knode->knext[i];
467 	    code = knode->kcode;
468 	    break;
469 	}
470     }
471 
472     if (code != 0) {
473 	/* either key is resolved, or it is something we don't understand */
474 	knode = NULL;
475     }
476     return code;
477 }
478 
479 
EnableFkeys(newstate)480 PUBLIC void EnableFkeys(newstate)
481 int newstate;
482 {
483     /*
484      * Emit the sequence that some terminals require to
485      * enable/disable the special function keys.
486      */
487     if (!TC_transmit_on || !TC_transmit_off)
488 	return;
489     if (!newstate == !(Term.status & TERM_IS_FKEY))
490 	return;
491     if (newstate) {
492 	tputs(TC_transmit_on, 1, outchar);
493 	Term.status |= TERM_IS_FKEY;
494     } else {
495 	tputs(TC_transmit_off, 1, outchar);
496 	Term.status &= ~TERM_IS_FKEY;
497     }
498 /*    fflush(stdout);*/
499     FlushOutput();
500 }
501 
502 
503 #if defined(SIGWINCH) || defined(SIGCONT)
504 /* if screen size changed, update and return TRUE */
ResizeScreen()505 PUBLIC void ResizeScreen()
506 {
507     /* reset the flag that indicates a sig was caught */
508     caught_screen_change_sig = 0;
509 #ifdef SIGWINCH
510     /* reload the LINES/COLS values */
511     SetScreenSize();
512 #endif
513 }
514 #endif /* defined(SIGWINCH) || defined(SIGCONT) */
515 
516 
GetCursorPos(line_p,col_p)517 PUBLIC void GetCursorPos(line_p, col_p)
518 int *line_p, *col_p;
519 {
520     assert(TC_curr_col >= 0 && TC_curr_col < COLS);
521     assert(TC_curr_line >= 0 && TC_curr_line <= LINES);
522     *line_p = TC_curr_line;
523     *col_p = TC_curr_col;
524 }
525 
526 
InvalidateCursor()527 PUBLIC void InvalidateCursor()
528 {
529     /* indicate the cursor might not be where we think it is */
530     TC_curr_line = -1;
531     TC_curr_col = -1;
532 }
533 
534 
MoveCursor(line,col)535 PUBLIC void MoveCursor(line, col)
536 {
537     int d_col, d_line, d_total;
538     assert(col >= 0 && col < COLS && line >= 0 && line <= LINES);
539 
540     /* cursor is correct */
541     if (TC_curr_col == col && TC_curr_line == line)
542 	return;
543 
544     /* if we don't know where the cursor is right now, perform absolute move */
545     if (TC_curr_col < 0 || TC_curr_line < 0) {
546 do_absolute_motion:
547 	tputs(tgoto(TC_moveto, col, line), 1, outchar);
548 	TC_curr_col = col;
549 	TC_curr_line = line;
550 	return;
551     }
552 
553     /* move to beginning of line */
554     if (col == 0) {
555 	if (TC_curr_col > 0) {
556 	    putchar('\r');
557 	    TC_curr_col = 0;
558 	}
559 	if (TC_curr_line == line) {
560 	    /* beginning of same line */
561 	    return;
562 	}
563 	if (TC_curr_line == line-1) {
564 	    /* beginning of next line */
565 	    putchar('\n');
566 	    ++TC_curr_line;
567 	    return;
568 	}
569     }
570 
571     /* see if we are close enough so that relative motion makes sense */
572     d_col = col - TC_curr_col;
573     d_line = line - TC_curr_line;
574     d_total = (d_col < 0 ? -d_col : d_col) + (d_line < 0 ? -d_line : d_line);
575     if (d_total > 5)
576 	goto do_absolute_motion;
577 
578     /* relative horizontal motion */
579     if (d_col != 0) {
580 	assert(TC_left != NULL && TC_right != NULL);
581 	while (d_col < 0) {
582 	    /* negative value => physical > logical => must move left */
583 	    tputs(TC_left, 1, outchar);
584 	    ++d_col;
585 	}
586 	while (d_col > 0) {
587 	    /* positive value => logical > physiacl => must move right */
588 	    tputs(TC_right, 1, outchar);
589 	    --d_col;
590 	}
591 	TC_curr_col = col;
592     }
593 
594     /* relative vertical motion */
595     if (d_line != 0) {
596 	if (TC_up == NULL || TC_down == NULL)
597 	    goto do_absolute_motion;
598 	while (d_line < 0) {
599 	    /* negative value => physical > logical => must move up */
600 	    tputs(TC_up, 1, outchar);
601 	    ++d_line;
602 	}
603 	while (d_line > 0) {
604 	    /* positive value => logical > physical => must move down */
605 	    tputs(TC_down, 1, outchar);
606 	    --d_line;
607 	}
608 	TC_curr_line = line;
609     }
610 
611     assert(TC_curr_col == col);
612     assert(TC_curr_line == line);
613 }
614 
615 
ClearScreen()616 PUBLIC void ClearScreen()
617 {
618     assert(TC_clearscreen != NULL);
619     tputs(TC_clearscreen, 1, outchar);
620     TC_curr_line = TC_curr_col = 0;
621     FlushOutput();
622 }
623 
624 
CleartoEOLN()625 PUBLIC void CleartoEOLN()
626 {
627     assert(TC_cleartoeoln != NULL);
628     tputs(TC_cleartoeoln, 1, outchar);
629     FlushOutput();
630 }
631 
632 
CleartoEOS()633 PUBLIC void CleartoEOS()
634 {
635     assert(TC_cleartoeos != NULL);
636     tputs(TC_cleartoeos, 1, outchar);
637     FlushOutput();
638 }
639 
640 
StartStandout()641 PUBLIC void StartStandout()
642 {
643     assert(TC_start_standout != NULL);
644     tputs(TC_start_standout, 1, outchar);
645     FlushOutput();
646 }
647 
648 
EndStandout()649 PUBLIC void EndStandout()
650 {
651     assert(TC_end_standout != NULL);
652     tputs(TC_end_standout, 1, outchar);
653     FlushOutput();
654 }
655 
656 
WriteChar(ch)657 PUBLIC void WriteChar(ch)
658 int ch;
659 {
660 	/** write a character to the current screen location. **/
661 
662 	static int wrappedlastchar = 0;
663 	int justwrapped, nt;
664 
665 	ch &= 0xFF;
666 	justwrapped = 0;
667 
668 	/* if return, just go to left column. */
669 	if(ch == '\r') {
670 	  if (wrappedlastchar)
671 	    justwrapped = 1;                /* preserve wrap flag */
672 	  else {
673 	    putchar('\r');
674 	    TC_curr_col = 0;
675 	  }
676 	}
677 
678 	/* if newline and terminal just did a newline without our asking,
679 	 * do nothing, else output a newline and increment the line count */
680 	else if (ch == '\n') {
681 	  if (!wrappedlastchar) {
682 	    putchar('\n');
683 	    if (TC_curr_line < LINES)
684 	      ++TC_curr_line;
685 	  }
686 	}
687 
688 	/* if backspace, move back  one space  if not already in column 0 */
689 	else if (ch == BACKSPACE) {
690 	    if (TC_curr_col != 0) {
691 		assert(TC_left != NULL);
692 		tputs(TC_left, 1, outchar);
693 		TC_curr_col--;
694 	    }
695 	    else if (TC_curr_line > 0) {
696 		TC_curr_col = COLS - 1;
697 		TC_curr_line--;
698 		tputs(tgoto(TC_moveto, TC_curr_col, TC_curr_line), 1, outchar);
699 	    }
700 	    /* else BACKSPACE does nothing */
701 	}
702 
703 	/* if bell, ring the bell but don't advance the column */
704 	else if (ch == '\007') {
705 	  putchar(ch);
706 	}
707 
708 	/* if a tab, output it */
709 	else if (ch == '\t') {
710 	  if ((nt = tabstop(TC_curr_col)) >= COLS) {
711 	    putchar('\r');
712 	    putchar('\n');
713 	    TC_curr_col = 0;
714 	    ++TC_curr_line;
715 	  } else {
716 	    if (TC_tabspacing == 8) {
717 	      putchar(ch);
718 	      TC_curr_col = nt;
719 	    } else {
720 	      while (TC_curr_col < nt) {
721 		putchar(' ');
722 		++TC_curr_col;
723 	      }
724 	    }
725 	  }
726 	}
727 
728 	else {
729 	  /* if some kind of non-printable character change to a '?' */
730 #ifdef ASCII_CTYPE
731 	  if(!isascii(ch) || !isprint(ch))
732 #else
733 	  if ( (!isprint(ch) && !(ch & ~0x7f)) ||  ch == 0226)
734 /* 0226 can mess up xterm */
735 #endif
736 	    ch = '?';
737 
738 	  /* if we only have one column left, simulate automargins if
739 	   * the terminal doesn't have them */
740 	  if (TC_curr_col == COLS - 1) {
741 	    putchar(ch);
742 	    if (!TC_automargin || TC_eatnewlineglitch) {
743 	      putchar('\r');
744 	      putchar('\n');
745 	    }
746 	    if (TC_curr_line < LINES)
747 	      ++TC_curr_line;
748 	    TC_curr_col = 0;
749 	    justwrapped = 1;
750 	  }
751 
752 	  /* if we are here this means we have no interference from the
753 	   * right margin - just output the character and increment the
754 	   * column position. */
755 	  else {
756 	    putchar(ch);
757 	    TC_curr_col++;
758 	  }
759 	}
760 
761 	wrappedlastchar = justwrapped;
762 }
763 
764 
765 /*
766  * Insert a character at the current screen location, shifting the rest
767  * of the line right one column (char in last col is lost).  Returns 0
768  * if insert successful, -1 if insert capability not available.  Can be
769  * invoked with a negative value to check whether insert can be done
770  * on this terminal.
771  *
772  * This routine is guaranteed for only a *limited* set of circumstances.
773  * The character must be a plain printing character, and the insertion
774  * cannot occur in the last column of the line.
775  */
InsertChar(ch)776 PUBLIC void InsertChar(ch)
777 int ch;
778 {
779     assert(TC_start_insert || TC_char_insert);
780     assert(isprint(ch) && TC_curr_col < COLS-1);
781     if (TC_start_insert && *TC_start_insert)
782 	tputs(TC_start_insert, 1, outchar);
783     if (TC_char_insert && *TC_char_insert)
784 	tputs(TC_char_insert, 1, outchar);
785     outchar(ch);
786     ++TC_curr_col;
787     if (TC_pad_insert && *TC_pad_insert)
788 	tputs(TC_pad_insert, 1, outchar);
789     if (TC_end_insert && *TC_end_insert)
790 	tputs(TC_end_insert, 1, outchar);
791 }
792 
793 
794 /*
795  * Delete a number characters from the display starting at the current
796  * location, shifting the rest of the line left to fill in the deleted
797  * spaces.  Returns 0 if deletion successful, -1 if delete capability
798  * not available.  Can be invoked with a negative value to check whether
799  * delete can be done on this terminal.
800  */
DeleteChar(n)801 PUBLIC void DeleteChar(n)
802 int n;
803 {
804     assert (TC_char_delete && n > 0);
805     if (TC_start_delete && *TC_start_delete)
806 	tputs(TC_start_delete, 1, outchar);
807     while (--n >= 0)
808 	tputs(TC_char_delete, 1, outchar);
809     if (TC_end_delete && *TC_end_delete)
810 	tputs(TC_end_delete, 1, outchar);
811 }
812 
813 
Raw(state)814 PUBLIC void Raw(state)
815 int state;
816 {
817     int do_tite;
818 
819     do_tite = !(state & NO_TITE);
820     state &= ~NO_TITE;
821 
822     /* don't bother if already in desired state */
823     if (!state == !(Term.status & TERM_IS_RAW))
824 	return;
825 
826     FlushOutput();
827     if (state) {
828 	(void) tcsetattr(STDIN_FILENO, TCSADRAIN, &TC_raw_tty);
829 	if (do_tite && use_tite && TC_start_termcap)
830 	    tputs(TC_start_termcap, 1, outchar);
831 	Term.status |= TERM_IS_RAW;
832     } else {
833 	if (do_tite && use_tite && TC_end_termcap) {
834 	    tputs(TC_end_termcap, 1, outchar);
835 	    fflush(stdout);
836 	}
837 	(void) tcsetattr(STDIN_FILENO, TCSADRAIN, &TC_original_tty);
838 	Term.status &= ~TERM_IS_RAW;
839     }
840 }
841 
842 
ReadCh()843 PUBLIC int ReadCh()
844 {
845     /*
846      *	read a character with Raw mode set!
847      *
848      *	EAGAIN & EWOULDBLOCK are recognized just in case
849      *	O_NONBLOCK happens to be in effect.
850      */
851 
852     int ch;
853 
854     FlushOutput();
855     while ((ch = getchar()) <= 0) {
856 	if (errno == EINTR)
857 	    continue;
858 #ifdef	EAGAIN
859 	if (errno == EAGAIN)
860 	    continue;
861 #endif
862 #ifdef	EWOULDBLOCK
863 	if (errno == EWOULDBLOCK)
864 	    continue;
865 #endif
866 	ShutdownTerm();
867 	if (feof(stdin))
868 	    error("Unexepcted EOF from terminal!");
869 	else
870 	    error1("Error reading terminal! [%s]", strerror(errno));
871 	leave(LEAVE_EMERGENCY);
872     }
873     return ch;
874 }
875 
876 
UnreadCh(ch)877 PUBLIC void UnreadCh(ch)
878 int ch;
879 {
880     ungetc(ch, stdin);
881 }
882 
883 
FlushOutput()884 PUBLIC void FlushOutput()
885 {
886     (void) fflush(stdout);
887     (void) fflush(stderr);
888 }
889 
890 
FlushInput()891 PUBLIC void FlushInput()
892 {
893     (void) fflush(stdin);
894     (void) tcflush(STDIN_FILENO, TCIFLUSH);
895 }
896 
897 
outchar(c)898 static int outchar(c)
899 int c;
900 {
901     /** output the given character.  From tputs... **/
902     /** Note: this CANNOT be a macro!              **/
903 
904     return putc(c, stdout);
905 }
906 
907 
908 
909 static void debug_termtitle P_((int, int, const char *));
910 static void debug_termstr P_((int, int, const char *, const char *));
911 static void debug_termnum P_((int, int, const char *, int));
912 static void debug_termflag P_((int, int, const char *, int));
913 
debug_terminal()914 PUBLIC void debug_terminal()
915 {
916     int line;
917 
918     ClearScreen();
919     CenterLine(0, "--- Debug Display - Terminal Settings ---");
920 
921     line = 2;
922     debug_termstr(line, 0, "start_termcap", TC_start_termcap);
923     debug_termstr(line, 1, "end_termcap", TC_end_termcap);
924     ++line;
925     debug_termstr(line, 0, "start_standout", TC_start_standout);
926     debug_termstr(line, 1, "end_standout", TC_end_standout);
927     ++line;
928     debug_termstr(line, 0, "transmit_on", TC_transmit_on);
929     debug_termstr(line, 1, "transmit_off", TC_transmit_off);
930 
931     line += 2;
932     debug_termflag(line, 0, "ic supported?", !!(Term.status & TERM_CAN_IC));
933     ++line;
934     debug_termstr(line, 0, "start_insert", TC_start_insert);
935     debug_termstr(line, 1, "end_insert", TC_end_insert);
936     ++line;
937     debug_termstr(line, 0, "char_insert", TC_char_insert);
938     debug_termstr(line, 1, "pad_insert", TC_pad_insert);
939 
940     line += 2;
941     debug_termflag(line, 0, "dc supported?", !!(Term.status & TERM_CAN_DC));
942     ++line;
943     debug_termstr(line, 0, "start_delete", TC_start_delete);
944     debug_termstr(line, 1, "end_delete", TC_end_delete);
945     ++line;
946     debug_termstr(line, 0, "char_delete", TC_char_delete);
947 
948     line += 2;
949     debug_termstr(line, 0, "moveto", TC_moveto);
950     ++line;
951     debug_termstr(line, 0, "up", TC_up);
952     debug_termstr(line, 1, "down", TC_down);
953     ++line;
954     debug_termstr(line, 0, "left", TC_left);
955     debug_termstr(line, 1, "right", TC_right);
956 
957     line += 2;
958     debug_termnum(line, 0, "lines", TC_lines);
959     debug_termnum(line, 1, "columns", TC_columns);
960     ++line;
961     debug_termnum(line, 0, "tabspacing", TC_tabspacing);
962 }
963 
964 
debug_termtitle(line,col,title)965 static void debug_termtitle(line, col, title)
966 int line, col;
967 const char *title;
968 {
969     PutLine0(line, col*40, title);
970     MoveCursor(line, col*40+16);
971     return;
972 }
973 
974 
debug_termstr(line,col,title,value)975 static void debug_termstr(line, col, title, value)
976 int line, col;
977 const char *title, *value;
978 {
979     debug_termtitle(line, col, title);
980     if (value == NULL) {
981 	PutLine0(-1, -1, "(null)");
982 	return;
983     }
984     if (*value == '\0') {
985 	PutLine0(-1, -1, "(empty)");
986 	return;
987     }
988     for ( ; *value ; ++value) {
989 	if (isprint(*value) || isspace(*value)) {
990 	    WriteChar(*value);
991 	} else if (*value < 040) {
992 	    WriteChar('^');
993 	    WriteChar(*value | 0100);
994 	} else {
995 	    PutLine1(-1, -1, "\\%03o", *value);
996 	}
997     }
998 }
999 
1000 
debug_termnum(line,col,title,value)1001 static void debug_termnum(line, col, title, value)
1002 int line, col;
1003 const char *title;
1004 int value;
1005 {
1006     debug_termtitle(line, col, title);
1007     PutLine1(-1, -1, "%d", value);
1008 }
1009 
1010 
debug_termflag(line,col,title,value)1011 static void debug_termflag(line, col, title, value)
1012 int line, col;
1013 const char *title;
1014 int value;
1015 {
1016     debug_termtitle(line, col, title);
1017     PutLine0(-1, -1, (value ? "Yes" : "No"));
1018 }
1019 
1020