1 /* term.c
2  */
3 /* This software is copyrighted as detailed in the LICENSE file. */
4 
5 
6 #include "EXTERN.h"
7 #include "common.h"
8 #include "list.h"
9 #include "env.h"
10 #include "util.h"
11 #include "util2.h"
12 #include "final.h"
13 #include "help.h"
14 #include "hash.h"
15 #include "cache.h"
16 #include "opt.h"
17 #include "ngdata.h"
18 #include "nntpclient.h"
19 #include "datasrc.h"
20 #include "intrp.h"
21 #include "init.h"
22 #include "art.h"
23 #include "rt-select.h"
24 #ifdef SCORE
25 #include "score.h"		/* for sc_lookahead */
26 #endif
27 #ifdef SCAN
28 #include "scan.h"
29 #include "sdisp.h"
30 #endif
31 #ifdef SCAN_ART
32 #include "scanart.h"
33 #endif
34 #ifdef USE_TK
35 #include "tkstuff.h"
36 #endif
37 #include "univ.h"
38 #include "color.h"
39 #include "INTERN.h"
40 #include "term.h"
41 #include "term.ih"
42 
43 #ifdef u3b2
44 #undef TIOCGWINSZ
45 #endif
46 
47 #undef	USETITE		/* use terminal init/exit seqences (not recommended) */
48 #undef	USEKSKE		/* use keypad start/end sequences */
49 
50 char tcarea[TCSIZE];	/* area for "compiled" termcap strings */
51 
52 static KEYMAP*	topmap INIT(NULL);
53 
54 static char* lines_export = NULL;
55 static char* cols_export = NULL;
56 
57 static int leftcost, upcost;
58 static bool got_a_char = FALSE;	/* TRUE if we got a char since eating */
59 
60 /* guarantee capability pointer != NULL */
61 /* (I believe terminfo will ignore the &tmpaddr argument.) */
62 
63 char* tgetstr();
64 #define Tgetstr(key) ((tmpstr = tgetstr(key,&tmpaddr)) ? tmpstr : nullstr)
65 
66 /* terminal initialization */
67 
68 void
term_init()69 term_init()
70 {
71     savetty();				/* remember current tty state */
72 
73 #ifdef I_TERMIO
74     outspeed = _tty.c_cflag & CBAUD;	/* for tputs() */
75     ERASECH = _tty.c_cc[VERASE];	/* for finish_command() */
76     KILLCH = _tty.c_cc[VKILL];		/* for finish_command() */
77     if (tc_GT = ((_tty.c_oflag & TABDLY) != TAB3))
78 	/* we have tabs, so that's OK */;
79     else
80 	_tty.c_oflag &= ~TAB3;	/* turn off kernel tabbing -- done in rn */
81 #else /* !I_TERMIO */
82 # ifdef I_TERMIOS
83     outspeed = cfgetospeed(&_tty);	/* for tputs() (output) */
84     ERASECH = _tty.c_cc[VERASE];	/* for finish_command() */
85     KILLCH = _tty.c_cc[VKILL];		/* for finish_command() */
86 #if 0
87     _tty.c_oflag &= ~OXTABS;	/* turn off kernel tabbing-done in rn */
88 #endif
89 # else /* !I_TERMIOS */
90 #  ifdef I_SGTTY
91     outspeed = _tty.sg_ospeed;		/* for tputs() */
92     ERASECH = _tty.sg_erase;		/* for finish_command() */
93     KILLCH = _tty.sg_kill;		/* for finish_command() */
94     if (tc_GT = ((_tty.sg_flags & XTABS) != XTABS))
95 	/* we have tabs, so that's OK */;
96     else
97 	_tty.sg_flags &= ~XTABS;
98 #  else /* !I_SGTTY */
99 #   ifdef MSDOS
100     outspeed = B19200;
101     ERASECH = '\b';
102     KILLCH = Ctl('u');
103     tc_GT = 1;
104 #   else
105     ..."Don't know how to initialize the terminal!"
106 #   endif /* !MSDOS */
107 #  endif /* !I_SGTTY */
108 # endif /* !I_TERMIOS */
109 #endif /* !I_TERMIO */
110 
111     /* The following could be a table but I can't be sure that there isn't */
112     /* some degree of sparsity out there in the world. */
113 
114     switch (outspeed) {			/* 1 second of padding */
115 #ifdef BEXTA
116         case BEXTA:  just_a_sec = 1920; break;
117 #else
118 #ifdef B19200
119         case B19200: just_a_sec = 1920; break;
120 #endif
121 #endif
122         case B9600:  just_a_sec =  960; break;
123         case B4800:  just_a_sec =  480; break;
124         case B2400:  just_a_sec =  240; break;
125         case B1800:  just_a_sec =  180; break;
126         case B1200:  just_a_sec =  120; break;
127         case B600:   just_a_sec =   60; break;
128 	case B300:   just_a_sec =   30; break;
129 	/* do I really have to type the rest of this??? */
130         case B200:   just_a_sec =   20; break;
131         case B150:   just_a_sec =   15; break;
132         case B134:   just_a_sec =   13; break;
133         case B110:   just_a_sec =   11; break;
134         case B75:    just_a_sec =    8; break;
135         case B50:    just_a_sec =    5; break;
136         default:     just_a_sec =  960; break;
137 					/* if we are running detached I */
138     }					/*  don't want to know about it! */
139 }
140 
141 #ifdef PENDING
142 # if !defined(FIONREAD) && !defined(HAS_RDCHK) && !defined(MSDOS)
143 int devtty;
144 # endif
145 #endif
146 
147 /* set terminal characteristics */
148 
149 void
term_set(tcbuf)150 term_set(tcbuf)
151 char* tcbuf;		/* temp area for "uncompiled" termcap entry */
152 {
153     char* tmpaddr;			/* must not be register */
154     register char* tmpstr;
155     char* s;
156     int status;
157 #ifdef TIOCGWINSZ
158     struct winsize winsize;
159 #endif
160 
161 #ifdef PENDING
162 #if !defined (FIONREAD) && !defined (HAS_RDCHK) && !defined(MSDOS)
163     /* do no delay reads on something that always gets closed on exit */
164 
165     devtty = fileno(stdin);
166     if (isatty(devtty)) {
167 	devtty = open("/dev/tty",0);
168 	if (devtty < 0) {
169 	    printf(cantopen,"/dev/tty") FLUSH;
170 	    finalize(1);
171 	}
172 	fcntl(devtty,F_SETFL,O_NDELAY);
173     }
174 #endif
175 #endif
176 
177     /* get all that good termcap stuff */
178 
179 #ifdef HAS_TERMLIB
180 #ifdef MSDOS
181     tc_BC = "\b";
182     tc_UP = "\033[A";
183     tc_CR = "\r";
184     tc_VB = nullstr;
185     tc_CL = "\033[H\033[2J";
186     tc_CE = "\033[K";
187     tc_TI = nullstr;
188     tc_TE = nullstr;
189     tc_KS = nullstr;
190     tc_KE = nullstr;
191     tc_CM = "\033[%d;%dH";
192     tc_HO = "\033[H";
193     tc_IL = nullstr; /*$$*/
194     tc_CD = nullstr; /*$$*/
195     tc_SO = "\033[7m";
196     tc_SE = "\033[m";
197     tc_US = "\033[7m";
198     tc_UE = "\033[m";
199     tc_UC = nullstr;
200     set_lines_and_cols();
201     tc_AM = TRUE;
202 #else
203     s = getenv("TERM");
204     status = tgetent(tcbuf,s? s : "dumb");	/* get termcap entry */
205     if (status < 1) {
206 	printf("No termcap %s found.\n", status ? "file" : "entry") FLUSH;
207 	finalize(1);
208     }
209     tmpaddr = tcarea;			/* set up strange tgetstr pointer */
210     s = Tgetstr("pc");			/* get pad character */
211     tc_PC = *s;				/* get it where tputs wants it */
212     if (!tgetflag("bs")) {		/* is backspace not used? */
213 	tc_BC = Tgetstr("bc");		/* find out what is */
214 	if (tc_BC == nullstr) {		/* terminfo grok's 'bs' but not 'bc' */
215 	    tc_BC = Tgetstr("le");
216 	    if (tc_BC == nullstr)
217 		tc_BC = "\b";		/* better than nothing... */
218 	}
219     } else
220 	tc_BC = "\b";			/* make a backspace handy */
221     tc_UP = Tgetstr("up");		/* move up a line */
222     tc_CL = Tgetstr("cl");		/* get clear string */
223     tc_CE = Tgetstr("ce");		/* clear to end of line string */
224     tc_TI = Tgetstr("ti");		/* initialize display */
225     tc_TE = Tgetstr("te");		/* reset display */
226     tc_KS = Tgetstr("ks");		/* enter `keypad transmit' mode */
227     tc_KE = Tgetstr("ke");		/* exit `keypad transmit' mode */
228     tc_HO = Tgetstr("ho");		/* home cursor */
229     tc_IL = Tgetstr("al");		/* insert (add) line */
230     tc_CM = Tgetstr("cm");		/* cursor motion */
231     tc_CD = Tgetstr("cd");		/* clear to end of display */
232     if (!*tc_CE)
233 	tc_CE = tc_CD;
234     tc_SO = Tgetstr("so");		/* begin standout */
235     tc_SE = Tgetstr("se");		/* end standout */
236     if ((tc_SG = tgetnum("sg"))<0)
237 	tc_SG = 0;			/* blanks left by SG, SE */
238     tc_US = Tgetstr("us");		/* start underline */
239     tc_UE = Tgetstr("ue");		/* end underline */
240     if ((tc_UG = tgetnum("ug"))<0)
241 	tc_UG = 0;			/* blanks left by US, UE */
242     if (*tc_US)
243 	tc_UC = nullstr;		/* UC must not be NULL */
244     else
245 	tc_UC = Tgetstr("uc");		/* underline a character */
246     if (!*tc_US && !*tc_UC) {		/* no underline mode? */
247 	tc_US = tc_SO;			/* substitute standout mode */
248 	tc_UE = tc_SE;
249 	tc_UG = tc_SG;
250     }
251     tc_LINES = tgetnum("li");		/* lines per page */
252     tc_COLS = tgetnum("co");		/* columns on page */
253 
254 #ifdef TIOCGWINSZ
255     { struct winsize ws;
256 	if (ioctl(0, TIOCGWINSZ, &ws) >= 0 && ws.ws_row > 0 && ws.ws_col > 0) {
257 	    tc_LINES = ws.ws_row;
258 	    tc_COLS = ws.ws_col;
259 	}
260     }
261 #endif
262 
263     tc_AM = tgetflag("am");		/* terminal wraps automatically? */
264     tc_XN = tgetflag("xn");		/* then eats next newline? */
265     tc_VB = Tgetstr("vb");
266     if (!*tc_VB)
267 	tc_VB = "\007";
268     tc_CR = Tgetstr("cr");
269     if (!*tc_CR) {
270 	if (tgetflag("nc") && *tc_UP) {
271 	    tc_CR = safemalloc((MEM_SIZE)strlen(tc_UP)+2);
272 	    sprintf(tc_CR,"%s\r",tc_UP);
273 	}
274 	else
275 	    tc_CR = "\r";
276     }
277 #ifdef TIOCGWINSZ
278 	if (ioctl(1, TIOCGWINSZ, &winsize) >= 0) {
279 		if (winsize.ws_row > 0)
280 		    tc_LINES = winsize.ws_row;
281 		if (winsize.ws_col > 0)
282 		    tc_COLS = winsize.ws_col;
283 	}
284 # endif
285 #endif
286     if (!*tc_UP)			/* no UP string? */
287 	marking = 0;			/* disable any marking */
288     if (*tc_CM || *tc_HO)
289 	can_home = TRUE;
290     if (!*tc_CD || !can_home)		/* can we CE, CD, and home? */
291 	erase_each_line = FALSE;	/*  no, so disable use of clear eol */
292     if (muck_up_clear)			/* this is for weird HPs */
293 	tc_CL = NULL;
294     leftcost = strlen(tc_BC);
295     upcost = strlen(tc_UP);
296 #else /* !HAS_TERMLIB */
297     ..."Don't know how to set the terminal!"
298 #endif /* !HAS_TERMLIB */
299     termlib_init();
300     line_col_calcs();
301     noecho();				/* turn off echo */
302     crmode();				/* enter cbreak mode */
303     sprintf(buf, "%d", tc_LINES);
304     lines_export = export("LINES",buf);
305     sprintf(buf, "%d", tc_COLS);
306     cols_export = export("COLUMNS",buf);
307 
308     mac_init(tcbuf);
309 }
310 
311 #ifdef MSDOS
312 static void
set_lines_and_cols()313 set_lines_and_cols()
314 {
315     gotoxy(132,1);
316     if (wherex() == 132)
317 	tc_COLS = 132;
318     else
319 	tc_COLS = 80;
320     gotoxy(1,50);
321     if (wherey() == 50)
322 	tc_LINES = 50;
323     else {
324 	gotoxy(1,43);
325 	if (wherey() == 50)
326 	    tc_LINES = 50;
327 	else
328 	    tc_LINES = 25;
329     }
330 }
331 #endif
332 
333 void
set_macro(seq,def)334 set_macro(seq,def)
335 char* seq;	/* input sequence of keys */
336 char* def;	/* definition */
337 {
338     mac_line(def,seq,0);
339     /* check for common (?) brain damage: ku/kd/etc sequence may be the
340      * cursor move sequence instead of the input sequence.
341      * (This happens on the local xterm definitions.)
342      * Try to recognize and adjust for this case.
343      */
344     if (seq[0] == '\033' && seq[1] == '[' && seq[2]) {
345 	char lbuf[LBUFLEN];	/* copy of possibly non-writable string */
346 	strcpy(lbuf,seq);
347 	lbuf[1] = 'O';
348 	mac_line(def,lbuf,0);
349     }
350     if (seq[0] == '\033' && seq[1] == 'O' && seq[2]) {
351 	char lbuf[LBUFLEN];	/* copy of possibly non-writable string */
352 	strcpy(lbuf,seq);
353 	lbuf[1] = '[';
354 	mac_line(def,lbuf,0);
355     }
356 }
357 
358 char* up[] = {
359     "^@",
360     /* '(' at article or pager, '[' in thread sel, 'p' otherwise */
361     "%(%m=[ap]?\\(:%(%m=t?[:p))",
362     /* '(' at article or pager, '[' in thread sel, 'p' otherwise */
363     "%(%m=[ap]?\\(:%(%m=t?[:p))"
364 };
365 char* down[] = {
366     "^@",
367     /* ')' at article or pager, ']' in thread sel, 'n' otherwise */
368     "%(%m=[ap]?\\):%(%m=t?]:n))",
369     /* ')' at article or pager, ']' in thread sel, 'n' otherwise */
370     "%(%m=[ap]?\\):%(%m=t?]:n))"
371 };
372 char* left[] = {
373     "^@",
374     /* '[' at article or pager, 'Q' otherwise */
375     "%(%m=[ap]?\\[:Q)",
376     /* '[' at article or pager, '<' otherwise */
377     "%(%m=[ap]?\\[:<)"
378 };
379 char* right[] = {
380     "^@",
381     /* ']' at article or pager, CR otherwise */
382     "%(%m=[ap]?\\]:^j)",
383     /* CR at newsgroups, ']' at article or pager, '>' otherwise */
384     "%(%m=n?^j:%(%m=[ap]?\\]:>))"
385 };
386 
387 /* Turn the arrow keys into macros that do some basic trn functions.
388 ** Code provided by Clifford Adams.
389 */
390 void
arrow_macros(tmpbuf)391 arrow_macros(tmpbuf)
392 char* tmpbuf;
393 {
394 #ifdef HAS_TERMLIB
395     char lbuf[256];			/* should be long enough */
396 #ifndef MSDOS
397     char* tmpaddr = tmpbuf;
398 #endif
399     register char* tmpstr;
400 
401     /* If arrows are defined as single keys, we probably don't
402      * want to redefine them.  (The tvi912c defines kl as ^H)
403      */
404 #ifdef MSDOS
405     strcpy(lbuf,"\035\110");
406 #else
407     strcpy(lbuf,Tgetstr("ku"));		/* up */
408 #endif
409     if ((int)strlen(lbuf) > 1)
410 	set_macro(lbuf,up[auto_arrow_macros]);
411 
412 #ifdef MSDOS
413     strcpy(lbuf,"\035\120");
414 #else
415     strcpy(lbuf,Tgetstr("kd"));		/* down */
416 #endif
417     if ((int)strlen(lbuf) > 1)
418 	set_macro(lbuf,down[auto_arrow_macros]);
419 
420 #ifdef MSDOS
421     strcpy(lbuf,"\035\113");
422 #else
423     strcpy(lbuf,Tgetstr("kl"));		/* left */
424 #endif
425     if ((int)strlen(lbuf) > 1)
426 	set_macro(lbuf,left[auto_arrow_macros]);
427 
428 #ifdef MSDOS
429     strcpy(lbuf,"\035\115");
430 #else
431     strcpy(lbuf,Tgetstr("kr"));		/* right */
432 #endif
433     if ((int)strlen(lbuf) > 1)
434 	set_macro(lbuf,right[auto_arrow_macros]);
435 
436     if (*lbuf == '\033')
437 	set_macro("\033\033", "\033");
438 #endif
439 }
440 
441 static void
mac_init(tcbuf)442 mac_init(tcbuf)
443 char* tcbuf;
444 {
445     char tmpbuf[1024];
446 
447     if (auto_arrow_macros)
448 	arrow_macros(tmpbuf);
449     if (!use_threads
450      || (tmpfp = fopen(filexp(getval("TRNMACRO",TRNMACRO)),"r")) == NULL)
451 	tmpfp = fopen(filexp(getval("RNMACRO",RNMACRO)),"r");
452     if (tmpfp) {
453 	while (fgets(tcbuf,TCBUF_SIZE,tmpfp) != NULL)
454 	    mac_line(tcbuf,tmpbuf,sizeof tmpbuf);
455 	fclose(tmpfp);
456     }
457 }
458 
459 void
mac_line(line,tmpbuf,tbsize)460 mac_line(line,tmpbuf,tbsize)
461 char* line;
462 char* tmpbuf;
463 int tbsize;
464 {
465     register char* s;
466     register char* m;
467     register KEYMAP* curmap;
468     register int ch;
469     register int garbage = 0;
470     static char override[] = "\nkeymap overrides string\n";
471 
472     if (topmap == NULL)
473 	topmap = newkeymap();
474     if (*line == '#' || *line == '\n')
475 	return;
476     if (line[ch = strlen(line)-1] == '\n')
477 	line[ch] = '\0';
478     /* A 0 length signifies we already parsed the macro into tmpbuf,
479     ** so line is just the definition. */
480     if (tbsize)
481 	m = dointerp(tmpbuf,tbsize,line," \t",(char*)NULL);
482     else
483 	m = line;
484     if (!*m)
485 	return;
486     while (*m == ' ' || *m == '\t') m++;
487     for (s=tmpbuf,curmap=topmap; *s; s++) {
488 	ch = *s & 0177;
489 	if (s[1] == '+' && isdigit(s[2])) {
490 	    s += 2;
491 	    garbage = (*s & KM_GMASK) << KM_GSHIFT;
492 	}
493 	else
494 	    garbage = 0;
495 	if (s[1]) {
496 	    if ((curmap->km_type[ch] & KM_TMASK) == KM_STRING) {
497 		if (tbsize) {
498 		    fputs(override,stdout) FLUSH;
499 		    termdown(2);
500 		}
501 		free(curmap->km_ptr[ch].km_str);
502 		curmap->km_ptr[ch].km_str = NULL;
503 	    }
504 	    curmap->km_type[ch] = KM_KEYMAP + garbage;
505 	    if (curmap->km_ptr[ch].km_km == NULL)
506 		curmap->km_ptr[ch].km_km = newkeymap();
507 	    curmap = curmap->km_ptr[ch].km_km;
508 	}
509 	else {
510 	    if (tbsize && (curmap->km_type[ch] & KM_TMASK) == KM_KEYMAP) {
511 		fputs(override,stdout) FLUSH;
512 		termdown(2);
513 	    }
514 	    else {
515 		curmap->km_type[ch] = KM_STRING + garbage;
516 		curmap->km_ptr[ch].km_str = savestr(m);
517 	    }
518 	}
519     }
520 }
521 
522 static KEYMAP*
newkeymap()523 newkeymap()
524 {
525     register int i;
526     register KEYMAP* map;
527 
528 #ifndef lint
529     map = (KEYMAP*)safemalloc(sizeof(KEYMAP));
530 #else
531     map = NULL;
532 #endif /* lint */
533     for (i = 127; i >= 0; i--) {
534 	map->km_ptr[i].km_km = NULL;
535 	map->km_type[i] = KM_NOTHIN;
536     }
537     return map;
538 }
539 
540 void
show_macros()541 show_macros()
542 {
543     char prebuf[64];
544 
545     if (topmap != NULL) {
546 	print_lines("Macros:\n",STANDOUT);
547 	*prebuf = '\0';
548 	show_keymap(topmap,prebuf);
549     }
550     else {
551 	print_lines("No macros defined.\n", NOMARKING);
552     }
553 }
554 
555 static void
show_keymap(curmap,prefix)556 show_keymap(curmap,prefix)
557 register KEYMAP* curmap;
558 char* prefix;
559 {
560     register int i;
561     register char* next = prefix + strlen(prefix);
562     register int kt;
563 
564     for (i = 0; i < 128; i++) {
565 	if ((kt = curmap->km_type[i]) != 0) {
566 	    if (i < ' ')
567 		sprintf(next,"^%c",i+64);
568 	    else if (i == ' ')
569 		strcpy(next,"\\040");
570 	    else if (i == 127)
571 		strcpy(next,"^?");
572 	    else
573 		sprintf(next,"%c",i);
574 	    if ((kt >> KM_GSHIFT) & KM_GMASK) {
575 		sprintf(cmd_buf,"+%d", (kt >> KM_GSHIFT) & KM_GMASK);
576 		strcat(next,cmd_buf);
577 	    }
578 	    switch (kt & KM_TMASK) {
579 	      case KM_NOTHIN:
580 		sprintf(cmd_buf,"%s	%c\n",prefix,i);
581 		print_lines(cmd_buf,NOMARKING);
582 		break;
583 	      case KM_KEYMAP:
584 		show_keymap(curmap->km_ptr[i].km_km, prefix);
585 		break;
586 	      case KM_STRING:
587 		sprintf(cmd_buf,"%s	%s\n",prefix,curmap->km_ptr[i].km_str);
588 		print_lines(cmd_buf,NOMARKING);
589 		break;
590 	      case KM_BOGUS:
591 		sprintf(cmd_buf,"%s	BOGUS\n",prefix);
592 		print_lines(cmd_buf,STANDOUT);
593 		break;
594 	    }
595 	}
596     }
597 }
598 
599 void
set_mode(new_gmode,new_mode)600 set_mode(new_gmode, new_mode)
601 char_int new_gmode;
602 char_int new_mode;
603 {
604     if (gmode != new_gmode || mode != new_mode) {
605 	gmode = new_gmode;
606 	mode = new_mode;
607 	xmouse_check();
608     }
609 }
610 
611 /* routine to pass to tputs */
612 
613 int
putchr(ch)614 putchr(ch)
615 register char_int ch;
616 {
617     putchar(ch);
618 #ifdef lint
619     ch = '\0';
620     ch = ch;
621 #endif
622     return 0;
623 }
624 
625 int not_echoing = 0;
626 
627 void
hide_pending()628 hide_pending()
629 {
630     not_echoing = 1;
631     pushchar(0200);
632 }
633 
634 bool
finput_pending(check_term)635 finput_pending(check_term)
636 bool_int check_term;
637 {
638     while (nextout != nextin) {
639 	if (circlebuf[nextout] != '\200')
640 	    return 1;
641 	switch (not_echoing) {
642 	  case 0:
643 	    return 1;
644 	  case 1:
645 	    nextout++;
646 	    nextout %= PUSHSIZE;
647 	    not_echoing = 0;
648 	    break;
649 	  default:
650 	    circlebuf[nextout] = '\n';
651 	    not_echoing = 0;
652 	    return 1;
653 	}
654     }
655 #ifdef PENDING
656 #ifdef USE_TK
657     if (check_term && ttk_running) {
658 	ttk_do_waiting_events();	/* Update screen, process events. */
659 	if (ttk_keys && *ttk_keys)
660 	    return 1;
661     }
662 #endif
663     if (check_term) {
664 # ifdef FIONREAD
665 	int iocount;
666 	ioctl(0, FIONREAD, &iocount);
667 	return iocount;
668 # else /* !FIONREAD */
669 #  ifdef HAS_RDCHK
670 	return rdchk(0);
671 #  else /* !HAS_RDCHK */
672 #   ifdef MSDOS
673 	return kbhit();
674 #   else /* !MSDOS */
675 	return circfill();
676 #   endif /* !MSDOS */
677 #  endif /* !HAS_RDCHK */
678 #  endif /* !FIONREAD */
679     }
680 # endif /* !PENDING */
681     return 0;
682 }
683 
684 /* input the 2nd and succeeding characters of a multi-character command */
685 /* returns TRUE if command finished, FALSE if they rubbed out first character */
686 
687 int buflimit = LBUFLEN;
688 
689 bool
finish_command(donewline)690 finish_command(donewline)
691 int donewline;
692 {
693     register char* s;
694     char gmode_save = gmode;
695 
696     s = buf;
697     if (s[1] != FINISHCMD)		/* someone faking up a command? */
698 	return TRUE;
699     set_mode('i',mode);
700     if (not_echoing)
701 	not_echoing = 2;
702     do {
703 	s = edit_buf(s, buf);
704 	if (s == buf) {			/* entire string gone? */
705 	    fflush(stdout);		/* return to single char command mode */
706 	    set_mode(gmode_save,mode);
707 	    return FALSE;
708 	}
709 	if (s - buf == buflimit)
710 	    break;
711 	fflush(stdout);
712 	getcmd(s);
713 	if (errno || *s == '\f') {
714 	    *s = Ctl('r');		/* force rewrite on CONT */
715 	}
716     } while (*s != '\r' && *s != '\n'); /* until CR or NL (not echoed) */
717     mouse_is_down = FALSE;
718 
719     while (s[-1] == ' ') s--;
720     *s = '\0';				/* terminate the string nicely */
721 
722     if (donewline)
723 	newline();
724 
725     set_mode(gmode_save,mode);
726     return TRUE;			/* retrn success */
727 }
728 
729 static int
echo_char(ch)730 echo_char(ch)
731 char_int ch;
732 {
733     if (((Uchar)ch & 0x7F) < ' ') {
734 	putchar('^');
735 	putchar((ch & 0x7F) | 64);
736 	return 2;
737     }
738     if (ch == '\177') {
739 	putchar('^');
740 	putchar('?');
741 	return 2;
742     }
743     putchar(ch);
744     return 1;
745 }
746 
747 static bool screen_is_dirty; /*$$ remove this? */
748 
749 /* Process the character *s in the buffer buf returning the new 's' */
750 
751 char*
edit_buf(s,cmd)752 edit_buf(s, cmd)
753 register char* s;
754 char* cmd;
755 {
756     static bool quoteone = FALSE;
757     if (quoteone) {
758 	quoteone = FALSE;
759 	if (s != buf)
760 	    goto echo_it;
761     }
762     if (*s == '\033') {		/* substitution desired? */
763 #ifdef ESCSUBS
764 	char tmpbuf[4], *cpybuf;
765 
766 	tmpbuf[0] = '%';
767 	read_tty(&tmpbuf[1],1);
768 #ifdef RAWONLY
769 	tmpbuf[1] &= 0177;
770 #endif
771 	tmpbuf[2] = '\0';
772 	if (tmpbuf[1] == 'h') {
773 	    (void) help_subs();
774 	    *s = '\0';
775 	    reprint();
776 	}
777 	else if (tmpbuf[1] == '\033') {
778 	    *s = '\0';
779 	    cpybuf = savestr(buf);
780 	    interpsearch(buf, sizeof buf, cpybuf, cmd);
781 	    free(cpybuf);
782 	    s = buf + strlen(buf);
783 	    reprint();
784 	}
785 	else {
786 	    interpsearch(s, sizeof buf - (s-buf), tmpbuf, cmd);
787 	    fputs(s,stdout);
788 	    s += strlen(s);
789 	}
790 #else
791 	notincl("^[");
792 	*s = '\0';
793 	reprint();
794 #endif
795 	return s;
796     }
797     else if (*s == ERASECH) {		/* they want to rubout a char? */
798 	if (s != buf) {
799 	    rubout();
800 	    s--;			/* discount the char rubbed out */
801 	    if (!AT_NORM_CHAR(s))
802 		rubout();
803 	}
804 	return s;
805     }
806     else if (*s == KILLCH) {		/* wipe out the whole line? */
807 	while (s != buf) {		/* emulate that many ERASEs */
808 	    rubout();
809 	    s--;
810 	    if (!AT_NORM_CHAR(s))
811 		rubout();
812 	}
813 	return s;
814     }
815 #ifdef WORDERASE
816     else if (*s == Ctl('w')) {		/* wipe out one word? */
817 	if (s == buf)
818 	    return s;
819 	*s-- = ' ';
820 	while (!isspace(*s) || isspace(s[1])) {
821 	    rubout();
822 	    if (!AT_NORM_CHAR(s))
823 		rubout();
824 	    if (s == buf)
825 		return buf;
826 	    s--;
827 	}
828 	return s+1;
829     }
830 #endif
831     else if (*s == Ctl('r')) {
832 	*s = '\0';
833 	reprint();
834 	return s;
835     }
836     else if (*s == Ctl('v')) {
837 	putchar('^');
838 	backspace();
839 	fflush(stdout);
840 	getcmd(s);
841     }
842     else if (*s == '\\')
843 	quoteone = TRUE;
844 
845 echo_it:
846     if (!not_echoing)
847 	echo_char(*s);
848     return s+1;
849 }
850 
851 bool
finish_dblchar()852 finish_dblchar()
853 {
854     bool ret;
855     int buflimit_save = buflimit;
856     int not_echoing_save = not_echoing;
857     buflimit = 2;
858     ret = finish_command(FALSE);
859     buflimit = buflimit_save;
860     not_echoing = not_echoing_save;
861     return ret;
862 }
863 
864 /* discard any characters typed ahead */
865 
866 void
eat_typeahead()867 eat_typeahead()
868 {
869     static double last_time = 0.;
870     double this_time = current_time();
871 
872     /* do not eat typeahead while creating virtual group */
873     if (univ_ng_virtflag)
874       return;
875     /* Don't eat twice before getting a character */
876     if (!got_a_char)
877 	return;
878     got_a_char = FALSE;
879 
880     /* cancel only keyboard stuff */
881     if (!allow_typeahead && !mouse_is_down && !macro_pending()
882      && this_time - last_time > 0.3) {
883 #ifdef PENDING
884 	register KEYMAP* curmap = topmap;
885 	Uchar lc;
886 	int i, j;
887 	for (j = 0; input_pending(); ) {
888 	    errno = 0;
889 	    if (read_tty(&buf[j],1) < 0) {
890 		if (errno && errno != EINTR) {
891 		    perror(readerr);
892 		    sig_catcher(0);
893 		}
894 		continue;
895 	    }
896 	    lc = *(Uchar*)buf;
897 	    if ((lc & 0200) || curmap == NULL) {
898 		curmap = topmap;
899 		j = 0;
900 		continue;
901 	    }
902 	    j++;
903 	    for (i = (curmap->km_type[lc] >> KM_GSHIFT) & KM_GMASK; i; i--) {
904 		if (!input_pending())
905 		    goto dbl_break;
906 		read_tty(&buf[j++],1);
907 	    }
908 
909 	    switch (curmap->km_type[lc] & KM_TMASK) {
910 	      case KM_STRING:		/* a string? */
911 	      case KM_NOTHIN:		/* no entry? */
912 		curmap = topmap;
913 		j = 0;
914 		continue;
915 	      case KM_KEYMAP:		/* another keymap? */
916 		curmap = curmap->km_ptr[lc].km_km;
917 		break;
918 	    }
919 	}
920       dbl_break:
921 	if (j) {
922 	    /* Don't delete a partial macro sequence */
923 	    buf[j] = '\0';
924 	    pushstring(buf,0);
925 	}
926 #else /* this is probably v7 */
927 # ifdef I_SGTTY
928 	ioctl(_tty_ch,TIOCSETP,&_tty);
929 # else
930 #  ifdef I_TERMIO
931 	ioctl(_tty_ch,TCSETAW,&_tty);
932 #  else
933 #   ifdef I_TERMIOS
934 	tcsetattr(_tty_ch,TCSAFLUSH,&_tty);
935 #   else
936 	..."Don't know how to eat typeahead!"
937 #   endif
938 #  endif
939 # endif
940 #endif
941     }
942     last_time = this_time;
943 }
944 
945 void
save_typeahead(buf,len)946 save_typeahead(buf, len)
947 char* buf;
948 int len;
949 {
950     int cnt;
951 
952     while (input_pending()) {
953 	cnt = read_tty(buf, len);
954 	buf += cnt;
955 	len -= cnt;
956     }
957     *buf = '\0';
958 }
959 
960 void
settle_down()961 settle_down()
962 {
963     dingaling();
964     fflush(stdout);
965     /*sleep(1);*/
966     nextout = nextin;			/* empty circlebuf */
967     not_echoing = 0;
968     eat_typeahead();
969 }
970 
971 #ifdef SUPPORT_NNTP
972 bool ignore_EINTR = FALSE;
973 
974 Signal_t
alarm_catcher(signo)975 alarm_catcher(signo)
976 int signo;
977 {
978     /*printf("\n*** In alarm catcher **\n"); $$*/
979     ignore_EINTR = TRUE;
980     check_datasrcs();
981     sigset(SIGALRM,alarm_catcher);
982     (void) alarm(DATASRC_ALARM_SECS);
983 }
984 #endif
985 
986 /* read a character from the terminal, with multi-character pushback */
987 
988 int
read_tty(addr,size)989 read_tty(addr,size)
990 char* addr;
991 int size;
992 {
993     if (macro_pending()) {
994 	*addr = circlebuf[nextout++];
995 	nextout %= PUSHSIZE;
996 	return 1;
997     }
998 #ifdef USE_TK
999     if (ttk_running) {
1000 	ttk_wait_for_input();    /* handle events until input is available */
1001 	if (ttk_keys && *ttk_keys) {
1002 	    int len = strlen(ttk_keys);
1003 	    if (size > len)
1004 		size = len;
1005 	    strncpy(addr, ttk_keys, size);      /* return the first bit */
1006 	    if (len > size)
1007 		pushstring(ttk_keys+size,0);	/* and push the rest */
1008 	    free(ttk_keys);			/* every byte counts... */
1009 	    /* A plain NULL pointer will not work -- it is "\0" in TCL */
1010 	    ttk_keys = savestr(nullstr);
1011 	    return size;
1012 	}
1013     }
1014 #endif
1015 #ifdef MSDOS
1016     *addr = getch();
1017     if (*addr == '\0')
1018 	*addr = Ctl('\035');
1019     size = 1;
1020 #else
1021     size = read(0,addr,size);
1022 #endif
1023 #ifdef RAWONLY
1024     *addr &= 0177;
1025 #endif
1026     got_a_char = TRUE;
1027     return size;
1028 }
1029 
1030 #ifdef PENDING
1031 # if !defined(FIONREAD) && !defined(HAS_RDCHK) && !defined(MSDOS)
1032 int
circfill()1033 circfill()
1034 {
1035     register int Howmany;
1036 
1037     errno = 0;
1038     Howmany = read(devtty,circlebuf+nextin,1);
1039 
1040     if (Howmany < 0 && (errno == EAGAIN || errno == EINTR))
1041 	Howmany = 0;
1042     if (Howmany) {
1043 	nextin += Howmany;
1044 	nextin %= PUSHSIZE;
1045     }
1046     return Howmany;
1047 }
1048 # endif /* FIONREAD */
1049 #endif /* PENDING */
1050 
1051 void
pushchar(c)1052 pushchar(c)
1053 char_int c;
1054 {
1055     nextout--;
1056     if (nextout < 0)
1057 	nextout = PUSHSIZE - 1;
1058     if (nextout == nextin) {
1059 	fputs("\npushback buffer overflow\n",stdout) FLUSH;
1060 	sig_catcher(0);
1061     }
1062     circlebuf[nextout] = c;
1063 }
1064 
1065 /* print an underlined string, one way or another */
1066 
1067 void
underprint(s)1068 underprint(s)
1069 register char* s;
1070 {
1071     assert(tc_UC);
1072     if (*tc_UC) {	/* char by char underline? */
1073 	while (*s) {
1074 	    if (!AT_NORM_CHAR(s)) {
1075 		putchar('^');
1076 		backspace();/* back up over it */
1077 		underchar();/* and do the underline */
1078 		putchar((*s & 0x7F) | 64);
1079 		backspace();/* back up over it */
1080 		underchar();/* and do the underline */
1081 	    }
1082 	    else {
1083 		putchar(*s);
1084 		backspace();/* back up over it */
1085 		underchar();/* and do the underline */
1086 	    }
1087 	    s++;
1088 	}
1089     }
1090     else {		/* start and stop underline */
1091 	underline();	/* start underlining */
1092 	while (*s)
1093 	    echo_char(*s++);
1094 	un_underline();	/* stop underlining */
1095     }
1096 }
1097 
1098 /* keep screen from flashing strangely on magic cookie terminals */
1099 
1100 #ifdef NOFIREWORKS
1101 void
no_sofire()1102 no_sofire()
1103 {
1104     /* should we disable fireworks? */
1105     if (!(fire_is_out & STANDOUT) && (term_line|term_col)==0 && *tc_UP && *tc_SE) {
1106 	newline();
1107 	un_standout();
1108 	up_line();
1109 	carriage_return();
1110     }
1111 }
1112 #endif
1113 
1114 #ifdef NOFIREWORKS
1115 void
no_ulfire()1116 no_ulfire()
1117 {
1118     /* should we disable fireworks? */
1119     if (!(fire_is_out & UNDERLINE) && (term_line|term_col)==0 && *tc_UP && *tc_US) {
1120 	newline();
1121 	un_underline();
1122 	up_line();
1123 	carriage_return();
1124     }
1125 }
1126 #endif
1127 
1128 /* get a character into a buffer */
1129 
1130 void
getcmd(whatbuf)1131 getcmd(whatbuf)
1132 register char* whatbuf;
1133 {
1134     register KEYMAP* curmap;
1135     register int i;
1136     bool no_macros;
1137     int times = 0;			/* loop detector */
1138 
1139 #ifdef SUPPORT_NNTP
1140     if (!input_pending()) {
1141 	sigset(SIGALRM,alarm_catcher);
1142 	(void) alarm(DATASRC_ALARM_SECS);
1143     }
1144 #endif
1145 
1146 tryagain:
1147     curmap = topmap;
1148 #ifdef OLD_RN_WAY
1149     no_macros = (whatbuf != buf && !macro_pending());
1150 #else
1151     no_macros = (whatbuf != buf && !xmouse_is_on);
1152 #endif
1153     for (;;) {
1154 	int_count = 0;
1155 	errno = 0;
1156 #ifdef SUPPORT_NNTP
1157 	ignore_EINTR = FALSE;
1158 #endif
1159 	if (read_tty(whatbuf,1) < 0) {
1160 	    if (!errno)
1161 	        errno = EINTR;
1162 	    if (errno == EINTR) {
1163 #ifdef SUPPORT_NNTP
1164 		if (ignore_EINTR)
1165 		    continue;
1166 		(void) alarm(0);
1167 #endif
1168 		return;
1169 	    }
1170 	    perror(readerr);
1171 	    sig_catcher(0);
1172 	}
1173 	lastchar = *(Uchar*)whatbuf;
1174 	if (lastchar & 0200 || no_macros) {
1175 	    *whatbuf &= 0177;
1176 	    goto got_canonical;
1177 	}
1178 	if (curmap == NULL)
1179 	    goto got_canonical;
1180 	for (i = (curmap->km_type[lastchar] >> KM_GSHIFT) & KM_GMASK; i; i--)
1181 	    read_tty(&whatbuf[i],1);
1182 
1183 	switch (curmap->km_type[lastchar] & KM_TMASK) {
1184 	  case KM_NOTHIN:		/* no entry? */
1185 	    if (curmap == topmap)	/* unmapped canonical */
1186 		goto got_canonical;
1187 	    settle_down();
1188 	    goto tryagain;
1189 	  case KM_KEYMAP:		/* another keymap? */
1190 	    curmap = curmap->km_ptr[lastchar].km_km;
1191 	    assert(curmap != NULL);
1192 	    break;
1193 	  case KM_STRING:		/* a string? */
1194 	    pushstring(curmap->km_ptr[lastchar].km_str,0200);
1195 	    if (++times > 20) {		/* loop? */
1196 		fputs("\nmacro loop?\n",stdout);
1197 		termdown(2);
1198 		settle_down();
1199 	    }
1200 	    no_macros = FALSE;
1201 	    goto tryagain;
1202 	}
1203     }
1204 
1205 got_canonical:
1206     /* This hack is for mouse support */
1207     if (xmouse_is_on && *whatbuf == Ctl('c')) {
1208 	mouse_input(whatbuf+1);
1209 	times = 0;
1210 	goto tryagain;
1211     }
1212 #ifdef I_SGTTY
1213     if (*whatbuf == '\r')
1214 	*whatbuf = '\n';
1215 #endif
1216     if (whatbuf == buf)
1217 	whatbuf[1] = FINISHCMD;		/* tell finish_command to work */
1218 #ifdef SUPPORT_NNTP
1219     (void) alarm(0);
1220 #endif
1221 }
1222 
1223 void
pushstring(str,bits)1224 pushstring(str,bits)
1225 char* str;
1226 char_int bits;
1227 {
1228     register int i;
1229     char tmpbuf[PUSHSIZE];
1230     register char* s = tmpbuf;
1231 
1232     assert(str != NULL);
1233     interp(tmpbuf,PUSHSIZE,str);
1234     for (i = strlen(s)-1; i >= 0; i--)
1235 	pushchar(s[i] ^ bits);
1236 }
1237 
1238 int
get_anything()1239 get_anything()
1240 {
1241     char tmpbuf[64];
1242     char mode_save = mode;
1243 
1244 reask_anything:
1245     unflush_output();			/* disable any ^O in effect */
1246     color_object(COLOR_MORE, 1);
1247 #ifdef VERBOSE
1248     IF(verbose)
1249 	fputs("[Type space to continue] ",stdout);
1250     ELSE
1251 #endif
1252 #ifdef TERSE
1253 	fputs("[MORE] ",stdout);
1254 #endif
1255     color_pop();	/* of COLOR_MORE */
1256     fflush(stdout);
1257     eat_typeahead();
1258     if (int_count)
1259 	return -1;
1260     cache_until_key();
1261     set_mode(gmode, 'K');
1262     getcmd(tmpbuf);
1263     set_mode(gmode,mode_save);
1264     if (errno || *tmpbuf == '\f') {
1265 	newline();			/* if return from stop signal */
1266 	goto reask_anything;		/* give them a prompt again */
1267     }
1268     if (*tmpbuf == 'h') {
1269 #ifdef VERBOSE
1270 	IF(verbose)
1271 	    fputs("\nType q to quit or space to continue.\n",stdout) FLUSH;
1272 	ELSE
1273 #endif
1274 #ifdef TERSE
1275 	    fputs("\nq to quit, space to continue.\n",stdout) FLUSH;
1276 #endif
1277 	termdown(2);
1278 	goto reask_anything;
1279     }
1280     else if (*tmpbuf != ' ' && *tmpbuf != '\n') {
1281 	erase_line(0);	/* erase the prompt */
1282 	return *tmpbuf == 'q' ? -1 : *tmpbuf;
1283     }
1284     if (*tmpbuf == '\n') {
1285 	page_line = tc_LINES - 1;
1286 	erase_line(0);
1287     }
1288     else {
1289 	page_line = 1;
1290 	if (erase_screen)		/* -e? */
1291 	    clear();			/* clear screen */
1292 	else
1293 	    erase_line(0);		/* erase the prompt */
1294     }
1295     return 0;
1296 }
1297 
1298 int
pause_getcmd()1299 pause_getcmd()
1300 {
1301     char mode_save = mode;
1302 
1303     unflush_output();			/* disable any ^O in effect */
1304     color_object(COLOR_CMD, 1);
1305 #ifdef VERBOSE
1306     IF(verbose)
1307 	fputs("[Type space or a command] ",stdout);
1308     ELSE
1309 #endif
1310 #ifdef TERSE
1311 	fputs("[CMD] ",stdout);
1312 #endif
1313     color_pop();	/* of COLOR_CMD */
1314     fflush(stdout);
1315     eat_typeahead();
1316     if (int_count)
1317 	return -1;
1318     cache_until_key();
1319     set_mode(gmode,'K');
1320     getcmd(buf);
1321     set_mode(gmode,mode_save);
1322     if (errno || *buf == '\f')
1323 	return 0;			/* if return from stop signal */
1324     if (*buf != ' ') {
1325 	erase_line(0);	/* erase the prompt */
1326 	return *buf;
1327     }
1328     return 0;
1329 }
1330 
1331 void
in_char(prompt,newmode,dflt)1332 in_char(prompt, newmode, dflt)
1333 char* prompt;
1334 char_int newmode;
1335 char* dflt;
1336 {
1337     char mode_save = mode;
1338     char gmode_save = gmode;
1339     char* s;
1340     int newlines;
1341 
1342     for (newlines = 0, s = prompt; *s; s++) {
1343 	if (*s == '\n')
1344 	    newlines++;
1345     }
1346 
1347 reask_in_char:
1348     unflush_output();			/* disable any ^O in effect */
1349     printf("%s [%s] ", prompt, dflt);
1350     fflush(stdout);
1351     termdown(newlines);
1352     eat_typeahead();
1353     set_mode('p',newmode);
1354     getcmd(buf);
1355     if (errno || *buf == '\f') {
1356 	newline();			/* if return from stop signal */
1357 	goto reask_in_char;		/* give them a prompt again */
1358     }
1359     setdef(buf,dflt);
1360     set_mode(gmode_save,mode_save);
1361 }
1362 
1363 void
in_answer(prompt,newmode)1364 in_answer(prompt, newmode)
1365 char* prompt;
1366 char_int newmode;
1367 {
1368     char mode_save = mode;
1369     char gmode_save = gmode;
1370 
1371 reask_in_answer:
1372     unflush_output();			/* disable any ^O in effect */
1373     fputs(prompt,stdout);
1374     fflush(stdout);
1375     eat_typeahead();
1376     set_mode('i',newmode);
1377 reinp_in_answer:
1378     getcmd(buf);
1379     if (errno || *buf == '\f') {
1380 	newline();			/* if return from stop signal */
1381 	goto reask_in_answer;		/* give them a prompt again */
1382     }
1383     if (*buf == ERASECH)
1384 	goto reinp_in_answer;
1385     if (*buf != '\n' && *buf != ' ') {
1386 	if (!finish_command(FALSE))
1387 	    goto reinp_in_answer;
1388     }
1389     else
1390 	buf[1] = '\0';
1391     newline();
1392     set_mode(gmode_save,mode_save);
1393 }
1394 
1395 /* If this takes more than one line, return FALSE */
1396 
1397 bool
in_choice(prompt,value,choices,newmode)1398 in_choice(prompt, value, choices, newmode)
1399 char* prompt;
1400 char* value;
1401 char* choices;
1402 char_int newmode;
1403 {
1404     char mode_save = mode;
1405     char gmode_save = gmode;
1406     char* cp;
1407     char* bp;
1408     char* s;
1409     char* prefix = NULL;
1410     int len, number_was = -1, any_val_OK = 0, value_changed;
1411     char tmpbuf[80], prefixes[80];
1412 
1413     unflush_output();			/* disable any ^O in effect */
1414     eat_typeahead();
1415     set_mode('c',newmode);
1416     screen_is_dirty = FALSE;
1417 
1418     cp = choices;
1419     if (*cp == '[') {
1420 	for (s = prefixes, cp++; *cp != ']'; ) {
1421 	    if (*cp == '/') {
1422 		*s++ = '\0';
1423 		cp++;
1424 	    }
1425 	    else
1426 		*s++ = *cp++;
1427 	}
1428 	*s++ = '\0';
1429 	*s = '\0';
1430 	if (*++cp == ' ')
1431 	    cp++;
1432     }
1433     else
1434 	*prefixes = '\0';
1435     for (s = tmpbuf; *cp; ) {
1436 	if (*cp == '/') {
1437 	    *s++ = '\0';
1438 	    cp++;
1439 	}
1440 	else if (*cp == '<') {
1441 	    do {
1442 		*s++ = *cp;
1443 	    } while (*cp++ != '>');
1444 	    any_val_OK = 1;		/* flag that '<' was found */
1445 	}
1446 	else
1447 	    *s++ = *cp++;
1448     }
1449     cp = s;
1450     *s++ = '\0';
1451     *s = '\0';
1452     strcpy(buf,value);
1453 
1454 reask_in_choice:
1455     len = strlen(buf);
1456     bp = buf;
1457     if (*prefixes != '\0') {
1458 	s = prefix;
1459 	for (prefix = prefixes; *prefix; prefix += strlen(prefix)) {
1460 	    if (*prefix == *buf)
1461 		break;
1462 	}
1463 	if (*prefix) {
1464 	    for (bp = buf; *bp && *bp != ' '; bp++) ;
1465 	    while (*bp == ' ') bp++;
1466 	}
1467 	else
1468 	    prefix = NULL;
1469 	value_changed = prefix != s;
1470     }
1471     else {
1472 	prefix = NULL;
1473 	value_changed = 0;
1474     }
1475     s = cp;
1476     for (;;) {
1477 	cp += strlen(cp) + 1;
1478 	if (!*cp)
1479 	    cp = tmpbuf;
1480 	if (*cp == '<'
1481 	 && (*buf == '<' || cp[1] != '#' || isdigit(*buf) || !*s)) {
1482 	    prefix = NULL;
1483 	    break;
1484 	}
1485 	if (s == cp) {
1486 	    if (!value_changed) {
1487 		if (prefix)
1488 		    prefix = NULL;
1489 		else
1490 		    dingaling();
1491 	    }
1492 	    break;
1493 	}
1494 	if (!*bp || strnEQ(cp,bp,any_val_OK? len : 1))
1495 	    break;
1496     }
1497 
1498     if (*cp == '<') {
1499 	if (*buf == '<' || cp[1] == '#') {
1500 	    if (number_was >= 0)
1501 		sprintf(buf, "%d", number_was);
1502 	    else {
1503 		for (s = buf; isdigit(*s); s++) ;
1504 		*s = '\0';
1505 	    }
1506 	}
1507     }
1508     else {
1509 	if (prefix) {
1510 	    sprintf(buf, "%s ", prefix);
1511 	    strcat(buf,cp);
1512 	}
1513 	else
1514 	    strcpy(buf,cp);
1515     }
1516     s = buf + strlen(buf);
1517     carriage_return();
1518     erase_line(0);
1519     fputs(prompt,stdout);
1520     fputs(buf,stdout);
1521     len = strlen(prompt);
1522     number_was = -1;
1523 
1524 reinp_in_choice:
1525     if ((s-buf) + len >= tc_COLS)
1526 	screen_is_dirty = TRUE;
1527     fflush(stdout);
1528     getcmd(s);
1529     if (errno || *s == '\f')		/* if return from stop signal */
1530 	*s = '\n';
1531     if (*s != '\n') {
1532 	char ch = *s;
1533 	if (*cp == '<' && ch != '\t' && (ch != ' ' || buf != s)) {
1534 	    if (cp[1] == '#') {
1535 		s = edit_buf(s, (char*)NULL);
1536 		if (s != buf) {
1537 		    if (isdigit(s[-1]))
1538 			goto reinp_in_choice;
1539 		    else
1540 			number_was = atoi(buf);
1541 		}
1542 	    }
1543 	    else {
1544 		s = edit_buf(s, (char*)NULL);
1545 		goto reinp_in_choice;
1546 	    }
1547 	}
1548 	*s = '\0';
1549 	for (s = buf; *s && *s != ' '; s++) ;
1550 	if (*s == ' ') s++;
1551 	if (ch == ' ' || ch == '\t') {
1552 	    if (prefix)
1553 		*s = '\0';
1554 	    else
1555 		*buf = '\0';
1556 	}
1557 	else {
1558 	    char ch1 = buf[0];
1559 	    if (prefix) {
1560 		if (ch == ch1)
1561 		    ch = *s;
1562 		else {
1563 		    ch1 = ch;
1564 		    ch = buf[0];
1565 		}
1566 	    }
1567 	    sprintf(buf,"%c %c",ch == ERASECH || ch == KILLCH? '<' : ch, ch1);
1568 	}
1569 	goto reask_in_choice;
1570     }
1571     *s = '\0';
1572 
1573     set_mode(gmode_save,mode_save);
1574     return !screen_is_dirty;
1575 }
1576 
1577 int
print_lines(what_to_print,hilite)1578 print_lines(what_to_print,hilite)
1579 char* what_to_print;
1580 int hilite;
1581 {
1582     register char* s;
1583     register int i;
1584 
1585     for (s=what_to_print; *s; ) {
1586 	i = check_page_line();
1587 	if (i)
1588 	    return i;
1589 	if (hilite == STANDOUT) {
1590 #ifdef NOFIREWORKS
1591 	    if (erase_screen)
1592 		no_sofire();
1593 #endif
1594 	    standout();
1595 	}
1596 	else if (hilite == UNDERLINE) {
1597 #ifdef NOFIREWORKS
1598 	    if (erase_screen)
1599 		no_ulfire();
1600 #endif
1601 	    underline();
1602 	}
1603 	for (i = 0; i < tc_COLS; i++) {
1604 	    if (!*s)
1605 		break;
1606 	    if (AT_NORM_CHAR(s))
1607 		putchar(*s);
1608 	    else if (*s == '\t') {
1609 		putchar(*s);
1610 		i = ((i+8) & ~7) - 1;
1611 	    }
1612 	    else if (*s == '\n') {
1613 		i = 32000;
1614 	    }
1615 	    else {
1616 		i++;
1617 		putchar('^');
1618 		putchar(*s + 64);
1619 	    }
1620 	    s++;
1621 	}
1622 	if (i) {
1623 	    if (hilite == STANDOUT)
1624 		un_standout();
1625 	    else if (hilite == UNDERLINE)
1626 		un_underline();
1627 	    if (tc_AM && i == tc_COLS)
1628 		fflush(stdout);
1629 	    else
1630 		newline();
1631 	}
1632     }
1633     return 0;
1634 }
1635 
1636 int
check_page_line()1637 check_page_line()
1638 {
1639     if (page_line < 0)
1640 	return -1;
1641     if (page_line >= tc_LINES || int_count) {
1642 	register int cmd = -1;
1643 	if (int_count || (cmd = get_anything())) {
1644 	    page_line = -1;		/* disable further printing */
1645 	    if (cmd > 0)
1646 		pushchar(cmd);
1647 	    return cmd;
1648 	}
1649     }
1650     page_line++;
1651     return 0;
1652 }
1653 
1654 void
page_start()1655 page_start()
1656 {
1657     page_line = 1;
1658     if (erase_screen)
1659 	clear();
1660     else
1661 	newline();
1662 }
1663 
1664 void
errormsg(str)1665 errormsg(str)
1666 char* str;
1667 {
1668     if (gmode == 's') {
1669 	if (str != msg)
1670 	    strcpy(msg,str);
1671 	error_occurred = TRUE;
1672     }
1673     else if (*str) {
1674 	printf("\n%s\n", str) FLUSH;
1675 	termdown(2);
1676     }
1677 }
1678 
1679 void
warnmsg(str)1680 warnmsg(str)
1681 char* str;
1682 {
1683     if (gmode != 's') {
1684 	printf("\n%s\n", str) FLUSH;
1685 	termdown(2);
1686 	pad(just_a_sec/3);
1687     }
1688 }
1689 
1690 void
pad(num)1691 pad(num)
1692 int num;
1693 {
1694     register int i;
1695 
1696     for (i = num; i; i--)
1697 	putchar(tc_PC);
1698     fflush(stdout);
1699 }
1700 
1701 /* echo the command just typed */
1702 
1703 #ifdef VERIFY
1704 void
printcmd()1705 printcmd()
1706 {
1707     if (verify && buf[1] == FINISHCMD) {
1708 	if (!AT_NORM_CHAR(buf)) {
1709 	    putchar('^');
1710 	    putchar((*buf & 0x7F) | 64);
1711 	    backspace();
1712 	    backspace();
1713 	}
1714 	else {
1715 	    putchar(*buf);
1716 	    backspace();
1717 	}
1718 	fflush(stdout);
1719     }
1720 }
1721 #endif
1722 
1723 void
rubout()1724 rubout()
1725 {
1726     backspace();			/* do the old backspace, */
1727     putchar(' ');			/*   space, */
1728     backspace();			/*     backspace trick */
1729 }
1730 
1731 void
reprint()1732 reprint()
1733 {
1734     register char* s;
1735 
1736     fputs("^R\n",stdout) FLUSH;
1737     termdown(1);
1738     for (s = buf; *s; s++)
1739 	echo_char(*s);
1740     screen_is_dirty = TRUE;
1741 }
1742 
1743 void
erase_line(to_eos)1744 erase_line(to_eos)
1745 bool_int to_eos;
1746 {
1747     carriage_return();
1748     if (to_eos)
1749 	clear_rest();
1750     else
1751 	erase_eol();
1752     carriage_return();		/* Resets kernel's tab column counter to 0 */
1753     fflush(stdout);
1754 }
1755 
1756 void
clear()1757 clear()
1758 {
1759     term_line = term_col = fire_is_out = 0;
1760     if (tc_CL)
1761 	tputs(tc_CL,tc_LINES,putchr);
1762     else if (tc_CD) {
1763 	home_cursor();
1764 	tputs(tc_CD,tc_LINES,putchr);
1765     }
1766     else {
1767 	int i;
1768 	for (i = 0; i < tc_LINES; i++)
1769 	    putchr('\n');
1770 	home_cursor();
1771     }
1772     tputs(tc_CR,1,putchr) FLUSH;
1773 }
1774 
1775 void
home_cursor()1776 home_cursor()
1777 {
1778     char* tgoto();
1779 
1780     if (!*tc_HO) {		/* no home sequence? */
1781 	if (!*tc_CM) {		/* no cursor motion either? */
1782 	    fputs("\n\n\n", stdout);
1783 	    termdown(3);
1784 	    return;		/* forget it. */
1785 	}
1786 	tputs(tgoto(tc_CM, 0, 0), 1, putchr);	/* go to home via CM */
1787     }
1788     else {			/* we have home sequence */
1789 	tputs(tc_HO, 1, putchr);/* home via HO */
1790     }
1791     carriage_return();	/* Resets kernel's tab column counter to 0 */
1792     term_line = term_col = 0;
1793 }
1794 
1795 void
goto_xy(to_col,to_line)1796 goto_xy(to_col,to_line)
1797 int to_col;
1798 int to_line;
1799 {
1800     char* tgoto();
1801     char* str;
1802     int cmcost, xcost, ycost;
1803 
1804     if (term_col == to_col && term_line == to_line)
1805 	return;
1806     if (*tc_CM && !muck_up_clear) {
1807 	str = tgoto(tc_CM,to_col,to_line);
1808 	cmcost = strlen(str);
1809     } else {
1810 	str = NULL;
1811 	cmcost = 9999;
1812     }
1813 
1814     if ((ycost = (to_line - term_line)) < 0)
1815 	ycost = (upcost? -ycost * upcost : 7777);
1816     else if (ycost > 0)
1817 	term_col = 0;
1818 
1819     if ((xcost = (to_col - term_col)) < 0) {
1820 	if (!to_col && ycost+1 < cmcost) {
1821 	    carriage_return();
1822 	    xcost = 0;
1823 	}
1824 	else
1825 	    xcost = -xcost * leftcost;
1826     }
1827     else if (xcost > 0 && cmcost < 9999)
1828 	xcost = 9999;
1829 
1830     if (cmcost <= xcost + ycost) {
1831 	tputs(str,1,putchr);
1832 	term_line = to_line;
1833 	term_col = to_col;
1834 	return;
1835     }
1836 
1837     if (ycost == 7777)
1838 	home_cursor();
1839 
1840     if (to_line >= term_line)
1841 	while(term_line < to_line) newline();
1842     else
1843 	while(term_line > to_line) up_line();
1844 
1845     if (to_col >= term_col)
1846 	while(term_col < to_col) term_col++, putchar(' ');
1847     else
1848 	while(term_col > to_col) term_col--, backspace();
1849 }
1850 
1851 static void
line_col_calcs()1852 line_col_calcs()
1853 {
1854     if (tc_LINES > 0) {		/* is this a crt? */
1855 	if (!initlines || !option_def_vals[OI_INITIAL_ARTICLE_LINES]) {
1856 	    /* no -i or unreasonable value for initlines */
1857 	    if (outspeed >= B9600) 	/* whole page at >= 9600 baud */
1858 		initlines = tc_LINES;
1859 	    else if (outspeed >= B4800)	/* 16 lines at 4800 */
1860 		initlines = 16;
1861 	    else			/* otherwise just header */
1862 		initlines = 8;
1863 	}
1864 	/* Check for initlines bigger than the screen and fix it! */
1865 	if (initlines > tc_LINES)
1866 	    initlines = tc_LINES;
1867     }
1868     else {				/* not a crt */
1869 	tc_LINES = 30000;		/* so don't page */
1870 	tc_CL = "\n\n";			/* put a couple of lines between */
1871 	if (!initlines || !option_def_vals[OI_INITIAL_ARTICLE_LINES])
1872 	    initlines = 8;		/* make initlines reasonable */
1873     }
1874     if (tc_COLS <= 0)
1875 	tc_COLS = 80;
1876 #ifdef SCAN
1877     s_resize_win();	/* let various parts know */
1878 #endif
1879 }
1880 
1881 #ifdef SIGWINCH
1882 Signal_t
winch_catcher(dummy)1883 winch_catcher(dummy)
1884 int dummy;
1885 {
1886     /* Reset signal in case of System V dain bramage */
1887     sigset(SIGWINCH, winch_catcher);
1888 
1889     /* Come here if window size change signal received */
1890 #ifdef TIOCGWINSZ
1891     {	struct winsize ws;
1892 	if (ioctl(0, TIOCGWINSZ, &ws) >= 0 && ws.ws_row > 0 && ws.ws_col > 0) {
1893 	    if (tc_LINES != ws.ws_row || tc_COLS != ws.ws_col) {
1894 		tc_LINES = ws.ws_row;
1895 		tc_COLS = ws.ws_col;
1896 		line_col_calcs();
1897 		sprintf(lines_export, "%d", tc_LINES);
1898 		sprintf(cols_export, "%d", tc_COLS);
1899 		if (gmode == 's' || mode == 'a' || mode == 'p') {
1900 		    forceme("\f");	/* cause a refresh */
1901 					/* (defined only if TIOCSTI defined) */
1902 		}
1903 	    }
1904 	}
1905     }
1906 #else
1907     /* Well, if SIGWINCH is defined, but TIOCGWINSZ isn't, there's    */
1908     /* almost certainly something wrong.  Figure it out for yourself, */
1909     /* because I don't know how to deal with it :-)                   */
1910     ERROR!
1911 #endif
1912 }
1913 #endif
1914 
1915 void
termlib_init()1916 termlib_init()
1917 {
1918 #ifdef USETITE
1919     if (tc_TI && *tc_TI) {
1920 	tputs(tc_TI,1,putchr);
1921 	fflush(stdout);
1922     }
1923 #endif
1924 #ifdef USEKSKE
1925     if (tc_KS && *tc_KS) {
1926 	tputs(tc_KS,1,putchr);
1927 	fflush(stdout);
1928     }
1929 #endif
1930     term_line = tc_LINES-1;
1931     term_col = 0;
1932     term_scrolled = tc_LINES;
1933 }
1934 
1935 void
termlib_reset()1936 termlib_reset()
1937 {
1938 #ifdef USETITE
1939     if (tc_TE && *tc_TE) {
1940 	tputs(tc_TE,1,putchr);
1941 	fflush(stdout);
1942     }
1943 #endif
1944 #ifdef USEKSKE
1945     if (tc_KE && *tc_KE) {
1946 	tputs(tc_KE,1,putchr);
1947 	fflush(stdout);
1948     }
1949 #endif
1950 }
1951 
1952 /* Nice-Background routines used to improve interactive performance with
1953  * background processing.  When called, these routines will wait for a
1954  * keystroke or a time limit to expire.  Calling this before a
1955  * background loop may significantly increase responsiveness when a
1956  * user types commands quickly.
1957  */
1958 
1959 /* NOTE: If you have problems, uncomment this define for some debugging help.
1960  * (The time limit will be 5 seconds and lots of info will be printed). */
1961 /* #define DEBUG_NICEBG */
1962 
1963 #ifdef PENDING
1964 #ifdef NICEBG
1965 
1966 #ifdef NBG_SELECT
1967 #undef NBG_TERMIO
1968 #undef NBG_SIGIO
1969 #endif
1970 
1971 #ifdef NBG_TERMIO
1972 #undef NBG_SELECT
1973 #undef NBG_SIGIO
1974 #endif
1975 
1976 #ifdef NBG_SIGIO
1977 #undef NBG_TERMIO
1978 #undef NBG_SELECT
1979 #endif
1980 
1981 #ifdef NBG_SIGIO /* SIGIO style */
1982 Signal_t
waitkey_sig_handler(dummy)1983 waitkey_sig_handler(dummy)
1984 int dummy;
1985 {
1986 #ifdef DEBUG_NICEBG
1987     /* CAA: I doubt that printf is safe in a signal handler... */
1988     printf("wait_key_pause signal caught!\n") FLUSH;
1989     fflush(stdout);
1990 #endif
1991 }
1992 #endif /* NBG_SIGIO */
1993 
1994 /* /dev/tty file descriptor */
1995 /* note: for now we let the system close it on exit... */
1996 static int wait_ttyfd INIT(-1);
1997 static bool wait_initialized INIT(FALSE);
1998 static bool wait_fd_opened INIT(FALSE);
1999 
2000 #ifdef NBG_SELECT
2001 static struct timeval wait_time;
2002 static struct fd_set wait_fdset;
2003 static int wait_tbl_size;
2004 #endif
2005 
2006 /* returns TRUE if input received */
2007 bool
wait_key_pause(ticks)2008 wait_key_pause(ticks)
2009 int ticks;		/* tenths of seconds to wait */
2010 {
2011 #ifdef DEBUG_NICEBG
2012     ticks = 50;		/* debugging: wait 5 seconds */
2013 #endif
2014     if (input_pending())
2015 	return TRUE;
2016 #ifdef DEBUG_NICEBG
2017     printf("entry: wait_key_pause\n") FLUSH; /* */
2018 #endif
2019     /* common initialization */
2020     if (!wait_fd_opened) {
2021 	wait_ttyfd = open("/dev/tty",0); /*$$ possible cron prob */
2022 	/* CAA: the open shouldn't really be a problem now that the
2023 	 * open return value is checked below.  If running from cron,
2024 	 * I hope that the open will simply fail.  If the open succeeds,
2025 	 * however, then there should be no cause for complaint by the
2026 	 * routines below.  (The select method may be safest.)  Still,
2027 	 * it might be nice to have some flag which means "we are running
2028 	 * in the background".
2029 	 */
2030 	wait_fd_opened = TRUE;
2031     }
2032     if (wait_ttyfd < 0) {
2033 	/* tty open failed.  Don't wait around */
2034 	return input_pending();
2035     }
2036 #ifdef NBG_TERMIO
2037     /* First try a nice standard way */
2038     {
2039 #ifdef I_TERMIO
2040 	struct termio save_tty, wait_tty;
2041 #else	/* must be TERMIOS then */
2042 	struct termios save_tty, wait_tty;
2043 #endif
2044 	char lbuf[1];		/* for the read command */
2045 	int nrd;		/* number read */
2046 
2047 #ifdef DEBUG_NICEBG
2048 	printf("wait_key_pause: using termio(s)\n") FLUSH;
2049 #endif
2050 	if (!wait_initialized)
2051 	    wait_initialized = TRUE;
2052 	if (ioctl(wait_ttyfd,TCGETA,&save_tty) == -1) {
2053 	    printf("wait_key_pause (term.c): ioctl TCGETA error\n") FLUSH;
2054 	    assert(FALSE);
2055 	}
2056 	wait_tty = save_tty;
2057 	wait_tty.c_lflag &= ~ICANON;
2058 	wait_tty.c_lflag &= ~ECHO;
2059 	wait_tty.c_cc[VMIN] = 0;
2060 	wait_tty.c_cc[VTIME] = ticks;
2061 	if (ioctl(wait_ttyfd,TCSETAF,&wait_tty) == -1) {
2062 	    printf("wait_key_pause (term.c): ioctl TCSETAF error\n") FLUSH;
2063 	    assert(FALSE);
2064 	}
2065 	nrd = read(wait_ttyfd,&lbuf,1);
2066 	ioctl(wait_ttyfd,TCSETAF,&save_tty);	/* put back the old info */
2067 	if (nrd == 1) {
2068 	    pushchar(lbuf[0]);	/* put it back */
2069 #ifdef DEBUG_NICEBG
2070 	    printf("early exit: wait_key_pause\n") FLUSH; /* */
2071 #endif
2072 	}
2073 #ifdef DEBUG_NICEBG
2074 	printf("exit: wait_key_pause\n") FLUSH; /* */
2075 #endif
2076 	return input_pending();
2077     }
2078 #endif /* NBG_TERMIO */
2079 #ifdef NBG_SELECT
2080 #ifdef DEBUG_NICEBG
2081 	printf("wait_key_pause: using select\n") FLUSH;
2082 #endif
2083 	if (!wait_initialized) {
2084 	    wait_tbl_size = wait_ttyfd + 1;
2085 	    wait_initialized = TRUE;
2086 	}
2087 	FD_ZERO(&wait_fdset);
2088 	FD_SET(wait_ttyfd,&wait_fdset);
2089 
2090 	wait_time.tv_usec = (ticks%10)*1000000;
2091 	wait_time.tv_sec = ticks/10;
2092 
2093 	(void)select(wait_tbl_size,&wait_fdset,NULL,NULL,&wait_time);
2094 
2095 #ifdef DEBUG_NICEBG
2096 	printf("exit: wait_key_pause\n") FLUSH; /* */
2097 #endif
2098 	return input_pending();
2099 #endif /* NBG_SELECT */
2100 #ifdef NBG_SIGIO
2101 /* SIGIO signal sent for each keystroke style */
2102 /* Method of last resort.  Surprisingly, this code has been quite
2103  * portable to several systems.
2104  */
2105     {
2106 #ifdef DEBUG_NICEBG
2107 	printf("wait_key_pause: using SIGIO style\n") FLUSH;
2108 #endif
2109 	if (!wait_initialized) {	/* not initialized yet? */
2110 /* Portable? (probably not) */
2111 	    wait_initialized = TRUE;
2112 	    sigset(SIGALRM,waitkey_sig_handler);
2113 	    sigset(SIGIO,waitkey_sig_handler);
2114 	    /* enable SIGIO (some systems need it).  ifdef it? */
2115 	    fcntl(wait_ttyfd,F_SETOWN,(int)getpid());
2116 	    fcntl(wait_ttyfd,F_SETFL,FASYNC);
2117 	}
2118 	(void) alarm((ticks+9)/10);	/* sleep if no key pressed */
2119 /* NOTE: There is a race condition here, but not a very important one IMO.
2120  *	 If the alarm signal is sent *before* pause() is called, then this
2121  *	 function will simply wait until a key is pressed.  In this case
2122  *	 no background processing will occur.
2123  *	 Noted by CAA, August 1992.
2124  */
2125 /* note2: The race condition can be minimized by using a variable
2126  * which will tell if the alarm arrived before the pause (really before
2127  * the variable was set, which should be near-instant.)  Look into
2128  * this later.
2129  */
2130 	pause();	/* wait for either ALRM or SIGIO signal */
2131 	(void) alarm(0);
2132 #ifdef DEBUG_NICEBG
2133 	printf("exit: wait_key_pause\n") FLUSH;
2134 #endif
2135 	return input_pending();
2136     }
2137 #endif /* NBG_SIGIO */
2138 /* if no nice-background methods are defined, return. */
2139 /* Thanks to Kian-Tat Lim for finding an earlier bug here */
2140 #ifndef NBG_SELECT
2141 #ifndef NBG_TERMIO
2142 #ifndef NBG_SIGIO
2143     /* really kind of an error, but a reasonable way to handle it */
2144     /* Act as if no input was present */
2145 #ifdef DEBUG_NICEBG
2146     printf("wait_key_pause: no handler style, returning FALSE.\n");
2147 #endif
2148     return FALSE;	/* don't worry if not reached */
2149 #endif
2150 #endif
2151 #endif
2152 }
2153 #endif /* NICEBG */
2154 #endif /* PENDING */
2155 
2156 void
xmouse_init(progname)2157 xmouse_init(progname)
2158 char* progname;
2159 {
2160     char* s;
2161     if (!can_home || !use_threads)
2162 	return;
2163     s = getenv("XTERMMOUSE");
2164     if (s && *s) {
2165 	interp(msg, sizeof msg, s);
2166 	set_option(OI_USE_MOUSE, msg);
2167     }
2168     else if (progname[strlen(progname)-1] == 'x') {
2169 	/* an 'x' at the end means enable Xterm mouse tracking */
2170 	set_option(OI_USE_MOUSE, "y");
2171     }
2172 }
2173 
2174 void
xmouse_check()2175 xmouse_check()
2176 {
2177     mousebar_cnt = 0;
2178     if (UseMouse) {
2179 	bool turn_it_on;
2180 	char mmode = mode;
2181 	if (gmode == 'p') {
2182 	    turn_it_on = TRUE;
2183 	    mmode = '\0';
2184 	}
2185 	else if (gmode == 'i' || gmode == 'p'
2186 	      || (muck_up_clear && gmode != 's'))
2187 	    turn_it_on = FALSE;
2188 	else {
2189 	    interp(msg, sizeof msg, MouseModes);
2190 	    turn_it_on = (index(msg, mode) != NULL);
2191 	}
2192 	if (turn_it_on) {
2193 	    char* s;
2194 	    int i, j;
2195 	    switch (mmode) {
2196 	      case 'c':
2197 		mousebar_btns = NewsrcSelBtns;
2198 		mousebar_cnt = NewsrcSelBtnCnt;
2199 		break;
2200 	      case 'j':
2201 		mousebar_btns = AddSelBtns;
2202 		mousebar_cnt = AddSelBtnCnt;
2203 		break;
2204 	      case 'l':
2205 		mousebar_btns = OptionSelBtns;
2206 		mousebar_cnt = OptionSelBtnCnt;
2207 		break;
2208 	      case 't':
2209 		mousebar_btns = NewsSelBtns;
2210 		mousebar_cnt = NewsSelBtnCnt;
2211 		break;
2212 	      case 'w':
2213 		mousebar_btns = NewsgroupSelBtns;
2214 		mousebar_cnt = NewsgroupSelBtnCnt;
2215 		break;
2216 	      case 'a':  case 'p':
2217 		mousebar_btns = ArtPagerBtns;
2218 		mousebar_cnt = ArtPagerBtnCnt;
2219 		break;
2220 	      case 'v':
2221 		mousebar_btns = UnivSelBtns;
2222 		mousebar_cnt = UnivSelBtnCnt;
2223 		break;
2224 	      default:
2225 		mousebar_btns = nullstr;
2226 		/*mousebar_cnt = 0;*/
2227 		break;
2228 	    }
2229 	    s = mousebar_btns;
2230 	    mousebar_width = 0;
2231 	    for (i = 0; i < mousebar_cnt; i++) {
2232 		j = strlen(s);
2233 		if (*s == '[') {
2234 		    mousebar_width += j;
2235 		    s += j + 1;
2236 		    j = strlen(s);
2237 		}
2238 		else
2239 		    mousebar_width += (j < 2? 3+1 : (j == 2? 4+1
2240 						     : (j < 5? j: 5+1)));
2241 		s += j + 1;
2242 	    }
2243 	    xmouse_on();
2244 	}
2245 	else
2246 	    xmouse_off();
2247 	mouse_is_down = FALSE;
2248     }
2249 }
2250 
2251 void
xmouse_on()2252 xmouse_on()
2253 {
2254     if (!xmouse_is_on) {
2255 	/* save old highlight mouse tracking and enable mouse tracking */
2256 	fputs("\033[?1001s\033[?1000h",stdout);
2257 	fflush(stdout);
2258 	xmouse_is_on = TRUE;
2259     }
2260 }
2261 
2262 void
xmouse_off()2263 xmouse_off()
2264 {
2265     if (xmouse_is_on) {
2266 	/* disable mouse tracking and restore old highlight mouse tracking */
2267 	fputs("\033[?1000l\033[?1001r",stdout);
2268 	fflush(stdout);
2269 	xmouse_is_on = FALSE;
2270     }
2271 }
2272 
2273 void
draw_mousebar(limit,restore_cursor)2274 draw_mousebar(limit,restore_cursor)
2275 int limit;
2276 bool_int restore_cursor;
2277 {
2278     int i;
2279     char* s;
2280     char* t;
2281     int save_col = term_col;
2282     int save_line = term_line;
2283 
2284     mousebar_width = 0;
2285     if (mousebar_cnt == 0)
2286 	return;
2287 
2288     s = mousebar_btns;
2289     t = msg;
2290     for (i = 0; i < mousebar_cnt; i++) {
2291 	if (*s == '[') {
2292 	    while (*++s) *t++ = *s;
2293 	    s++;
2294 	}
2295 	else {
2296 	    switch (strlen(s)) {
2297 	      case 0:
2298 		*t++ = ' ';
2299 		/* FALL THROUGH */
2300 	      case 1:  case 2:
2301 		*t++ = ' ';
2302 		while (*s) *t++ = *s++;
2303 		*t++ = ' ';
2304 		break;
2305 	      case 3:  case 4:
2306 		while (*s) *t++ = *s++;
2307 		break;
2308 	      default:
2309 		strncpy(t, s, 5);
2310 		t += 5;
2311 		break;
2312 	    }
2313 	}
2314 	s += strlen(s) + 1;
2315 	*t++ = '\0';
2316     }
2317     mousebar_width = t - msg;
2318     mousebar_start = 0;
2319 
2320     s = msg;
2321     while (mousebar_width > limit) {
2322 	int len = strlen(s) + 1;
2323 	s += len;
2324 	mousebar_width -= len;
2325 	mousebar_start++;
2326     }
2327 
2328     goto_xy(tc_COLS - mousebar_width - 1, tc_LINES-1);
2329     for (i = mousebar_start; i < mousebar_cnt; i++) {
2330 	putchar(' ');
2331 	color_string(COLOR_MOUSE,s);
2332 	s += strlen(s) + 1;
2333     }
2334     term_col = tc_COLS-1;
2335     if (restore_cursor)
2336 	goto_xy(save_col, save_line);
2337 }
2338 
2339 static void
mouse_input(cp)2340 mouse_input(cp)
2341 char* cp;
2342 {
2343     static int last_btn;
2344     static int last_x, last_y;
2345     int btn;
2346     int x, y;
2347 
2348     if (cp[2] < ' ' || cp[2] > ' '+3)
2349 	return;
2350     btn = (cp[2] & 3);
2351     x = cp[1] - 33;
2352     y = cp[0] - 33;
2353 
2354     if (btn != 3) {
2355 #if defined(HAS_GETTIMEOFDAY) || defined(HAS_FTIME)
2356 	static double last_time = 0.;
2357 	double this_time = current_time();
2358 	if (last_btn == btn && last_y == y && this_time - last_time <= 0.75
2359 	 && (last_x == x || last_x == x-1 || last_x == x+1))
2360 	    btn |= 4;
2361 	last_time = this_time;
2362 #endif /* HAS_GETTIMEOFDAY || HAS_FTIME */
2363 	last_btn = (btn & 3);
2364 	last_x = x;
2365 	last_y = y;
2366 	mouse_is_down = TRUE;
2367     }
2368     else {
2369 	if (!mouse_is_down)
2370 	    return;
2371 	mouse_is_down = FALSE;
2372     }
2373 
2374     if (gmode == 's')
2375 	selector_mouse(btn, x,y, last_btn, last_x,last_y);
2376     else if (mode == 'a' || mode == 'p')
2377 	pager_mouse(btn, x,y, last_btn, last_x,last_y);
2378     else
2379 	pushchar(' ');
2380 }
2381 
2382 bool
check_mousebar(btn,x,y,btn_clk,x_clk,y_clk)2383 check_mousebar(btn, x,y, btn_clk, x_clk,y_clk)
2384 int btn;
2385 int x, y;
2386 int btn_clk;
2387 int x_clk, y_clk;
2388 {
2389     char* s = mousebar_btns;
2390     char* t;
2391     int i, j;
2392     int col = tc_COLS - mousebar_width;
2393 
2394     if (mousebar_width != 0 && btn_clk == 0 && y_clk == tc_LINES-1
2395      && (x_clk -= col-1) > 0) {
2396 	x -= col-1;
2397 	for (i = 0; i < mousebar_start; i++) {
2398 	    if (*s == '[')
2399 		s += strlen(s) + 1;
2400 	    s += strlen(s) + 1;
2401 	}
2402 	while (1) {
2403 	    i = strlen(s);
2404 	    t = s;
2405 	    if (*s == '[') {
2406 		s += i + 1;
2407 		j = 0;
2408 		while (*++t == ' ') j++;
2409 	    }
2410 	    else if (i <= 2) {
2411 		i = 3 + (i==2) + 1;
2412 		j = 1;
2413 	    }
2414 	    else {
2415 		i = (i < 5? i+1 : 5+1);
2416 		j = 0;
2417 	    }
2418 	    if (x_clk < i) {
2419 		int tcol = term_col;
2420 		int tline = term_line;
2421 		goto_xy(col+j,tc_LINES-1);
2422 		if (btn == 3)
2423 		    color_object(COLOR_MOUSE, 1);
2424 		if (s == t) {
2425 		    for (j = 0; j < 5 && *t; j++, t++)
2426 			term_col++, putchar(*t);
2427 		}
2428 		else {
2429 		    for (; *t && *t != ' '; t++)
2430 			term_col++, putchar(*t);
2431 		}
2432 		if (btn == 3)
2433 		    color_pop();	/* of COLOR_MOUSE */
2434 		goto_xy(tcol,tline);
2435 		fflush(stdout);
2436 		if (btn == 3 && x > 0 && x < i)
2437 		    pushstring(s,0);
2438 		break;
2439 	    }
2440 	    if (!(x_clk -= i))
2441 		break;
2442 	    x -= i;
2443 	    col += i;
2444 	    s += strlen(s) + 1;
2445 	}
2446 	return TRUE;
2447     }
2448     return FALSE;
2449 }
2450 
2451 static int tc_string_cnt = 0;
2452 
2453 static struct {
2454 	char* capability;	/* name of capability, e.g. "forground red" */
2455 	char* string;		/* escape sequence, e.g. "\033[31m" */
2456 } tc_strings[TC_STRINGS];
2457 
2458 /* Parse a line from the [termcap] section of trnrc. */
2459 void
add_tc_string(capability,string)2460 add_tc_string(capability, string)
2461 char* capability;
2462 char* string;
2463 {
2464     int i;
2465 
2466     for (i = 0; i < tc_string_cnt; i++) {
2467 	if (strEQ(capability, tc_strings[i].capability)) {
2468 	    free(tc_strings[i].string);
2469 	    break;
2470 	}
2471     }
2472     if (i == tc_string_cnt) {
2473 	if (tc_string_cnt == TC_STRINGS) {
2474 	    fprintf(stderr,"trn: too many colors in [termcap] section (max is %d).\n",
2475 		    TC_STRINGS);
2476 	    finalize(1);
2477 	}
2478 	tc_string_cnt++;
2479 	tc_strings[i].capability = savestr(capability);
2480     }
2481 
2482     tc_strings[i].string = savestr(string);
2483 }
2484 
2485 /* Return the named termcap color capability's string. */
2486 char*
tc_color_capability(capability)2487 tc_color_capability(capability)
2488 char* capability;
2489 {
2490     int c;
2491 
2492     for (c = 0; c < tc_string_cnt; c++) {
2493 	if (strEQ(tc_strings[c].capability, capability))
2494 	    return tc_strings[c].string;
2495     }
2496     return NULL;
2497 }
2498 
2499 #ifdef MSDOS
2500 int
tputs(str,num,func)2501 tputs(str,num,func)
2502 char* str;
2503 int num;
2504 int (*func) _((char_int));
2505 {
2506     printf(str,num);
2507     return 0;
2508 }
2509 #endif
2510 
2511 #ifdef MSDOS
2512 char*
tgoto(str,x,y)2513 tgoto(str,x,y)
2514 char* str;
2515 int x;
2516 int y;
2517 {
2518     static char gbuf[32];
2519     sprintf(gbuf,str,y+1,x+1);
2520     return gbuf;
2521 }
2522 #endif
2523