xref: /netbsd-src/lib/libcurses/setterm.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /*	$NetBSD: setterm.c,v 1.64 2017/01/31 09:17:53 roy Exp $	*/
2 
3 /*
4  * Copyright (c) 1981, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)setterm.c	8.8 (Berkeley) 10/25/94";
36 #else
37 __RCSID("$NetBSD: setterm.c,v 1.64 2017/01/31 09:17:53 roy Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include <sys/ioctl.h>		/* TIOCGWINSZ on old systems. */
42 
43 #include <stdlib.h>
44 #include <string.h>
45 #include <termios.h>
46 #include <unistd.h>
47 
48 #include "curses.h"
49 #include "curses_private.h"
50 
51 static int does_esc_m(const char *cap);
52 static int does_ctrl_o(const char *exit_cap, const char *acs_cap);
53 static bool __use_env = true;
54 
55 attr_t	 __mask_op, __mask_me, __mask_ue, __mask_se;
56 
57 void
58 use_env(bool value)
59 {
60 
61 	__use_env = value;
62 }
63 
64 int
65 setterm(char *type)
66 {
67 
68 	return _cursesi_setterm(type, _cursesi_screen);
69 }
70 
71 int
72 _cursesi_setterm(char *type, SCREEN *screen)
73 {
74 	int unknown, r;
75 	struct winsize win;
76 	char *p;
77 
78 	if (type[0] == '\0')
79 		type = "xx";
80 	unknown = 0;
81 	if (screen->term)
82 		del_curterm(screen->term);
83 	(void)ti_setupterm(&screen->term, type, fileno(screen->outfd), &r);
84 	if (screen->term == NULL) {
85 		unknown++;
86 		(void)ti_setupterm(&screen->term, "dumb",
87 		    fileno(screen->outfd), &r);
88 		/* No dumb term? We can't continue */
89 		if (screen->term == NULL)
90 			return ERR;
91 	}
92 #ifdef DEBUG
93 	__CTRACE(__CTRACE_INIT, "setterm: tty = %s\n", type);
94 #endif
95 
96 	/* Try TIOCGWINSZ, and, if it fails, the terminfo entry. */
97 	if (ioctl(fileno(screen->outfd), TIOCGWINSZ, &win) != -1 &&
98 	    win.ws_row != 0 && win.ws_col != 0) {
99 		screen->LINES = win.ws_row;
100 		screen->COLS = win.ws_col;
101 	}  else {
102 		if (unknown) {
103 			screen->LINES = -1;
104 			screen->COLS = -1;
105 		} else {
106 			screen->LINES = t_lines(screen->term);
107 			screen->COLS = t_columns(screen->term);
108 		}
109 	}
110 
111 	if (screen->filtered) {
112 		/* Disable use of clear, cud, cud1, cup, cuu1 and vpa. */
113 		screen->term->strs[TICODE_clear] = NULL;
114 		screen->term->strs[TICODE_cud] = NULL;
115 		screen->term->strs[TICODE_cud1] = NULL;
116 		screen->term->strs[TICODE_cup] = NULL;
117 		screen->term->strs[TICODE_cuu] = NULL;
118 		screen->term->strs[TICODE_cuu1] = NULL;
119 		screen->term->strs[TICODE_vpa] = NULL;
120 		/* Set the value of the home string to the value of
121 		 * the cr string. */
122 		screen->term->strs[TICODE_home] = screen->term->strs[TICODE_cr];
123 		/* Set lines equal to 1. */
124 		screen->LINES = 1;
125 	}
126 #ifdef DEBUG
127 	__CTRACE(__CTRACE_INIT, "setterm: filtered %d", screen->filtered);
128 #endif
129 
130 	/* POSIX 1003.2 requires that the environment override. */
131 	if (__use_env) {
132 		if (!screen->filtered && (p = getenv("LINES")) != NULL)
133 			screen->LINES = (int)strtol(p, NULL, 0);
134 		if ((p = getenv("COLUMNS")) != NULL)
135 			screen->COLS = (int)strtol(p, NULL, 0);
136 	}
137 	if ((p = getenv("ESCDELAY")) != NULL)
138 		screen->ESCDELAY = (int)strtol(p, NULL, 0);
139 	else
140 		screen->ESCDELAY = ESCDELAY_DEFAULT;
141 	if ((p = getenv("TABSIZE")) != NULL)
142 		screen->TABSIZE = (int)strtol(p, NULL, 0);
143 	else if (t_init_tabs(screen->term) >= 0)
144 		screen->TABSIZE = (int)t_init_tabs(screen->term);
145 	else
146 		screen->TABSIZE = TABSIZE_DEFAULT;
147 	/*
148 	 * Want cols > 4, otherwise things will fail.
149 	 */
150 	if (screen->COLS <= 4)
151 		return ERR;
152 
153 	LINES = screen->LINES - __rippedlines(screen);
154 	COLS = screen->COLS;
155 	ESCDELAY = screen->ESCDELAY;
156 	TABSIZE = screen->TABSIZE;
157 
158 #ifdef DEBUG
159 	__CTRACE(__CTRACE_INIT,
160 	    "setterm: LINES = %d, COLS = %d, TABSIZE = %d\n",
161 	    LINES, COLS, TABSIZE);
162 #endif
163 
164 	/*
165 	 * set the pad char, only take the first char of the pc capability
166 	 * as this is all we can use.
167 	 */
168 	screen->padchar = t_pad_char(screen->term) ?
169 	    t_pad_char(screen->term)[0] : 0;
170 
171 	/* If no scrolling commands, no quick change. */
172 	screen->noqch =
173 	    (t_change_scroll_region(screen->term) == NULL ||
174 		t_cursor_home(screen->term) == NULL ||
175 		(t_parm_index(screen->term) == NULL &&
176 		    t_scroll_forward(screen->term) == NULL) ||
177 		(t_parm_rindex(screen->term) == NULL &&
178 		    t_scroll_reverse(screen->term) == NULL)) &&
179 	    ((t_parm_insert_line(screen->term) == NULL &&
180 		t_insert_line(screen->term) == NULL) ||
181 		(t_parm_delete_line(screen->term) == NULL &&
182 		    t_delete_line(screen->term) == NULL));
183 
184 	/*
185 	 * Precalculate conflict info for color/attribute end commands.
186 	 */
187 #ifndef HAVE_WCHAR
188 	screen->mask_op = __ATTRIBUTES & ~__COLOR;
189 #else
190 	screen->mask_op = WA_ATTRIBUTES & ~__COLOR;
191 #endif /* HAVE_WCHAR */
192 	if (t_orig_pair(screen->term) != NULL) {
193 		if (does_esc_m(t_orig_pair(screen->term)))
194 			screen->mask_op &=
195 			    ~(__STANDOUT | __UNDERSCORE | __TERMATTR);
196 		else {
197 			if (t_exit_standout_mode(screen->term) != NULL &&
198 			    !strcmp(t_orig_pair(screen->term),
199 				t_exit_standout_mode(screen->term)))
200 				screen->mask_op &= ~__STANDOUT;
201 			if (t_exit_underline_mode(screen->term) != NULL &&
202 			    !strcmp(t_orig_pair(screen->term),
203 				t_exit_underline_mode(screen->term)))
204 				screen->mask_op &= ~__UNDERSCORE;
205 			if (t_exit_attribute_mode(screen->term) != NULL &&
206 			    !strcmp(t_orig_pair(screen->term),
207 				t_exit_attribute_mode(screen->term)))
208 				screen->mask_op &= ~__TERMATTR;
209 		}
210 	}
211 	/*
212 	 * Assume that "me" turns off all attributes apart from ACS.
213 	 * It might turn off ACS, so check for that.
214 	 */
215 	if (t_exit_attribute_mode(screen->term) != NULL &&
216 	    t_exit_alt_charset_mode(screen->term) != NULL &&
217 	    does_ctrl_o(t_exit_attribute_mode(screen->term),
218 	    t_exit_alt_charset_mode(screen->term)))
219 		screen->mask_me = 0;
220 	else
221 		screen->mask_me = __ALTCHARSET;
222 
223 	/* Check what turning off the attributes also turns off */
224 #ifndef HAVE_WCHAR
225 	screen->mask_ue = __ATTRIBUTES & ~__UNDERSCORE;
226 #else
227 	screen->mask_ue = WA_ATTRIBUTES & ~__UNDERSCORE;
228 #endif /* HAVE_WCHAR */
229 	if (t_exit_underline_mode(screen->term) != NULL) {
230 		if (does_esc_m(t_exit_underline_mode(screen->term)))
231 			screen->mask_ue &=
232 			    ~(__STANDOUT | __TERMATTR | __COLOR);
233 		else {
234 			if (t_exit_standout_mode(screen->term) != NULL &&
235 			    !strcmp(t_exit_underline_mode(screen->term),
236 				t_exit_standout_mode(screen->term)))
237 				screen->mask_ue &= ~__STANDOUT;
238 			if (t_exit_attribute_mode(screen->term) != NULL &&
239 			    !strcmp(t_exit_underline_mode(screen->term),
240 				t_exit_attribute_mode(screen->term)))
241 				screen->mask_ue &= ~__TERMATTR;
242 			if (t_orig_pair(screen->term) != NULL &&
243 			    !strcmp(t_exit_underline_mode(screen->term),
244 				t_orig_pair(screen->term)))
245 				screen->mask_ue &= ~__COLOR;
246 		}
247 	}
248 #ifndef HAVE_WCHAR
249 	screen->mask_se = __ATTRIBUTES & ~__STANDOUT;
250 #else
251 	screen->mask_se = WA_ATTRIBUTES & ~__STANDOUT;
252 #endif /* HAVE_WCHAR */
253 	if (t_exit_standout_mode(screen->term) != NULL) {
254 		if (does_esc_m(t_exit_standout_mode(screen->term)))
255 			screen->mask_se &=
256 			    ~(__UNDERSCORE | __TERMATTR | __COLOR);
257 		else {
258 			if (t_exit_underline_mode(screen->term) != NULL &&
259 			    !strcmp(t_exit_standout_mode(screen->term),
260 				t_exit_underline_mode(screen->term)))
261 				screen->mask_se &= ~__UNDERSCORE;
262 			if (t_exit_attribute_mode(screen->term) != NULL &&
263 			    !strcmp(t_exit_standout_mode(screen->term),
264 				t_exit_attribute_mode(screen->term)))
265 				screen->mask_se &= ~__TERMATTR;
266 			if (t_orig_pair(screen->term) != NULL &&
267 			    !strcmp(t_exit_standout_mode(screen->term),
268 				t_orig_pair(screen->term)))
269 				screen->mask_se &= ~__COLOR;
270 		}
271 	}
272 
273 	return unknown ? ERR : OK;
274 }
275 
276 /*
277  * _cursesi_resetterm --
278  *  Copy the terminal instance data for the given screen to the global
279  *  variables.
280  */
281 void
282 _cursesi_resetterm(SCREEN *screen)
283 {
284 
285 	LINES = screen->LINES - __rippedlines(screen);
286 	COLS = screen->COLS;
287 	ESCDELAY = screen->ESCDELAY;
288 	TABSIZE = screen->TABSIZE;
289 	__GT = screen->GT;
290 
291 	__noqch = screen->noqch;
292 	__mask_op = screen->mask_op;
293 	__mask_me = screen->mask_me;
294 	__mask_ue = screen->mask_ue;
295 	__mask_se = screen->mask_se;
296 
297 	set_curterm(screen->term);
298 }
299 
300 /*
301  * does_esc_m --
302  * A hack for xterm-like terminals where "\E[m" or "\E[0m" will turn off
303  * other attributes, so we check for this in the capability passed to us.
304  * Note that we can't just do simple string comparison, as the capability
305  * may contain multiple, ';' separated sequence parts.
306  */
307 static int
308 does_esc_m(const char *cap)
309 {
310 #define WAITING 0
311 #define PARSING 1
312 #define NUMBER 2
313 #define FOUND 4
314 	const char *capptr;
315 	int seq;
316 
317 #ifdef DEBUG
318 	__CTRACE(__CTRACE_INIT, "does_esc_m: Checking %s\n", cap);
319 #endif
320 	/* Is it just "\E[m" or "\E[0m"? */
321 	if (!strcmp(cap, "\x1b[m") || !strcmp(cap, "\x1b[0m"))
322 		return 1;
323 
324 	/* Does it contain a "\E[...m" sequence? */
325 	if (strlen(cap) < 4)
326 		return 0;
327 	capptr = cap;
328 	seq = WAITING;
329 	while (*capptr != 0) {
330 		switch (seq) {
331 		/* Start of sequence */
332 		case WAITING:
333 			if (!strncmp(capptr, "\x1b[", 2)) {
334 				capptr+=2;
335 				if (*capptr == 'm')
336 					return 1;
337 				else {
338 					seq=PARSING;
339 					continue;
340 				}
341 			}
342 			break;
343 		/* Looking for '0' */
344 		case PARSING:
345 			if (*capptr == '0')
346 				seq=FOUND;
347 			else if (*capptr > '0' && *capptr <= '9')
348 				seq=NUMBER;
349 			else if (*capptr != ';')
350 				seq=WAITING;
351 			break;
352 		/* Some other number */
353 		case NUMBER:
354 			if (*capptr == ';')
355 				seq=PARSING;
356 			else if (!(*capptr >= '0' && *capptr <= '9'))
357 				seq=WAITING;
358 			break;
359 		/* Found a '0' */
360 		case FOUND:
361 			if (*capptr == 'm')
362 				return 1;
363 			else if (!((*capptr >= '0' && *capptr <= '9') ||
364 			    *capptr == ';'))
365 				seq=WAITING;
366 			break;
367 		default:
368 			break;
369 		}
370 		capptr++;
371 	}
372 	return 0;
373 }
374 
375 /*
376  * does_ctrl_o --
377  * A hack for vt100/xterm-like terminals where the "me" capability also
378  * unsets acs.
379  */
380 static int
381 does_ctrl_o(const char *exit_cap, const char *acs_cap)
382 {
383 	const char *eptr = exit_cap, *aptr = acs_cap;
384 	int l;
385 
386 #ifdef DEBUG
387 	__CTRACE(__CTRACE_INIT, "does_ctrl_o: Testing %s for %s\n", eptr, aptr);
388 #endif
389 	l = strlen(acs_cap);
390 	while (*eptr != 0) {
391 		if (!strncmp(eptr, aptr, l))
392 			return 1;
393 		eptr++;
394 	}
395 	return 0;
396 }
397 
398 /*
399  * set_tabsize --
400  *   Sets the tabsize for the current screen.
401  */
402 int
403 set_tabsize(int tabsize)
404 {
405 
406 	if (_cursesi_screen == NULL)
407 		return ERR;
408 	_cursesi_screen->TABSIZE = tabsize;
409 	TABSIZE = tabsize;
410 	return OK;
411 }
412