1 /*
2  * term.c: termcap stuff...
3  *
4  * Written By Michael Sandrof
5  * HP-UX modifications by Mark T. Dame (Mark.Dame@uc.edu)
6  * Termio modifications by Stellan Klebom (d88-skl@nada.kth.se)
7  * Many, many cleanups, modifications, and some new broken code
8  * added by Scott Reynolds (scottr@edsi.org), June 1995.
9  *
10  * Copyright (c) 1990 Michael Sandrof.
11  * Copyright (c) 1991, 1992 Troy Rollo.
12  * Copyright (c) 1992-2014 Matthew R. Green.
13  * All rights reserved.
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions
17  * are met:
18  * 1. Redistributions of source code must retain the above copyright
19  *    notice, this list of conditions and the following disclaimer.
20  * 2. Redistributions in binary form must reproduce the above copyright
21  *    notice, this list of conditions and the following disclaimer in the
22  *    documentation and/or other materials provided with the distribution.
23  * 3. The name of the author may not be used to endorse or promote products
24  *    derived from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
27  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
33  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #include "irc.h"
40 IRCII_RCSID("@(#)$eterna: term.c,v 1.118 2020/11/17 08:11:57 mrg Exp $");
41 
42 #ifdef HAVE_SYS_IOCTL_H
43 # include <sys/ioctl.h>
44 #endif /* HAVE_SYS_IOCTL_H */
45 
46 #include <sys/stat.h>
47 #include <termios.h>
48 
49 #ifndef CBREAK
50 # define CBREAK RAW
51 #endif
52 
53 #include "ircterm.h"
54 #include "translat.h"
55 #include "window.h"
56 #include "screen.h"
57 #include "output.h"
58 
59 #ifndef	STTY_ONLY
60 static int	term_CE_clear_to_eol(void);
61 static int	term_CS_scroll(int, int, int);
62 static int	term_ALDL_scroll(int, int, int);
63 static int	term_param_ALDL_scroll(int, int, int);
64 static int	term_IC_insert(u_int);
65 static int	term_IMEI_insert(u_int);
66 static int	term_DC_delete(void);
67 static int	term_null_function(void);
68 static int	term_BS_cursor_left(void);
69 static int	term_LE_cursor_left(void);
70 static int	term_ND_cursor_right(void);
71 static void	tputs_x(const char *);
72 #endif /* STTY_ONLY */
73 
74 
75 static	int	tty_des;		/* descriptor for the tty */
76 
77 static struct termios	oldb,
78 			newb;
79 
80 #ifndef STTY_ONLY
81 
82 #if defined(__CYGWIN__) || defined(__CYGWIN32__)
83 #define TGETENT_BUFSIZ 2048
84 #else
85 #define TGETENT_BUFSIZ 1024
86 #endif
87 
88 static	char	termcap[TGETENT_BUFSIZ];
89 
90 /*
91  * Function variables: each returns 1 if the function is not supported on the
92  * current term type, otherwise they do their thing and return 0
93  */
94 static	int	(*term_scroll_func)(int, int, int);	/* best scroll */
95 static	int	(*term_insert_func)(u_int);		/* best insert */
96 static	int	(*term_delete_func)(void);		/* best delete */
97 static	int	(*term_cursor_left_func)(void);		/* best left */
98 static	int	(*term_cursor_right_func)(void);	/* best right */
99 static	int	(*term_clear_to_eol_func)(void);	/* figure it out */
100 
101 /* The termcap variables */
102 static	char	*CM,
103 		*CE,
104 		*CL,
105 		*CR,
106 		*NL,
107 		*AL,
108 		*DL,
109 		*CS,
110 		*DC,
111 		*IC,
112 		*IM,
113 		*EI,
114 		*SO,
115 		*SE,
116 		*US,
117 		*UE,
118 		*MD,
119 		*ME,
120 		*SF,
121 		*SR,
122 		*ND,
123 		*LE,
124 		*BL,
125 		*TI,
126 		*TE;
127 static	int	SG;
128 
129 /*
130  * term_reset_flag: set to true whenever the terminal is reset, thus letting
131  * the calling program work out what to do
132  */
133 static	int	term_reset_flag = 0;
134 
135 static	FILE	*term_fp = 0;
136 static	int	li, co;
137 
138 void
term_set_fp(FILE * fp)139 term_set_fp(FILE *fp)
140 {
141 	term_fp = fp;
142 }
143 
144 /* putchar_x: the putchar function used by tputs */
145 TPUTSRETVAL
putchar_x(TPUTSARGVAL c)146 putchar_x(TPUTSARGVAL c)
147 {
148 	fputc(c, term_fp);
149 #ifndef TPUTSVOIDRET	/* what the hell is this value used for anyway? */
150 	return (0);
151 #endif
152 }
153 
154 void
term_flush(void)155 term_flush(void)
156 {
157 	fflush(term_fp);
158 }
159 
160 /*
161  * term_reset: sets terminal attributed back to what they were before the
162  * program started
163  */
164 void
term_reset(void)165 term_reset(void)
166 {
167 	tcsetattr(tty_des, TCSADRAIN, &oldb);
168 
169 	if (CS)
170 		tputs_x(tgoto(CS, get_li() - 1, 0));
171 	if (!use_termcap_enterexit() && TE)
172 		tputs_x(TE);
173 	term_move_cursor(0, get_li() - 1);
174 	term_reset_flag = 1;
175 	term_flush();
176 }
177 
178 /*
179  * term_cont: sets the terminal back to IRCII stuff when it is restarted
180  * after a SIGSTOP.  Somewhere, this must be used in a signal() call
181  */
182 void
term_cont(int signo)183 term_cont(int signo)
184 {
185 	tcsetattr(tty_des, TCSADRAIN, &newb);
186 
187 	if (!use_termcap_enterexit() && TI)
188 		tputs_x(TI);
189 
190 	on_signal_occurred(signo);
191 }
192 
193 /*
194  * term_pause: sets terminal back to pre-program days, then SIGSTOPs itself.
195  */
196 void
term_pause(u_int key,u_char * ptr)197 term_pause(u_int key, u_char *ptr)
198 {
199 #if !defined(SIGSTOP) || !defined(SIGTSTP)
200 	say("The STOP_IRC function does not work on this system type.");
201 #else
202 	term_reset();
203 	kill(getpid(), SIGSTOP);
204 #endif /* !SIGSTOP */
205 }
206 #endif /* STTY_ONLY */
207 
208 /*
209  * term_init: does all terminal initialization... reads termcap info, sets
210  * the terminal to CBREAK, no ECHO mode.   Chooses the best of the terminal
211  * attributes to use ..  for the version of this function that is called for
212  * wserv, we set the termial to RAW, no ECHO, so that all the signals are
213  * ignored.. fixes quite a few problems...  -phone, jan 1993..
214  */
215 void
term_init(void)216 term_init(void)
217 {
218 #ifndef	STTY_ONLY
219 	char	bp[TGETENT_BUFSIZ],
220 		*term,
221 		*ptr;
222 
223 	if ((term = getenv("TERM")) == NULL)
224 	{
225 		fprintf(stderr, "irc: No TERM variable set!\n");
226 		fprintf(stderr,"irc: You may still run irc by using the -d switch\n");
227 		exit(1);
228 	}
229 	if (tgetent(bp, term) < 1)
230 	{
231 		fprintf(stderr, "irc: No termcap entry for %s.\n", term);
232 		fprintf(stderr,"irc: You may still run irc by using the -d switch\n");
233 		exit(1);
234 	}
235 
236 	if ((co = tgetnum("co")) == -1)
237 		co = 80;
238 	if ((li = tgetnum("li")) == -1)
239 		li = 24;
240 	ptr = termcap;
241 
242 	/*
243 	 * Thanks to Max Bell (mbell@cie.uoregon.edu) for info about TVI
244 	 * terminals and the sg terminal capability
245 	 */
246 	SG = tgetnum("sg");
247 	CM = tgetstr("cm", &ptr);
248 	CL = tgetstr("cl", &ptr);
249 	if ((CM == NULL) ||
250 	    (CL == NULL))
251 	{
252 		fprintf(stderr, "This terminal does not have the necessary "
253 				 "capabilities to run IRCII\n"
254 				 "in full screen mode. You may still run "
255 				 "irc by using the -d switch\n");
256 		exit(1);
257 	}
258 	if ((CR = tgetstr("cr", &ptr)) == NULL)
259 		CR = "\r";
260 	if ((NL = tgetstr("nl", &ptr)) == NULL)
261 		NL = "\n";
262 
263 	if ((CE = tgetstr("ce", &ptr)) != NULL)
264 		term_clear_to_eol_func = term_CE_clear_to_eol;
265 	else
266 		term_clear_to_eol_func = term_null_function;
267 
268 	TE = tgetstr("te", &ptr);
269 	if (!use_termcap_enterexit() && TE && (TI = tgetstr("ti", &ptr)) != NULL )
270 		tputs_x(TI);
271 	else
272 		TE = TI = NULL;
273 
274 	/* if ((ND = tgetstr("nd", &ptr)) || (ND = tgetstr("kr", &ptr))) */
275 	if ((ND = tgetstr("nd", &ptr)) != NULL)
276 		term_cursor_right_func = term_ND_cursor_right;
277 	else
278 		term_cursor_right_func = term_null_function;
279 
280 	/* if ((LE = tgetstr("le", &ptr)) || (LE = tgetstr("kl", &ptr))) */
281 	if ((LE = tgetstr("le", &ptr)) != NULL)
282 		term_cursor_left_func = term_LE_cursor_left;
283 	else if (tgetflag("bs"))
284 		term_cursor_left_func = term_BS_cursor_left;
285 	else
286 		term_cursor_left_func = term_null_function;
287 
288 	SF = tgetstr("sf", &ptr);
289 	SR = tgetstr("sr", &ptr);
290 
291 	if ((CS = tgetstr("cs", &ptr)) != NULL)
292 		term_scroll_func = term_CS_scroll;
293 	else if ((AL = tgetstr("AL", &ptr)) && (DL = tgetstr("DL", &ptr)))
294 		term_scroll_func = term_param_ALDL_scroll;
295 	else if ((AL = tgetstr("al", &ptr)) && (DL = tgetstr("dl", &ptr)))
296 		term_scroll_func = term_ALDL_scroll;
297 	else
298 		term_scroll_func = (int (*)(int, int, int)) term_null_function;
299 
300 	if ((IC = tgetstr("ic", &ptr)) != NULL)
301 		term_insert_func = term_IC_insert;
302 	else
303 	{
304 		if ((IM = tgetstr("im", &ptr)) && (EI = tgetstr("ei", &ptr)))
305 			term_insert_func = term_IMEI_insert;
306 		else
307 			term_insert_func = (int (*)(u_int)) term_null_function;
308 	}
309 
310 	if ((DC = tgetstr("dc", &ptr)) != NULL)
311 		term_delete_func = term_DC_delete;
312 	else
313 		term_delete_func = term_null_function;
314 
315 	SO = tgetstr("so", &ptr);
316 	SE = tgetstr("se", &ptr);
317 	if ((SO == NULL) || (SE == NULL))
318 	{
319 		SO = CP(empty_string());
320 		SE = CP(empty_string());
321 	}
322 	US = tgetstr("us", &ptr);
323 	UE = tgetstr("ue", &ptr);
324 	if ((US == NULL) || (UE == NULL))
325 	{
326 		US = CP(empty_string());
327 		UE = CP(empty_string());
328 	}
329 	MD = tgetstr("md", &ptr);
330 	ME = tgetstr("me", &ptr);
331 	if ((MD == NULL) || (ME == NULL))
332 	{
333 		MD = CP(empty_string());
334 		ME = CP(empty_string());
335 	}
336 	if ((BL = tgetstr("bl", &ptr)) == NULL)
337 		BL = "\007";
338 #endif /* STTY_ONLY */
339 
340 	if (getenv("IRC_DEBUG")|| (tty_des = open("/dev/tty", O_RDWR, 0)) == -1)
341 		tty_des = 0;
342 
343 	tcgetattr(tty_des, &oldb);
344 
345 	newb = oldb;
346 	newb.c_lflag &= ~(ICANON | ECHO);	/* set equivalent of
347 						 * CBREAK and no ECHO
348 						 */
349 	newb.c_cc[VMIN] = 1;	/* read() satified after 1 char */
350 	newb.c_cc[VTIME] = 0;	/* No timer */
351 
352 #ifndef _POSIX_VDISABLE
353 # define _POSIX_VDISABLE 0
354 #endif
355 
356 	newb.c_cc[VQUIT] = _POSIX_VDISABLE;
357 #ifdef VDISCARD
358 	newb.c_cc[VDISCARD] = _POSIX_VDISABLE;
359 #endif
360 #ifdef VDSUSP
361 	newb.c_cc[VDSUSP] = _POSIX_VDISABLE;
362 #endif
363 #ifdef VSUSP
364 	newb.c_cc[VSUSP] = _POSIX_VDISABLE;
365 #endif
366 
367 #ifndef STTY_ONLY
368 	if (!use_flow_control())
369 		newb.c_iflag &= ~IXON;	/* No XON/XOFF */
370 #endif /* STTY_ONLY */
371 
372 	tcsetattr(tty_des, TCSADRAIN, &newb);
373 }
374 
375 
376 #ifndef STTY_ONLY
377 /*
378  * term_resize: gets the terminal height and width.  Trys to get the info
379  * from the tty driver about size, if it can't... uses the termcap values. If
380  * the terminal size has changed since last time term_resize() has been
381  * called, 1 is returned.  If it is unchanged, 0 is returned.
382  */
383 int
term_resize(void)384 term_resize(void)
385 {
386 	int	new_li = li, new_co = co;
387 
388 	/*
389 	 * if we're not the main screen, we've probably arrived here via
390 	 * the wserv message path, and we should have already setup the
391 	 * values of "li" and "co".
392 	 */
393 	if (is_main_screen(get_current_screen()))
394 	{
395 #ifdef TIOCGWINSZ
396 		struct	winsize window;
397 
398 		if (ioctl(tty_des, TIOCGWINSZ, &window) == 0)
399 		{
400 			new_li = window.ws_row;
401 			new_co = window.ws_col;
402 		}
403 #endif /* TIOCGWINSZ */
404 #ifndef TERM_USE_LAST_COLUMN
405 		new_co--;
406 #endif
407 	}
408 	return screen_set_size(new_li, new_co);
409 }
410 
411 /*
412  * term_null_function: used when a terminal is missing a particulary useful
413  * feature, such as scrolling, to warn the calling program that no such
414  * function exists
415  */
416 static	int
term_null_function(void)417 term_null_function(void)
418 {
419 	return (1);
420 }
421 
422 /* term_CE_clear_to_eol(): the clear to eol function, right? */
423 static	int
term_CE_clear_to_eol(void)424 term_CE_clear_to_eol(void)
425 {
426 	tputs_x(CE);
427 	return (0);
428 }
429 
430 /* * term_space_erase: this can be used if term_CE_clear_to_eol() returns 1.
431  * This will erase from x to the end of the screen uses space.  Actually, it
432  * doesn't reposition the cursor at all, so the cursor must be in the correct
433  * spot at the beginning and you must move it back afterwards
434  */
435 void
term_space_erase(int x)436 term_space_erase(int x)
437 {
438 	int	i,
439 		cnt;
440 
441 	cnt = get_co() - x;
442 	for (i = 0; i < cnt; i++)
443 		fputc(' ', term_fp);
444 }
445 
446 /*
447  * term_CS_scroll: should be used if the terminal has the CS capability by
448  * setting term_scroll equal to it
449  */
450 static	int
term_CS_scroll(int line1,int line2,int n)451 term_CS_scroll(int line1, int line2, int n)
452 {
453 	int	i;
454 	u_char	*thing;
455 
456 	if (n > 0)
457 		thing = UP(SF ? SF : NL);
458 	else if (n < 0)
459 	{
460 		if (SR)
461 			thing = UP(SR);
462 		else
463 			return 1;
464 	}
465 	else
466 		return 0;
467 	tputs_x(tgoto(CS, line2, line1));  /* shouldn't do this each time */
468 	if (n < 0)
469 	{
470 		term_move_cursor(0, line1);
471 		n = -n;
472 	}
473 	else
474 		term_move_cursor(0, line2);
475 	for (i = 0; i < n; i++)
476 		tputs_x(CP(thing));
477 	tputs_x(tgoto(CS, get_li() - 1, 0));	/* shouldn't do this each time */
478 	return (0);
479 }
480 
481 /*
482  * term_ALDL_scroll: should be used for scrolling if the term has AL and DL
483  * by setting the term_scroll function to it
484  */
485 static	int
term_ALDL_scroll(int line1,int line2,int n)486 term_ALDL_scroll(int line1, int line2, int n)
487 {
488 	int	i;
489 
490 	if (n > 0)
491 	{
492 		term_move_cursor(0, line1);
493 		for (i = 0; i < n; i++)
494 			tputs_x(DL);
495 		term_move_cursor(0, line2 - n + 1);
496 		for (i = 0; i < n; i++)
497 			tputs_x(AL);
498 	}
499 	else if (n < 0)
500 	{
501 		n = -n;
502 		term_move_cursor(0, line2-n+1);
503 		for (i = 0; i < n; i++)
504 			tputs_x(DL);
505 		term_move_cursor(0, line1);
506 		for (i = 0; i < n; i++)
507 			tputs_x(AL);
508 	}
509 	return (0);
510 }
511 
512 /*
513  * term_param_ALDL_scroll: Uses the parameterized version of AL and DL
514  */
515 static	int
term_param_ALDL_scroll(int line1,int line2,int n)516 term_param_ALDL_scroll(int line1, int line2, int n)
517 {
518 	if (n > 0)
519 	{
520 		term_move_cursor(0, line1);
521 		tputs_x(tgoto(DL, n, n));
522 		term_move_cursor(0, line2 - n + 1);
523 		tputs_x(tgoto(AL, n, n));
524 	}
525 	else if (n < 0)
526 	{
527 		n = -n;
528 		term_move_cursor(0, line2-n+1);
529 		tputs_x(tgoto(DL, n, n));
530 		term_move_cursor(0, line1);
531 		tputs_x(tgoto(AL, n, n));
532 	}
533 	return (0);
534 }
535 
536 /*
537  * term_IC_insert: should be used for character inserts if the term has IC by
538  * setting term_insert to it.
539  */
540 static	int
term_IC_insert(u_int c)541 term_IC_insert(u_int c)
542 {
543 	tputs_x(IC);
544 	putchar_x(c);
545 	return (0);
546 }
547 
548 /*
549  * term_IMEI_insert: should be used for character inserts if the term has IM
550  * and EI by setting term_insert to it
551  */
552 static	int
term_IMEI_insert(u_int c)553 term_IMEI_insert(u_int c)
554 {
555 	tputs_x(IM);
556 	putchar_x(c);
557 	tputs_x(EI);
558 	return (0);
559 }
560 
561 /*
562  * term_DC_delete: should be used for character deletes if the term has DC by
563  * setting term_delete to it
564  */
565 static	int
term_DC_delete(void)566 term_DC_delete(void)
567 {
568 	tputs_x(DC);
569 	return (0);
570 }
571 
572 /* term_ND_cursor_right: got it yet? */
573 static	int
term_ND_cursor_right(void)574 term_ND_cursor_right(void)
575 {
576 	tputs_x(ND);
577 	return (0);
578 }
579 
580 /* term_LE_cursor_left:  shouldn't you move on to something else? */
581 static	int
term_LE_cursor_left(void)582 term_LE_cursor_left(void)
583 {
584 	tputs_x(LE);
585 	return (0);
586 }
587 
588 static	int
term_BS_cursor_left(void)589 term_BS_cursor_left(void)
590 {
591 	fputc('\010', term_fp);
592 	return (0);
593 }
594 
595 void
copy_window_size(int * nlines,int * cols)596 copy_window_size(int *nlines, int *cols)
597 {
598 	if (nlines)
599 		*nlines = get_li();
600 	if (cols)
601 		*cols = get_co();
602 }
603 
604 static void
tputs_x(const char * s)605 tputs_x(const char *s)
606 {
607 	tputs(s, 0, putchar_x);
608 }
609 
610 void
term_underline_on(void)611 term_underline_on(void)
612 {
613 	tputs_x(US);
614 }
615 
616 void
term_underline_off(void)617 term_underline_off(void)
618 {
619 	tputs_x(UE);
620 }
621 
622 void
term_standout_on(void)623 term_standout_on(void)
624 {
625 	tputs_x(SO);
626 }
627 
628 void
term_standout_off(void)629 term_standout_off(void)
630 {
631 	tputs_x(SE);
632 }
633 
634 void
term_clear_screen(void)635 term_clear_screen(void)
636 {
637 	tputs_x(CL);
638 }
639 
640 void
term_move_cursor(int c,int r)641 term_move_cursor(int c, int r)
642 {
643 	tputs_x(tgoto(CM, c, r));
644 }
645 
646 void
term_cr(void)647 term_cr(void)
648 {
649 	tputs_x(CR);
650 }
651 
652 void
term_newline(void)653 term_newline(void)
654 {
655 	tputs_x(NL);
656 }
657 
658 void
term_beep(void)659 term_beep(void)
660 {
661 	tputs_x(BL);
662 	Screen *screen = get_current_screen();
663 
664 	fflush(screen ? screen_get_fpout(screen) : stdout);
665 }
666 
667 void
term_bold_on(void)668 term_bold_on(void)
669 {
670 	tputs_x(MD);
671 }
672 
673 void
term_bold_off(void)674 term_bold_off(void)
675 {
676 	tputs_x(ME);
677 }
678 
679 int
term_eight_bit(void)680 term_eight_bit(void)
681 {
682 	return (((oldb.c_cflag) & CSIZE) == CS8) ? 1 : 0;
683 }
684 #endif /* STTY_ONLY */
685 
686 void
set_term_eight_bit(int value)687 set_term_eight_bit(int value)
688 {
689 #ifndef	STTY_ONLY
690 	if (term_basic())
691 		return;
692 #endif /* STTY_ONLY */
693 	if (value == ON)
694 	{
695 		newb.c_cflag |= CS8;
696 		newb.c_iflag &= ~ISTRIP;
697 	}
698 	else
699 	{
700 		newb.c_cflag &= ~CS8;
701 		newb.c_iflag |= ISTRIP;
702 	}
703 	tcsetattr(tty_des, TCSADRAIN, &newb);
704 }
705 
706 #ifndef	STTY_ONLY
707 void
term_check_refresh(void)708 term_check_refresh(void)
709 {
710 	if (term_reset_flag)
711 	{
712 		refresh_screen(0, NULL);
713 		term_reset_flag = 0;
714 	}
715 }
716 
717 int
term_scroll(int line1,int line2,int n)718 term_scroll(int line1, int line2, int n)
719 {
720 	return (*term_scroll_func)(line1, line2, n);
721 }
722 
723 int
term_insert(u_int c)724 term_insert(u_int c)
725 {
726 	return (*term_insert_func)(c);
727 }
728 
729 int
term_delete(void)730 term_delete(void)
731 {
732 	return (*term_delete_func)();
733 }
734 
735 int
term_cursor_right(void)736 term_cursor_right(void)
737 {
738 	return (*term_cursor_right_func)();
739 }
740 
741 int
term_cursor_left(void)742 term_cursor_left(void)
743 {
744 	return (*term_cursor_left_func)();
745 }
746 
747 int
term_clear_to_eol(void)748 term_clear_to_eol(void)
749 {
750 	return (*term_clear_to_eol_func)();
751 }
752 #endif /* STTY_ONLY */
753