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