1/*	$OpenBSD: tty.c,v 1.40 2023/03/08 04:43:11 guenther Exp $	*/
2
3/* This file is in the public domain. */
4
5/*
6 * Terminfo display driver
7 *
8 * Terminfo is a terminal information database and routines to describe
9 * terminals on most modern UNIX systems.  Many other systems have adopted
10 * this as a reasonable way to allow for widely varying and ever changing
11 * varieties of terminal types.	 This should be used where practical.
12 */
13/*
14 * Known problems: If you have a terminal with no clear to end of screen and
15 * memory of lines below the ones visible on the screen, display will be
16 * wrong in some cases.  I doubt that any such terminal was ever made, but I
17 * thought everyone with delete line would have clear to end of screen too...
18 *
19 * Code for terminals without clear to end of screen and/or clear to end of line
20 * has not been extensively tested.
21 *
22 * Cost calculations are very rough.  Costs of insert/delete line may be far
23 * from the truth.  This is accentuated by display.c not knowing about
24 * multi-line insert/delete.
25 *
26 * Using scrolling region vs insert/delete line should probably be based on cost
27 * rather than the assumption that scrolling region operations look better.
28 */
29
30#include <sys/ioctl.h>
31#include <sys/queue.h>
32#include <sys/types.h>
33#include <sys/time.h>
34#include <signal.h>
35#include <stdio.h>
36#include <term.h>
37#include <unistd.h>
38
39#include "def.h"
40
41static int	 charcost(const char *);
42
43static int	 cci;
44static int	 insdel;	/* Do we have both insert & delete line? */
45static char	*scroll_fwd;	/* How to scroll forward. */
46
47static void	 winchhandler(int);
48
49volatile sig_atomic_t	 winch_flag;
50int			 tceeol;
51int			 tcinsl;
52int			 tcdell;
53
54static void
55winchhandler(int sig)
56{
57	winch_flag = 1;
58}
59
60/*
61 * Initialize the terminal when the editor
62 * gets started up.
63 */
64void
65ttinit(void)
66{
67	char *tty;
68	int errret;
69
70	if (batch == 1)
71		tty = "pty";
72	else
73		tty = NULL;
74
75	if (setupterm(tty, STDOUT_FILENO, &errret))
76		panic("Terminal setup failed");
77
78	signal(SIGWINCH, winchhandler);
79	signal(SIGCONT, winchhandler);
80	siginterrupt(SIGWINCH, 1);
81
82	scroll_fwd = scroll_forward;
83	if (scroll_fwd == NULL || *scroll_fwd == '\0') {
84		/* this is what GNU Emacs does */
85		scroll_fwd = parm_down_cursor;
86		if (scroll_fwd == NULL || *scroll_fwd == '\0')
87			scroll_fwd = curbp->b_nlchr;
88	}
89
90	if (cursor_address == NULL || cursor_up == NULL)
91		panic("This terminal is too stupid to run mg");
92
93	/* set nrow & ncol */
94	ttresize();
95
96	if (!clr_eol)
97		tceeol = ncol;
98	else
99		tceeol = charcost(clr_eol);
100
101	/* Estimate cost of inserting a line */
102	if (change_scroll_region && scroll_reverse)
103		tcinsl = charcost(change_scroll_region) * 2 +
104		    charcost(scroll_reverse);
105	else if (parm_insert_line)
106		tcinsl = charcost(parm_insert_line);
107	else if (insert_line)
108		tcinsl = charcost(insert_line);
109	else
110		/* make this cost high enough */
111		tcinsl = nrow * ncol;
112
113	/* Estimate cost of deleting a line */
114	if (change_scroll_region)
115		tcdell = charcost(change_scroll_region) * 2 +
116		    charcost(scroll_fwd);
117	else if (parm_delete_line)
118		tcdell = charcost(parm_delete_line);
119	else if (delete_line)
120		tcdell = charcost(delete_line);
121	else
122		/* make this cost high enough */
123		tcdell = nrow * ncol;
124
125	/* Flag to indicate that we can both insert and delete lines */
126	insdel = (insert_line || parm_insert_line) &&
127	    (delete_line || parm_delete_line);
128
129	if (enter_ca_mode)
130		/* enter application mode */
131		putpad(enter_ca_mode, 1);
132
133	ttresize();
134}
135
136/*
137 * Re-initialize the terminal when the editor is resumed.
138 * The keypad_xmit doesn't really belong here but...
139 */
140void
141ttreinit(void)
142{
143	/* check if file was modified while we were gone */
144	if (fchecktime(curbp) != TRUE) {
145		curbp->b_flag |= BFDIRTY;
146	}
147
148	if (enter_ca_mode)
149		/* enter application mode */
150		putpad(enter_ca_mode, 1);
151
152	if (keypad_xmit)
153		/* turn on keypad */
154		putpad(keypad_xmit, 1);
155
156	ttresize();
157}
158
159/*
160 * Clean up the terminal, in anticipation of a return to the command
161 * interpreter. This is a no-op on the ANSI display. On the SCALD display,
162 * it sets the window back to half screen scrolling. Perhaps it should
163 * query the display for the increment, and put it back to what it was.
164 */
165void
166tttidy(void)
167{
168	ttykeymaptidy();
169
170	/* set the term back to normal mode */
171	if (exit_ca_mode)
172		putpad(exit_ca_mode, 1);
173}
174
175/*
176 * Move the cursor to the specified origin 0 row and column position. Try to
177 * optimize out extra moves; redisplay may have left the cursor in the right
178 * location last time!
179 */
180void
181ttmove(int row, int col)
182{
183	if (ttrow != row || ttcol != col) {
184		putpad(tgoto(cursor_address, col, row), 1);
185		ttrow = row;
186		ttcol = col;
187	}
188}
189
190/*
191 * Erase to end of line.
192 */
193void
194tteeol(void)
195{
196	int	i;
197
198	if (clr_eol)
199		putpad(clr_eol, 1);
200	else {
201		i = ncol - ttcol;
202		while (i--)
203			ttputc(' ');
204		ttrow = ttcol = HUGE;
205	}
206}
207
208/*
209 * Erase to end of page.
210 */
211void
212tteeop(void)
213{
214	int	line;
215
216	if (clr_eos)
217		putpad(clr_eos, nrow - ttrow);
218	else {
219		putpad(clr_eol, 1);
220		if (insdel)
221			ttdell(ttrow + 1, lines, lines - ttrow - 1);
222		else {
223			/* do it by hand */
224			for (line = ttrow + 1; line <= lines; ++line) {
225				ttmove(line, 0);
226				tteeol();
227			}
228		}
229		ttrow = ttcol = HUGE;
230	}
231}
232
233/*
234 * Make a noise.
235 */
236void
237ttbeep(void)
238{
239	putpad(bell, 1);
240	ttflush();
241}
242
243/*
244 * Insert nchunk blank line(s) onto the screen, scrolling the last line on
245 * the screen off the bottom.  Use the scrolling region if possible for a
246 * smoother display.  If there is no scrolling region, use a set of insert
247 * and delete line sequences.
248 */
249void
250ttinsl(int row, int bot, int nchunk)
251{
252	int	i, nl;
253
254	/* One line special cases */
255	if (row == bot) {
256		ttmove(row, 0);
257		tteeol();
258		return;
259	}
260	/* Use scroll region and back index */
261	if (change_scroll_region && scroll_reverse) {
262		nl = bot - row;
263		ttwindow(row, bot);
264		ttmove(row, 0);
265		while (nchunk--)
266			putpad(scroll_reverse, nl);
267		ttnowindow();
268		return;
269	/* else use insert/delete line */
270	} else if (insdel) {
271		ttmove(1 + bot - nchunk, 0);
272		nl = nrow - ttrow;
273		if (parm_delete_line)
274			putpad(tgoto(parm_delete_line, 0, nchunk), nl);
275		else
276			/* For all lines in the chunk */
277			for (i = 0; i < nchunk; i++)
278				putpad(delete_line, nl);
279		ttmove(row, 0);
280
281		/* ttmove() changes ttrow */
282		nl = nrow - ttrow;
283
284		if (parm_insert_line)
285			putpad(tgoto(parm_insert_line, 0, nchunk), nl);
286		else
287			/* For all lines in the chunk */
288			for (i = 0; i < nchunk; i++)
289				putpad(insert_line, nl);
290		ttrow = HUGE;
291		ttcol = HUGE;
292	} else
293		panic("ttinsl: Can't insert/delete line");
294}
295
296/*
297 * Delete nchunk line(s) from "row", replacing the bottom line on the
298 * screen with a blank line.  Unless we're using the scrolling region,
299 * this is done with crafty sequences of insert and delete lines.  The
300 * presence of the echo area makes a boundary condition go away.
301 */
302void
303ttdell(int row, int bot, int nchunk)
304{
305	int	i, nl;
306
307	/* One line special cases */
308	if (row == bot) {
309		ttmove(row, 0);
310		tteeol();
311		return;
312	}
313	/* scrolling region */
314	if (change_scroll_region) {
315		nl = bot - row;
316		ttwindow(row, bot);
317		ttmove(bot, 0);
318		while (nchunk--)
319			putpad(scroll_fwd, nl);
320		ttnowindow();
321	/* else use insert/delete line */
322	} else if (insdel) {
323		ttmove(row, 0);
324		nl = nrow - ttrow;
325		if (parm_delete_line)
326			putpad(tgoto(parm_delete_line, 0, nchunk), nl);
327		else
328			/* For all lines in the chunk */
329			for (i = 0; i < nchunk; i++)
330				putpad(delete_line, nl);
331		ttmove(1 + bot - nchunk, 0);
332
333		/* ttmove() changes ttrow */
334		nl = nrow - ttrow;
335
336		if (parm_insert_line)
337			putpad(tgoto(parm_insert_line, 0, nchunk), nl);
338		else
339			/* For all lines in the chunk */
340			for (i = 0; i < nchunk; i++)
341				putpad(insert_line, nl);
342		ttrow = HUGE;
343		ttcol = HUGE;
344	} else
345		panic("ttdell: Can't insert/delete line");
346}
347
348/*
349 * This routine sets the scrolling window on the display to go from line
350 * "top" to line "bot" (origin 0, inclusive).  The caller checks for the
351 * pathological 1-line scroll window which doesn't work right and avoids
352 * it.  The "ttrow" and "ttcol" variables are set to a crazy value to
353 * ensure that the next call to "ttmove" does not turn into a no-op (the
354 * window adjustment moves the cursor).
355 */
356void
357ttwindow(int top, int bot)
358{
359	if (change_scroll_region && (tttop != top || ttbot != bot)) {
360		putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow);
361		ttrow = HUGE;	/* Unknown.		 */
362		ttcol = HUGE;
363		tttop = top;	/* Remember region.	 */
364		ttbot = bot;
365	}
366}
367
368/*
369 * Switch to full screen scroll. This is used by "spawn.c" just before it
370 * suspends the editor and by "display.c" when it is getting ready to
371 * exit.  This function does a full screen scroll by telling the terminal
372 * to set a scrolling region that is lines or nrow rows high, whichever is
373 * larger.  This behavior seems to work right on systems where you can set
374 * your terminal size.
375 */
376void
377ttnowindow(void)
378{
379	if (change_scroll_region) {
380		putpad(tgoto(change_scroll_region,
381		    (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow);
382		ttrow = HUGE;	/* Unknown.		 */
383		ttcol = HUGE;
384		tttop = HUGE;	/* No scroll region.	 */
385		ttbot = HUGE;
386	}
387}
388
389/*
390 * Set the current writing color to the specified color. Watch for color
391 * changes that are not going to do anything (the color is already right)
392 * and don't send anything to the display.  The rainbow version does this
393 * in putline.s on a line by line basis, so don't bother sending out the
394 * color shift.
395 */
396void
397ttcolor(int color)
398{
399	if (color != tthue) {
400		if (color == CTEXT)
401			/* normal video */
402			putpad(exit_standout_mode, 1);
403		else if (color == CMODE)
404			/* reverse video */
405			putpad(enter_standout_mode, 1);
406		/* save the color */
407		tthue = color;
408	}
409}
410
411/*
412 * This routine is called by the "refresh the screen" command to try
413 * to resize the display. Look in "window.c" to see how
414 * the caller deals with a change.
415 *
416 * We use `newrow' and `newcol' so vtresize() know the difference between the
417 * new and old settings.
418 */
419void
420ttresize(void)
421{
422	int newrow = 0, newcol = 0;
423
424	struct	winsize winsize;
425
426	if (ioctl(0, TIOCGWINSZ, &winsize) == 0) {
427		newrow = winsize.ws_row;
428		newcol = winsize.ws_col;
429	}
430	if ((newrow <= 0 || newcol <= 0) &&
431	    ((newrow = lines) <= 0 || (newcol = columns) <= 0)) {
432		newrow = 24;
433		newcol = 80;
434	}
435	if (vtresize(1, newrow, newcol) != TRUE)
436		panic("vtresize failed");
437}
438
439/*
440 * fake char output for charcost()
441 */
442static int
443fakec(int c)
444{
445	cci++;
446	return (0);
447}
448
449/* calculate the cost of doing string s */
450static int
451charcost(const char *s)
452{
453	cci = 0;
454
455	tputs(s, nrow, fakec);
456	return (cci);
457}
458