xref: /freebsd/contrib/nvi/cl/cl_term.c (revision 9336e0699bda8a301cd2bfa37106b6ec5e32012e)
1 /*-
2  * Copyright (c) 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1993, 1994, 1995, 1996
5  *	Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9 
10 #include "config.h"
11 
12 #ifndef lint
13 static const char sccsid[] = "@(#)cl_term.c	10.22 (Berkeley) 9/15/96";
14 #endif /* not lint */
15 
16 #include <sys/types.h>
17 #include <sys/ioctl.h>
18 #include <sys/queue.h>
19 #include <sys/stat.h>
20 
21 #include <bitstring.h>
22 #include <curses.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <termios.h>
30 #include <unistd.h>
31 
32 #include "../common/common.h"
33 #include "cl.h"
34 
35 static int cl_pfmap __P((SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t));
36 
37 /*
38  * XXX
39  * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE.
40  */
41 typedef struct _tklist {
42 	char	*ts;			/* Key's termcap string. */
43 	char	*output;		/* Corresponding vi command. */
44 	char	*name;			/* Name. */
45 	u_char	 value;			/* Special value (for lookup). */
46 } TKLIST;
47 static TKLIST const c_tklist[] = {	/* Command mappings. */
48 	{"kil1",	"O",	"insert line"},
49 	{"kdch1",	"x",	"delete character"},
50 	{"kcud1",	"j",	"cursor down"},
51 	{"kel",		"D",	"delete to eol"},
52 	{"kind",     "\004",	"scroll down"},			/* ^D */
53 	{"kll",		"$",	"go to eol"},
54 	{"kend",	"$",	"go to eol"},
55 	{"khome",	"^",	"go to sol"},
56 	{"kich1",	"i",	"insert at cursor"},
57 	{"kdl1",       "dd",	"delete line"},
58 	{"kcub1",	"h",	"cursor left"},
59 	{"knp",	     "\006",	"page down"},			/* ^F */
60 	{"kpp",	     "\002",	"page up"},			/* ^B */
61 	{"kri",	     "\025",	"scroll up"},			/* ^U */
62 	{"ked",	       "dG",	"delete to end of screen"},
63 	{"kcuf1",	"l",	"cursor right"},
64 	{"kcuu1",	"k",	"cursor up"},
65 	{NULL},
66 };
67 static TKLIST const m1_tklist[] = {	/* Input mappings (lookup). */
68 	{NULL},
69 };
70 static TKLIST const m2_tklist[] = {	/* Input mappings (set or delete). */
71 	{"kcud1",  "\033ja",	"cursor down"},			/* ^[ja */
72 	{"kcub1",  "\033ha",	"cursor left"},			/* ^[ha */
73 	{"kcuu1",  "\033ka",	"cursor up"},			/* ^[ka */
74 	{"kcuf1",  "\033la",	"cursor right"},		/* ^[la */
75 	{NULL},
76 };
77 
78 /*
79  * cl_term_init --
80  *	Initialize the special keys defined by the termcap/terminfo entry.
81  *
82  * PUBLIC: int cl_term_init __P((SCR *));
83  */
84 int
85 cl_term_init(sp)
86 	SCR *sp;
87 {
88 	KEYLIST *kp;
89 	SEQ *qp;
90 	TKLIST const *tkp;
91 	char *t;
92 
93 	/* Command mappings. */
94 	for (tkp = c_tklist; tkp->name != NULL; ++tkp) {
95 		if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
96 			continue;
97 		if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t),
98 		    tkp->output, strlen(tkp->output), SEQ_COMMAND,
99 		    SEQ_NOOVERWRITE | SEQ_SCREEN))
100 			return (1);
101 	}
102 
103 	/* Input mappings needing to be looked up. */
104 	for (tkp = m1_tklist; tkp->name != NULL; ++tkp) {
105 		if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
106 			continue;
107 		for (kp = keylist;; ++kp)
108 			if (kp->value == tkp->value)
109 				break;
110 		if (kp == NULL)
111 			continue;
112 		if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t),
113 		    &kp->ch, 1, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
114 			return (1);
115 	}
116 
117 	/* Input mappings that are already set or are text deletions. */
118 	for (tkp = m2_tklist; tkp->name != NULL; ++tkp) {
119 		if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1)
120 			continue;
121 		/*
122 		 * !!!
123 		 * Some terminals' <cursor_left> keys send single <backspace>
124 		 * characters.  This is okay in command mapping, but not okay
125 		 * in input mapping.  That combination is the only one we'll
126 		 * ever see, hopefully, so kluge it here for now.
127 		 */
128 		if (!strcmp(t, "\b"))
129 			continue;
130 		if (tkp->output == NULL) {
131 			if (seq_set(sp, tkp->name, strlen(tkp->name),
132 			    t, strlen(t), NULL, 0,
133 			    SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
134 				return (1);
135 		} else
136 			if (seq_set(sp, tkp->name, strlen(tkp->name),
137 			    t, strlen(t), tkp->output, strlen(tkp->output),
138 			    SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN))
139 				return (1);
140 	}
141 
142 	/*
143 	 * Rework any function key mappings that were set before the
144 	 * screen was initialized.
145 	 */
146 	for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = qp->q.le_next)
147 		if (F_ISSET(qp, SEQ_FUNCMAP))
148 			(void)cl_pfmap(sp, qp->stype,
149 			    qp->input, qp->ilen, qp->output, qp->olen);
150 	return (0);
151 }
152 
153 /*
154  * cl_term_end --
155  *	End the special keys defined by the termcap/terminfo entry.
156  *
157  * PUBLIC: int cl_term_end __P((GS *));
158  */
159 int
160 cl_term_end(gp)
161 	GS *gp;
162 {
163 	SEQ *qp, *nqp;
164 
165 	/* Delete screen specific mappings. */
166 	for (qp = gp->seqq.lh_first; qp != NULL; qp = nqp) {
167 		nqp = qp->q.le_next;
168 		if (F_ISSET(qp, SEQ_SCREEN))
169 			(void)seq_mdel(qp);
170 	}
171 	return (0);
172 }
173 
174 /*
175  * cl_fmap --
176  *	Map a function key.
177  *
178  * PUBLIC: int cl_fmap __P((SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t));
179  */
180 int
181 cl_fmap(sp, stype, from, flen, to, tlen)
182 	SCR *sp;
183 	seq_t stype;
184 	CHAR_T *from, *to;
185 	size_t flen, tlen;
186 {
187 	/* Ignore until the screen is running, do the real work then. */
188 	if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI))
189 		return (0);
190 	if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX))
191 		return (0);
192 
193 	return (cl_pfmap(sp, stype, from, flen, to, tlen));
194 }
195 
196 /*
197  * cl_pfmap --
198  *	Map a function key (private version).
199  */
200 static int
201 cl_pfmap(sp, stype, from, flen, to, tlen)
202 	SCR *sp;
203 	seq_t stype;
204 	CHAR_T *from, *to;
205 	size_t flen, tlen;
206 {
207 	size_t nlen;
208 	char *p, keyname[64];
209 
210 	(void)snprintf(keyname, sizeof(keyname), "kf%d", atoi(from + 1));
211 	if ((p = tigetstr(keyname)) == NULL ||
212 	    p == (char *)-1 || strlen(p) == 0)
213 		p = NULL;
214 	if (p == NULL) {
215 		msgq_str(sp, M_ERR, from, "233|This terminal has no %s key");
216 		return (1);
217 	}
218 
219 	nlen = snprintf(keyname,
220 	    sizeof(keyname), "function key %d", atoi(from + 1));
221 	return (seq_set(sp, keyname, nlen,
222 	    p, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN));
223 }
224 
225 /*
226  * cl_optchange --
227  *	Curses screen specific "option changed" routine.
228  *
229  * PUBLIC: int cl_optchange __P((SCR *, int, char *, u_long *));
230  */
231 int
232 cl_optchange(sp, opt, str, valp)
233 	SCR *sp;
234 	int opt;
235 	char *str;
236 	u_long *valp;
237 {
238 	CL_PRIVATE *clp;
239 
240 	clp = CLP(sp);
241 
242 	switch (opt) {
243 	case O_COLUMNS:
244 	case O_LINES:
245 	case O_TERM:
246 		/*
247 		 * Changing the columns, lines or terminal require that
248 		 * we restart the screen.
249 		 */
250 		F_SET(sp->gp, G_SRESTART);
251 		F_CLR(sp, SC_SCR_EX | SC_SCR_VI);
252 		break;
253 	case O_MESG:
254 		(void)cl_omesg(sp, clp, !*valp);
255 		break;
256 	case O_WINDOWNAME:
257 		if (*valp) {
258 			F_CLR(clp, CL_RENAME_OK);
259 
260 			(void)cl_rename(sp, NULL, 0);
261 		} else {
262 			F_SET(clp, CL_RENAME_OK);
263 
264 			/*
265 			 * If the screen is live, i.e. we're not reading the
266 			 * .exrc file, update the window.
267 			 */
268 			if (sp->frp != NULL && sp->frp->name != NULL)
269 				(void)cl_rename(sp, sp->frp->name, 1);
270 		}
271 		break;
272 	}
273 	return (0);
274 }
275 
276 /*
277  * cl_omesg --
278  *	Turn the tty write permission on or off.
279  *
280  * PUBLIC: int cl_omesg __P((SCR *, CL_PRIVATE *, int));
281  */
282 int
283 cl_omesg(sp, clp, on)
284 	SCR *sp;
285 	CL_PRIVATE *clp;
286 	int on;
287 {
288 	struct stat sb;
289 	char *tty;
290 
291 	/* Find the tty, get the current permissions. */
292 	if ((tty = ttyname(STDERR_FILENO)) == NULL) {
293 		if (sp != NULL)
294 			msgq(sp, M_SYSERR, "stderr");
295 		return (1);
296 	}
297 	if (stat(tty, &sb) < 0) {
298 		if (sp != NULL)
299 			msgq(sp, M_SYSERR, "%s", tty);
300 		return (1);
301 	}
302 
303 	/* Save the original status if it's unknown. */
304 	if (clp->tgw == TGW_UNKNOWN)
305 		clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET;
306 
307 	/* Toggle the permissions. */
308 	if (on) {
309 		if (chmod(tty, sb.st_mode | S_IWGRP) < 0) {
310 			if (sp != NULL)
311 				msgq(sp, M_SYSERR,
312 				    "046|messages not turned on: %s", tty);
313 			return (1);
314 		}
315 	} else
316 		if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) {
317 			if (sp != NULL)
318 				msgq(sp, M_SYSERR,
319 				    "045|messages not turned off: %s", tty);
320 			return (1);
321 		}
322 	return (0);
323 }
324 
325 /*
326  * cl_ssize --
327  *	Return the terminal size.
328  *
329  * PUBLIC: int cl_ssize __P((SCR *, int, size_t *, size_t *, int *));
330  */
331 int
332 cl_ssize(sp, sigwinch, rowp, colp, changedp)
333 	SCR *sp;
334 	int sigwinch;
335 	size_t *rowp, *colp;
336 	int *changedp;
337 {
338 #ifdef TIOCGWINSZ
339 	struct winsize win;
340 #endif
341 	size_t col, row;
342 	int rval;
343 	char *p;
344 
345 	/* Assume it's changed. */
346 	if (changedp != NULL)
347 		*changedp = 1;
348 
349 	/*
350 	 * !!!
351 	 * sp may be NULL.
352 	 *
353 	 * Get the screen rows and columns.  If the values are wrong, it's
354 	 * not a big deal -- as soon as the user sets them explicitly the
355 	 * environment will be set and the screen package will use the new
356 	 * values.
357 	 *
358 	 * Try TIOCGWINSZ.
359 	 */
360 	row = col = 0;
361 #ifdef TIOCGWINSZ
362 	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) {
363 		row = win.ws_row;
364 		col = win.ws_col;
365 	}
366 #endif
367 	/* If here because of suspend or a signal, only trust TIOCGWINSZ. */
368 	if (sigwinch) {
369 		/*
370 		 * Somebody didn't get TIOCGWINSZ right, or has suspend
371 		 * without window resizing support.  The user just lost,
372 		 * but there's nothing we can do.
373 		 */
374 		if (row == 0 || col == 0) {
375 			if (changedp != NULL)
376 				*changedp = 0;
377 			return (0);
378 		}
379 
380 		/*
381 		 * SunOS systems deliver SIGWINCH when windows are uncovered
382 		 * as well as when they change size.  In addition, we call
383 		 * here when continuing after being suspended since the window
384 		 * may have changed size.  Since we don't want to background
385 		 * all of the screens just because the window was uncovered,
386 		 * ignore the signal if there's no change.
387 		 */
388 		if (sp != NULL &&
389 		    row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) {
390 			if (changedp != NULL)
391 				*changedp = 0;
392 			return (0);
393 		}
394 
395 		if (rowp != NULL)
396 			*rowp = row;
397 		if (colp != NULL)
398 			*colp = col;
399 		return (0);
400 	}
401 
402 	/*
403 	 * !!!
404 	 * If TIOCGWINSZ failed, or had entries of 0, try termcap.  This
405 	 * routine is called before any termcap or terminal information
406 	 * has been set up.  If there's no TERM environmental variable set,
407 	 * let it go, at least ex can run.
408 	 */
409 	if (row == 0 || col == 0) {
410 		if ((p = getenv("TERM")) == NULL)
411 			goto noterm;
412 		if (row == 0)
413 			if ((rval = tigetnum("lines")) < 0)
414 				msgq(sp, M_SYSERR, "tigetnum: lines");
415 			else
416 				row = rval;
417 		if (col == 0)
418 			if ((rval = tigetnum("cols")) < 0)
419 				msgq(sp, M_SYSERR, "tigetnum: cols");
420 			else
421 				col = rval;
422 	}
423 
424 	/* If nothing else, well, it's probably a VT100. */
425 noterm:	if (row == 0)
426 		row = 24;
427 	if (col == 0)
428 		col = 80;
429 
430 	/*
431 	 * !!!
432 	 * POSIX 1003.2 requires the environment to override everything.
433 	 * Often, people can get nvi to stop messing up their screen by
434 	 * deleting the LINES and COLUMNS environment variables from their
435 	 * dot-files.
436 	 */
437 	if ((p = getenv("LINES")) != NULL)
438 		row = strtol(p, NULL, 10);
439 	if ((p = getenv("COLUMNS")) != NULL)
440 		col = strtol(p, NULL, 10);
441 
442 	if (rowp != NULL)
443 		*rowp = row;
444 	if (colp != NULL)
445 		*colp = col;
446 	return (0);
447 }
448 
449 /*
450  * cl_putchar --
451  *	Function version of putchar, for tputs.
452  *
453  * PUBLIC: int cl_putchar __P((int));
454  */
455 int
456 cl_putchar(ch)
457 	int ch;
458 {
459 	return (putchar(ch));
460 }
461