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
41 static int charcost(const char *);
42
43 static int cci;
44 static int insdel; /* Do we have both insert & delete line? */
45 static char *scroll_fwd; /* How to scroll forward. */
46
47 static void winchhandler(int);
48
49 volatile sig_atomic_t winch_flag;
50 int tceeol;
51 int tcinsl;
52 int tcdell;
53
54 static void
winchhandler(int sig)55 winchhandler(int sig)
56 {
57 winch_flag = 1;
58 }
59
60 /*
61 * Initialize the terminal when the editor
62 * gets started up.
63 */
64 void
ttinit(void)65 ttinit(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 */
140 void
ttreinit(void)141 ttreinit(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 */
165 void
tttidy(void)166 tttidy(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 */
180 void
ttmove(int row,int col)181 ttmove(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 */
193 void
tteeol(void)194 tteeol(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 */
211 void
tteeop(void)212 tteeop(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 */
236 void
ttbeep(void)237 ttbeep(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 */
249 void
ttinsl(int row,int bot,int nchunk)250 ttinsl(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 */
302 void
ttdell(int row,int bot,int nchunk)303 ttdell(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 */
356 void
ttwindow(int top,int bot)357 ttwindow(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 */
376 void
ttnowindow(void)377 ttnowindow(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 */
396 void
ttcolor(int color)397 ttcolor(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 */
419 void
ttresize(void)420 ttresize(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 */
442 static int
fakec(int c)443 fakec(int c)
444 {
445 cci++;
446 return (0);
447 }
448
449 /* calculate the cost of doing string s */
450 static int
charcost(const char * s)451 charcost(const char *s)
452 {
453 cci = 0;
454
455 tputs(s, nrow, fakec);
456 return (cci);
457 }
458