xref: /netbsd-src/external/bsd/nvi/dist/cl/cl_screen.c (revision 6798bdb86d03ad4512598e1546baa70065279f27)
1 /*	$NetBSD: cl_screen.c,v 1.6 2017/09/01 07:21:01 mlelstv Exp $ */
2 /*-
3  * Copyright (c) 1993, 1994
4  *	The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1993, 1994, 1995, 1996
6  *	Keith Bostic.  All rights reserved.
7  *
8  * See the LICENSE file for redistribution information.
9  */
10 
11 #include "config.h"
12 
13 #include <sys/cdefs.h>
14 #if 0
15 #ifndef lint
16 static const char sccsid[] = "Id: cl_screen.c,v 10.56 2002/05/03 19:59:44 skimo Exp  (Berkeley) Date: 2002/05/03 19:59:44 ";
17 #endif /* not lint */
18 #else
19 __RCSID("$NetBSD: cl_screen.c,v 1.6 2017/09/01 07:21:01 mlelstv Exp $");
20 #endif
21 
22 #include <sys/types.h>
23 #include <sys/queue.h>
24 
25 #include <bitstring.h>
26 #include <errno.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <termios.h>
32 #include <unistd.h>
33 
34 #include "../common/common.h"
35 #include "cl.h"
36 
37 #ifndef BLOCK_SIGNALS
38 extern sigset_t __sigblockset;
39 #endif
40 
41 static int	cl_ex_end __P((GS *));
42 static int	cl_ex_init __P((SCR *));
43 static void	cl_freecap __P((CL_PRIVATE *));
44 static int	cl_vi_end __P((GS *));
45 static int	cl_vi_init __P((SCR *));
46 static int	cl_putenv __P((SCR *, const char *, const char *, u_long));
47 
48 /*
49  * cl_screen --
50  *	Switch screen types.
51  *
52  * PUBLIC: int cl_screen __P((SCR *, u_int32_t));
53  */
54 int
cl_screen(SCR * sp,u_int32_t flags)55 cl_screen(SCR *sp, u_int32_t flags)
56 {
57 	CL_PRIVATE *clp;
58 	WINDOW *win;
59 	GS *gp;
60 	int ret, error;
61 	sigset_t oset;
62 
63 	gp = sp->gp;
64 	clp = CLP(sp);
65 	win = CLSP(sp) ? CLSP(sp) : stdscr;
66 
67 	ret = 0;
68 
69 	/*
70 	 * During initialization of the screen, block signals to make sure that
71 	 * curses/terminfo routines are not interrupted.
72 	 */
73 	error = sigprocmask(SIG_BLOCK, &__sigblockset, &oset);
74 
75 	/* See if the current information is incorrect. */
76 	if (F_ISSET(gp, G_SRESTART)) {
77 		if (CLSP(sp)) {
78 		    delwin(CLSP(sp));
79 		    sp->cl_private = NULL;
80 		}
81 		if (cl_quit(gp)) {
82 			ret = 1;
83 			goto end;
84 		}
85 		F_CLR(gp, G_SRESTART);
86 	}
87 
88 	/* See if we're already in the right mode. */
89 	if ((LF_ISSET(SC_EX) && F_ISSET(sp, SC_SCR_EX)) ||
90 	    (LF_ISSET(SC_VI) && F_ISSET(sp, SC_SCR_VI)))
91 		goto end;
92 
93 	/*
94 	 * Fake leaving ex mode.
95 	 *
96 	 * We don't actually exit ex or vi mode unless forced (e.g. by a window
97 	 * size change).  This is because many curses implementations can't be
98 	 * called twice in a single program.  Plus, it's faster.  If the editor
99 	 * "leaves" vi to enter ex, when it exits ex we'll just fall back into
100 	 * vi.
101 	 */
102 	if (F_ISSET(sp, SC_SCR_EX))
103 		F_CLR(sp, SC_SCR_EX);
104 
105 	/*
106 	 * Fake leaving vi mode.
107 	 *
108 	 * Clear out the rest of the screen if we're in the middle of a split
109 	 * screen.  Move to the last line in the current screen -- this makes
110 	 * terminal scrolling happen naturally.  Note: *don't* move past the
111 	 * end of the screen, as there are ex commands (e.g., :read ! cat file)
112 	 * that don't want to.  Don't clear the info line, its contents may be
113 	 * valid, e.g. :file|append.
114 	 */
115 	if (F_ISSET(sp, SC_SCR_VI)) {
116 		F_CLR(sp, SC_SCR_VI);
117 
118 		if (TAILQ_NEXT(sp, q) != NULL) {
119 			(void)wmove(win, RLNO(sp, sp->rows), 0);
120 			wclrtobot(win);
121 		}
122 		(void)wmove(win, RLNO(sp, sp->rows) - 1, 0);
123 		wrefresh(win);
124 	}
125 
126 	/* Enter the requested mode. */
127 	if (LF_ISSET(SC_EX)) {
128 		if (cl_ex_init(sp)) {
129 			ret = 1;
130 			goto end;
131 		}
132 		F_SET(clp, CL_IN_EX | CL_SCR_EX_INIT);
133 
134 		/*
135 		 * If doing an ex screen for ex mode, move to the last line
136 		 * on the screen.
137 		 */
138 		if (F_ISSET(sp, SC_EX) && clp->cup != NULL)
139 			tputs(tgoto(clp->cup,
140 			    0, O_VAL(sp, O_LINES) - 1), 1, cl_putchar);
141 	} else {
142 		if (cl_vi_init(sp)) {
143 			ret = 1;
144 			goto end;
145 		}
146 		F_CLR(clp, CL_IN_EX);
147 		F_SET(clp, CL_SCR_VI_INIT);
148 	}
149 end:
150 	/* Unblock signals. */
151 	if (error == 0)
152 		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
153 	return ret;
154 }
155 
156 /*
157  * cl_quit --
158  *	Shutdown the screens.
159  *
160  * PUBLIC: int cl_quit __P((GS *));
161  */
162 int
cl_quit(GS * gp)163 cl_quit(GS *gp)
164 {
165 	CL_PRIVATE *clp;
166 	int rval;
167 
168 	rval = 0;
169 	clp = GCLP(gp);
170 
171 	/*
172 	 * If we weren't really running, ignore it.  This happens if the
173 	 * screen changes size before we've called curses.
174 	 */
175 	if (!F_ISSET(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT))
176 		return (0);
177 
178 	/* Clean up the terminal mappings. */
179 	if (cl_term_end(gp))
180 		rval = 1;
181 
182 	/* Really leave vi mode. */
183 	if (F_ISSET(clp, CL_STDIN_TTY) &&
184 	    F_ISSET(clp, CL_SCR_VI_INIT) && cl_vi_end(gp))
185 		rval = 1;
186 
187 	/* Really leave ex mode. */
188 	if (F_ISSET(clp, CL_STDIN_TTY) &&
189 	    F_ISSET(clp, CL_SCR_EX_INIT) && cl_ex_end(gp))
190 		rval = 1;
191 
192 	/*
193 	 * If we were running ex when we quit, or we're using an implementation
194 	 * of curses where endwin() doesn't get this right, restore the original
195 	 * terminal modes.
196 	 *
197 	 * XXX
198 	 * We always do this because it's too hard to figure out what curses
199 	 * implementations get it wrong.  It may discard type-ahead characters
200 	 * from the tty queue.
201 	 */
202 	(void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig);
203 
204 	F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT);
205 	return (rval);
206 }
207 
208 /*
209  * cl_vi_init --
210  *	Initialize the curses vi screen.
211  */
212 static int
cl_vi_init(SCR * sp)213 cl_vi_init(SCR *sp)
214 {
215 	CL_PRIVATE *clp;
216 	char *o_cols, *o_lines, *o_term;
217 	const char *ttype;
218 
219 	clp = CLP(sp);
220 
221 	/* If already initialized, just set the terminal modes. */
222 	if (F_ISSET(clp, CL_SCR_VI_INIT))
223 		goto fast;
224 
225 	/* Curses vi always reads from (and writes to) a terminal. */
226 	if (!F_ISSET(clp, CL_STDIN_TTY) || !isatty(STDOUT_FILENO)) {
227 		msgq(sp, M_ERR,
228 		    "016|Vi's standard input and output must be a terminal");
229 		return (1);
230 	}
231 
232 	/* We'll need a terminal type. */
233 	if (opts_empty(sp, O_TERM, 0))
234 		return (1);
235 	ttype = O_STR(sp, O_TERM);
236 
237 	/*
238 	 * XXX
239 	 * Changing the row/column and terminal values is done by putting them
240 	 * into the environment, which is then read by curses.  What this loses
241 	 * in ugliness, it makes up for in stupidity.  We can't simply put the
242 	 * values into the environment ourselves, because in the presence of a
243 	 * kernel mechanism for returning the window size, entering values into
244 	 * the environment will screw up future screen resizing events, e.g. if
245 	 * the user enters a :shell command and then resizes their window.  So,
246 	 * if they weren't already in the environment, we make sure to delete
247 	 * them immediately after setting them.
248 	 *
249 	 * XXX
250 	 * Putting the TERM variable into the environment is necessary, even
251 	 * though we're using newterm() here.  We may be using initscr() as
252 	 * the underlying function.
253 	 */
254 	o_term = getenv("TERM");
255 	cl_putenv(sp, "TERM", ttype, 0);
256 	o_lines = getenv("LINES");
257 	cl_putenv(sp, "LINES", NULL, (u_long)O_VAL(sp, O_LINES));
258 	o_cols = getenv("COLUMNS");
259 	cl_putenv(sp, "COLUMNS", NULL, (u_long)O_VAL(sp, O_COLUMNS));
260 
261 	/* Delete cur_term if exists. */
262 	if (F_ISSET(clp, CL_SETUPTERM)) {
263 		if (del_curterm(cur_term))
264 			return (1);
265 		F_CLR(clp, CL_SETUPTERM);
266 	}
267 
268 	/*
269 	 * XXX
270 	 * The SunOS initscr() can't be called twice.  Don't even think about
271 	 * using it.  It fails in subtle ways (e.g. select(2) on fileno(stdin)
272 	 * stops working).  (The SVID notes that applications should only call
273 	 * initscr() once.)
274 	 *
275 	 * XXX
276 	 * The HP/UX newterm doesn't support the NULL first argument, so we
277 	 * have to specify the terminal type.
278 	 */
279 	errno = 0;
280 	if ((clp->screen = newterm(__UNCONST(ttype), stdout, stdin)) == NULL) {
281 		if (errno)
282 			msgq(sp, M_SYSERR, "%s", ttype);
283 		else
284 			msgq(sp, M_ERR, "%s: unknown terminal type", ttype);
285 		return (1);
286 	}
287 
288 	if (o_term == NULL)
289 		cl_unsetenv(sp, "TERM");
290 	if (o_lines == NULL)
291 		cl_unsetenv(sp, "LINES");
292 	if (o_cols == NULL)
293 		cl_unsetenv(sp, "COLUMNS");
294 
295 	/*
296 	 * XXX
297 	 * Someone got let out alone without adult supervision -- the SunOS
298 	 * newterm resets the signal handlers.  There's a race, but it's not
299 	 * worth closing.
300 	 */
301 	(void)sig_init(sp->gp, sp);
302 
303 	/*
304 	 * We use raw mode.  What we want is 8-bit clean, however, signals
305 	 * and flow control should continue to work.  Admittedly, it sounds
306 	 * like cbreak, but it isn't.  Using cbreak() can get you additional
307 	 * things like IEXTEN, which turns on flags like DISCARD and LNEXT.
308 	 *
309 	 * !!!
310 	 * If raw isn't turning off echo and newlines, something's wrong.
311 	 * However, it shouldn't hurt.
312 	 */
313 	noecho();			/* No character echo. */
314 	nonl();				/* No CR/NL translation. */
315 	raw();				/* 8-bit clean. */
316 	idlok(stdscr, 1);		/* Use hardware insert/delete line. */
317 
318 	/* Put the cursor keys into application mode. */
319 	(void)keypad(stdscr, TRUE);
320 
321 	/*
322 	 * XXX
323 	 * The screen TI sequence just got sent.  See the comment in
324 	 * cl_funcs.c:cl_attr().
325 	 */
326 	clp->ti_te = TI_SENT;
327 
328 	/*
329 	 * XXX
330 	 * Historic implementations of curses handled SIGTSTP signals
331 	 * in one of three ways.  They either:
332 	 *
333 	 *	1: Set their own handler, regardless.
334 	 *	2: Did not set a handler if a handler was already installed.
335 	 *	3: Set their own handler, but then called any previously set
336 	 *	   handler after completing their own cleanup.
337 	 *
338 	 * We don't try and figure out which behavior is in place, we force
339 	 * it to SIG_DFL after initializing the curses interface, which means
340 	 * that curses isn't going to take the signal.  Since curses isn't
341 	 * reentrant (i.e., the whole curses SIGTSTP interface is a fantasy),
342 	 * we're doing The Right Thing.
343 	 */
344 	(void)signal(SIGTSTP, SIG_DFL);
345 
346 	/*
347 	 * If flow control was on, turn it back on.  Turn signals on.  ISIG
348 	 * turns on VINTR, VQUIT, VDSUSP and VSUSP.   The main curses code
349 	 * already installed a handler for VINTR.  We're going to disable the
350 	 * other three.
351 	 *
352 	 * XXX
353 	 * We want to use ^Y as a vi scrolling command.  If the user has the
354 	 * DSUSP character set to ^Y (common practice) clean it up.  As it's
355 	 * equally possible that the user has VDSUSP set to 'a', we disable
356 	 * it regardless.  It doesn't make much sense to suspend vi at read,
357 	 * so I don't think anyone will care.  Alternatively, we could look
358 	 * it up in the table of legal command characters and turn it off if
359 	 * it matches one.  VDSUSP wasn't in POSIX 1003.1-1990, so we test for
360 	 * it.
361 	 *
362 	 * XXX
363 	 * We don't check to see if the user had signals enabled originally.
364 	 * If they didn't, it's unclear what we're supposed to do here, but
365 	 * it's also pretty unlikely.
366 	 */
367 	if (tcgetattr(STDIN_FILENO, &clp->vi_enter)) {
368 		msgq(sp, M_SYSERR, "tcgetattr");
369 		goto err;
370 	}
371 	if (clp->orig.c_iflag & IXON)
372 		clp->vi_enter.c_iflag |= IXON;
373 	if (clp->orig.c_iflag & IXOFF)
374 		clp->vi_enter.c_iflag |= IXOFF;
375 
376 	clp->vi_enter.c_lflag |= ISIG;
377 #ifdef VDSUSP
378 	clp->vi_enter.c_cc[VDSUSP] = _POSIX_VDISABLE;
379 #endif
380 	clp->vi_enter.c_cc[VQUIT] = _POSIX_VDISABLE;
381 	clp->vi_enter.c_cc[VSUSP] = _POSIX_VDISABLE;
382 
383 	/*
384 	 * XXX
385 	 * OSF/1 doesn't turn off the <discard>, <literal-next> or <status>
386 	 * characters when curses switches into raw mode.  It should be OK
387 	 * to do it explicitly for everyone.
388 	 */
389 #ifdef VDISCARD
390 	clp->vi_enter.c_cc[VDISCARD] = _POSIX_VDISABLE;
391 #endif
392 #ifdef VLNEXT
393 	clp->vi_enter.c_cc[VLNEXT] = _POSIX_VDISABLE;
394 #endif
395 #ifdef VSTATUS
396 	clp->vi_enter.c_cc[VSTATUS] = _POSIX_VDISABLE;
397 #endif
398 
399 	/* Initialize terminal based information. */
400 	if (cl_term_init(sp))
401 		goto err;
402 
403 fast:	/* Set the terminal modes. */
404 	if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter)) {
405 		msgq(sp, M_SYSERR, "tcsetattr");
406 err:		(void)cl_vi_end(sp->gp);
407 		return (1);
408 	}
409 	return (0);
410 }
411 
412 /*
413  * cl_vi_end --
414  *	Shutdown the vi screen.
415  */
416 static int
cl_vi_end(GS * gp)417 cl_vi_end(GS *gp)
418 {
419 	CL_PRIVATE *clp;
420 
421 	clp = GCLP(gp);
422 
423 	/* Restore the cursor keys to normal mode. */
424 	(void)keypad(stdscr, FALSE);
425 
426 	/*
427 	 * If we were running vi when we quit, scroll the screen up a single
428 	 * line so we don't lose any information.
429 	 *
430 	 * Move to the bottom of the window (some endwin implementations don't
431 	 * do this for you).
432 	 */
433 	if (!F_ISSET(clp, CL_IN_EX) && !F_ISSET(gp, G_SRESTART)) {
434 		(void)move(0, 0);
435 		(void)deleteln();
436 		(void)move(LINES - 1, 0);
437 		(void)refresh();
438 	}
439 
440 	cl_freecap(clp);
441 
442 	/* End curses window. */
443 	(void)endwin();
444 
445 	/* Delete curses screen. */
446 	delscreen(clp->screen);
447 
448 	/*
449 	 * XXX
450 	 * The screen TE sequence just got sent.  See the comment in
451 	 * cl_funcs.c:cl_attr().
452 	 */
453 	clp->ti_te = TE_SENT;
454 
455 	return (0);
456 }
457 
458 /*
459  * cl_ex_init --
460  *	Initialize the ex screen.
461  */
462 static int
cl_ex_init(SCR * sp)463 cl_ex_init(SCR *sp)
464 {
465 	CL_PRIVATE *clp;
466 	int error;
467 	const char *ttype;
468 
469 	clp = CLP(sp);
470 
471 	/* If already initialized, just set the terminal modes. */
472 	if (F_ISSET(clp, CL_SCR_EX_INIT))
473 		goto fast;
474 
475 	/* If not reading from a file, we're done. */
476 	if (!F_ISSET(clp, CL_STDIN_TTY))
477 		return (0);
478 
479 	if (F_ISSET(clp, CL_CHANGE_TERM)) {
480 		if (F_ISSET(clp, CL_SETUPTERM) && del_curterm(cur_term))
481 			return (1);
482 		F_CLR(clp, CL_SETUPTERM | CL_CHANGE_TERM);
483 	}
484 
485 	if (!F_ISSET(clp, CL_SETUPTERM)) {
486 		/* We'll need a terminal type. */
487 		if (opts_empty(sp, O_TERM, 0))
488 			return (1);
489 		ttype = O_STR(sp, O_TERM);
490 		(void)setupterm(ttype, STDOUT_FILENO, &error);
491 		if (error == 0 || error == -1)
492 			return (1);
493 	}
494 
495 	/* Get the ex termcap/terminfo strings. */
496 	(void)cl_getcap(sp, "cup", &clp->cup);
497 	(void)cl_getcap(sp, "smso", &clp->smso);
498 	(void)cl_getcap(sp, "rmso", &clp->rmso);
499 	(void)cl_getcap(sp, "el", &clp->el);
500 	(void)cl_getcap(sp, "cuu1", &clp->cuu1);
501 
502 	/* Enter_standout_mode and exit_standout_mode are paired. */
503 	if (clp->smso == NULL || clp->rmso == NULL) {
504 		if (clp->smso != NULL) {
505 			free(clp->smso);
506 			clp->smso = NULL;
507 		}
508 		if (clp->rmso != NULL) {
509 			free(clp->rmso);
510 			clp->rmso = NULL;
511 		}
512 	}
513 
514 	/*
515 	 * Turn on canonical mode, with normal input and output processing.
516 	 * Start with the original terminal settings as the user probably
517 	 * had them (including any local extensions) set correctly for the
518 	 * current terminal.
519 	 *
520 	 * !!!
521 	 * We can't get everything that we need portably; for example, ONLCR,
522 	 * mapping <newline> to <carriage-return> on output isn't required
523 	 * by POSIX 1003.1b-1993.  If this turns out to be a problem, then
524 	 * we'll either have to play some games on the mapping, or we'll have
525 	 * to make all ex printf's output \r\n instead of \n.
526 	 */
527 	clp->ex_enter = clp->orig;
528 	clp->ex_enter.c_lflag  |= ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG;
529 #ifdef ECHOCTL
530 	clp->ex_enter.c_lflag |= ECHOCTL;
531 #endif
532 #ifdef ECHOKE
533 	clp->ex_enter.c_lflag |= ECHOKE;
534 #endif
535 	clp->ex_enter.c_iflag |= ICRNL;
536 	clp->ex_enter.c_oflag |= OPOST;
537 #ifdef ONLCR
538 	clp->ex_enter.c_oflag |= ONLCR;
539 #endif
540 
541 fast:	if (tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->ex_enter)) {
542 		if (errno == EINTR)
543 			goto fast;
544 		msgq(sp, M_SYSERR, "tcsetattr");
545 		return (1);
546 	}
547 	return (0);
548 }
549 
550 /*
551  * cl_ex_end --
552  *	Shutdown the ex screen.
553  */
554 static int
cl_ex_end(GS * gp)555 cl_ex_end(GS *gp)
556 {
557 	CL_PRIVATE *clp;
558 
559 	clp = GCLP(gp);
560 
561 	cl_freecap(clp);
562 
563 	return (0);
564 }
565 
566 /*
567  * cl_getcap --
568  *	Retrieve termcap/terminfo strings.
569  *
570  * PUBLIC: int cl_getcap __P((SCR *, const char *, char **));
571  */
572 int
cl_getcap(SCR * sp,const char * name,char ** elementp)573 cl_getcap(SCR *sp, const char *name, char **elementp)
574 {
575 	size_t len;
576 	char *t;
577 
578 	if ((t = tigetstr(name)) != NULL &&
579 	    t != (char *)-1 && (len = strlen(t)) != 0) {
580 		MALLOC_RET(sp, *elementp, char *, len + 1);
581 		memmove(*elementp, t, len + 1);
582 	}
583 	return (0);
584 }
585 
586 /*
587  * cl_freecap --
588  *	Free any allocated termcap/terminfo strings.
589  */
590 static void
cl_freecap(CL_PRIVATE * clp)591 cl_freecap(CL_PRIVATE *clp)
592 {
593 	if (clp->el != NULL) {
594 		free(clp->el);
595 		clp->el = NULL;
596 	}
597 	if (clp->cup != NULL) {
598 		free(clp->cup);
599 		clp->cup = NULL;
600 	}
601 	if (clp->cuu1 != NULL) {
602 		free(clp->cuu1);
603 		clp->cuu1 = NULL;
604 	}
605 	if (clp->rmso != NULL) {
606 		free(clp->rmso);
607 		clp->rmso = NULL;
608 	}
609 	if (clp->smso != NULL) {
610 		free(clp->smso);
611 		clp->smso = NULL;
612 	}
613 }
614 
615 /*
616  * cl_putenv --
617  *	Put a value into the environment.
618  */
619 static int
cl_putenv(SCR * sp,const char * name,const char * str,u_long value)620 cl_putenv(SCR *sp, const char *name, const char *str, u_long value)
621 {
622 	char buf[40];
623 
624 	if (str == NULL) {
625 		(void)snprintf(buf, sizeof(buf), "%lu", value);
626 		return (cl_setenv(sp, name, buf));
627 	} else
628 		return (cl_setenv(sp, name, str));
629 }
630