1/*	$NetBSD: setterm.c,v 1.72 2024/05/17 23:32:50 uwe 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
35static char sccsid[] = "@(#)setterm.c	8.8 (Berkeley) 10/25/94";
36#else
37__RCSID("$NetBSD: setterm.c,v 1.72 2024/05/17 23:32:50 uwe Exp $");
38#endif
39#endif /* not lint */
40
41#include <stdlib.h>
42#include <string.h>
43#include <termios.h>
44#include <unistd.h>
45
46#include "curses.h"
47#include "curses_private.h"
48
49static int does_esc_m(const char *cap);
50static int does_ctrl_o(const char *exit_cap, const char *acs_cap);
51
52attr_t	 __mask_op, __mask_me, __mask_ue, __mask_se;
53
54int
55setterm(const char *type)
56{
57
58	return _cursesi_setterm(type, _cursesi_screen);
59}
60
61int
62_cursesi_setterm(const char *type, SCREEN *screen)
63{
64	int unknown, r;
65	char *p;
66
67	if (type[0] == '\0')
68		type = "xx";
69	unknown = 0;
70	if (screen->term)
71		del_curterm(screen->term);
72	(void)ti_setupterm(&screen->term, type, fileno(screen->outfd), &r);
73	if (screen->term == NULL) {
74		unknown++;
75		(void)ti_setupterm(&screen->term, "dumb",
76		    fileno(screen->outfd), &r);
77		/* No dumb term? We can't continue */
78		if (screen->term == NULL)
79			return ERR;
80	}
81	__CTRACE(__CTRACE_INIT, "setterm: tty = %s\n", type);
82
83	/* lines and cols will have been setup correctly by ti_setupterm(3). */
84	screen->LINES = t_lines(screen->term);
85	screen->COLS = t_columns(screen->term);
86
87	if (screen->filtered) {
88		/* Disable use of clear, cud, cud1, cup, cuu1 and vpa. */
89		screen->term->strs[TICODE_clear] = NULL;
90		screen->term->strs[TICODE_cud] = NULL;
91		screen->term->strs[TICODE_cud1] = NULL;
92		screen->term->strs[TICODE_cup] = NULL;
93		screen->term->strs[TICODE_cuu] = NULL;
94		screen->term->strs[TICODE_cuu1] = NULL;
95		screen->term->strs[TICODE_vpa] = NULL;
96		/* Set the value of the home string to the value of
97		 * the cr string. */
98		screen->term->strs[TICODE_home] = screen->term->strs[TICODE_cr];
99		/* Set lines equal to 1. */
100		screen->LINES = 1;
101		t_lines(screen->term) = 1;
102	}
103	__CTRACE(__CTRACE_INIT, "setterm: filtered %d\n", screen->filtered);
104
105	if ((p = getenv("ESCDELAY")) != NULL)
106		screen->ESCDELAY = (int)strtol(p, NULL, 0);
107	else
108		screen->ESCDELAY = ESCDELAY_DEFAULT;
109	if ((p = getenv("TABSIZE")) != NULL)
110		screen->TABSIZE = (int)strtol(p, NULL, 0);
111	else if (t_init_tabs(screen->term) >= 0)
112		screen->TABSIZE = (int)t_init_tabs(screen->term);
113	else
114		screen->TABSIZE = TABSIZE_DEFAULT;
115	/*
116	 * Want cols > 4, otherwise things will fail.
117	 */
118	if (screen->COLS <= 4)
119		return ERR;
120
121	LINES = screen->LINES;
122	COLS = screen->COLS;
123	ESCDELAY = screen->ESCDELAY;
124	TABSIZE = screen->TABSIZE;
125
126	__CTRACE(__CTRACE_INIT,
127	    "setterm: LINES = %d, COLS = %d, TABSIZE = %d\n",
128	    LINES, COLS, TABSIZE);
129
130	/*
131	 * set the pad char, only take the first char of the pc capability
132	 * as this is all we can use.
133	 */
134	screen->padchar = t_pad_char(screen->term) ?
135	    t_pad_char(screen->term)[0] : 0;
136
137	/* If no scrolling commands, no quick change. */
138	screen->noqch =
139	    (t_change_scroll_region(screen->term) == NULL ||
140		t_cursor_home(screen->term) == NULL ||
141		(t_parm_index(screen->term) == NULL &&
142		    t_scroll_forward(screen->term) == NULL) ||
143		(t_parm_rindex(screen->term) == NULL &&
144		    t_scroll_reverse(screen->term) == NULL)) &&
145	    ((t_parm_insert_line(screen->term) == NULL &&
146		t_insert_line(screen->term) == NULL) ||
147		(t_parm_delete_line(screen->term) == NULL &&
148		    t_delete_line(screen->term) == NULL));
149
150	/*
151	 * Precalculate conflict info for color/attribute end commands.
152	 */
153#ifndef HAVE_WCHAR
154	screen->mask_op = __ATTRIBUTES & ~__COLOR;
155#else
156	screen->mask_op = WA_ATTRIBUTES & ~__COLOR;
157#endif /* HAVE_WCHAR */
158
159	const char *t_op = t_orig_pair(screen->term);
160	const char *t_esm = t_exit_standout_mode(screen->term);
161	const char *t_eum = t_exit_underline_mode(screen->term);
162	const char *t_eam = t_exit_attribute_mode(screen->term);
163
164	if (t_op != NULL) {
165		if (does_esc_m(t_op))
166			screen->mask_op &=
167			    ~(__STANDOUT | __UNDERSCORE | __TERMATTR);
168		else {
169			if (t_esm != NULL && !strcmp(t_op, t_esm))
170				screen->mask_op &= ~__STANDOUT;
171
172			if (t_eum != NULL && !strcmp(t_op, t_eum))
173				screen->mask_op &= ~__UNDERSCORE;
174
175			if (t_eam != NULL && !strcmp(t_op, t_eam))
176				screen->mask_op &= ~__TERMATTR;
177		}
178	}
179	/*
180	 * Assume that "me" turns off all attributes apart from ACS.
181	 * It might turn off ACS, so check for that.
182	 */
183	if (t_exit_attribute_mode(screen->term) != NULL &&
184	    t_exit_alt_charset_mode(screen->term) != NULL &&
185	    does_ctrl_o(t_exit_attribute_mode(screen->term),
186	    t_exit_alt_charset_mode(screen->term)))
187		screen->mask_me = 0;
188	else
189		screen->mask_me = __ALTCHARSET;
190
191	/* Check what turning off the attributes also turns off */
192#ifndef HAVE_WCHAR
193	screen->mask_ue = __ATTRIBUTES & ~__UNDERSCORE;
194#else
195	screen->mask_ue = WA_ATTRIBUTES & ~__UNDERSCORE;
196#endif /* HAVE_WCHAR */
197	if (t_eum != NULL) {
198		if (does_esc_m(t_eum))
199			screen->mask_ue &=
200			    ~(__STANDOUT | __TERMATTR | __COLOR);
201		else {
202			if (t_esm && !strcmp(t_eum, t_esm))
203				screen->mask_ue &= ~__STANDOUT;
204
205			if (t_eam != NULL && !strcmp(t_eum, t_eam))
206				screen->mask_ue &= ~__TERMATTR;
207
208			if (t_op != NULL && !strcmp(t_eum, t_op))
209				screen->mask_ue &= ~__COLOR;
210		}
211	}
212#ifndef HAVE_WCHAR
213	screen->mask_se = __ATTRIBUTES & ~__STANDOUT;
214#else
215	screen->mask_se = WA_ATTRIBUTES & ~__STANDOUT;
216#endif /* HAVE_WCHAR */
217	if (t_esm != NULL) {
218		if (does_esc_m(t_esm))
219			screen->mask_se &=
220			    ~(__UNDERSCORE | __TERMATTR | __COLOR);
221		else {
222			if (t_eum != NULL && !strcmp(t_esm, t_eum))
223				screen->mask_se &= ~__UNDERSCORE;
224
225			if (t_eam != NULL && !strcmp(t_esm, t_eam))
226				screen->mask_se &= ~__TERMATTR;
227
228			if (t_op != NULL && !strcmp(t_esm, t_op))
229				screen->mask_se &= ~__COLOR;
230		}
231	}
232
233	return unknown ? ERR : OK;
234}
235
236/*
237 * _cursesi_resetterm --
238 *  Copy the terminal instance data for the given screen to the global
239 *  variables.
240 */
241void
242_cursesi_resetterm(SCREEN *screen)
243{
244
245	LINES = screen->LINES;
246	COLS = screen->COLS;
247	ESCDELAY = screen->ESCDELAY;
248	TABSIZE = screen->TABSIZE;
249	__GT = screen->GT;
250
251	__noqch = screen->noqch;
252	__mask_op = screen->mask_op;
253	__mask_me = screen->mask_me;
254	__mask_ue = screen->mask_ue;
255	__mask_se = screen->mask_se;
256
257	set_curterm(screen->term);
258}
259
260/*
261 * does_esc_m --
262 * A hack for xterm-like terminals where "\E[m" or "\E[0m" will turn off
263 * other attributes, so we check for this in the capability passed to us.
264 * Note that we can't just do simple string comparison, as the capability
265 * may contain multiple, ';' separated sequence parts.
266 */
267static int
268does_esc_m(const char *cap)
269{
270#define WAITING 0
271#define PARSING 1
272#define NUMBER 2
273#define FOUND 4
274	const char *capptr;
275	int seq;
276
277	__CTRACE(__CTRACE_INIT, "does_esc_m: Checking %s\n", cap);
278	/* Is it just "\E[m" or "\E[0m"? */
279	if (!strcmp(cap, "\x1b[m") || !strcmp(cap, "\x1b[0m"))
280		return 1;
281
282	/* Does it contain a "\E[...m" sequence? */
283	if (strlen(cap) < 4)
284		return 0;
285	capptr = cap;
286	seq = WAITING;
287	while (*capptr != 0) {
288		switch (seq) {
289		/* Start of sequence */
290		case WAITING:
291			if (!strncmp(capptr, "\x1b[", 2)) {
292				capptr+=2;
293				if (*capptr == 'm')
294					return 1;
295				else {
296					seq=PARSING;
297					continue;
298				}
299			}
300			break;
301		/* Looking for '0' */
302		case PARSING:
303			if (*capptr == '0')
304				seq=FOUND;
305			else if (*capptr > '0' && *capptr <= '9')
306				seq=NUMBER;
307			else if (*capptr != ';')
308				seq=WAITING;
309			break;
310		/* Some other number */
311		case NUMBER:
312			if (*capptr == ';')
313				seq=PARSING;
314			else if (!(*capptr >= '0' && *capptr <= '9'))
315				seq=WAITING;
316			break;
317		/* Found a '0' */
318		case FOUND:
319			if (*capptr == 'm')
320				return 1;
321			else if (!((*capptr >= '0' && *capptr <= '9') ||
322			    *capptr == ';'))
323				seq=WAITING;
324			break;
325		default:
326			break;
327		}
328		capptr++;
329	}
330	return 0;
331}
332
333/*
334 * capdup_nodelay --
335 * A helper for does_ctrl_o below that creates a copy of the given
336 * capability with delay specifications dropped.
337 */
338static char *
339capdup_nodelay(const char *src)
340{
341	char *clean, *dst;
342
343	dst = clean = malloc(strlen(src) + 1);
344	if (__predict_false(clean == NULL))
345		return NULL;
346
347	while (*src != '\0') {
348		if (src[0] == '$' && src[1] == '<') {
349			const char *end = strchr(src + 2, '>');
350			if (__predict_true(end != NULL)) {
351				src = end + 1;
352				continue;
353			}
354		}
355		*dst++ = *src++;
356	}
357
358	*dst = '\0';
359	return clean;
360}
361
362/*
363 * does_ctrl_o --
364 * A hack for vt100/xterm-like terminals where the "me" capability also
365 * unsets acs.
366 */
367static int
368does_ctrl_o(const char *exit_cap, const char *acs_cap)
369{
370	char *eptr, *aptr;
371	int res;
372
373	__CTRACE(__CTRACE_INIT, "does_ctrl_o: Testing %s for %s\n",
374	    exit_cap, acs_cap);
375
376	eptr = capdup_nodelay(exit_cap);
377	if (__predict_false(eptr == NULL))
378		return 0;
379
380	aptr = capdup_nodelay(acs_cap);
381	if (__predict_false(aptr == NULL)) {
382		free(eptr);
383		return 0;
384	}
385
386	res = strstr(eptr, aptr) != NULL;
387
388	free(eptr);
389	free(aptr);
390	return res;
391}
392
393/*
394 * set_tabsize --
395 *   Sets the tabsize for the current screen.
396 */
397int
398set_tabsize(int tabsize)
399{
400
401	if (_cursesi_screen == NULL)
402		return ERR;
403	_cursesi_screen->TABSIZE = tabsize;
404	TABSIZE = tabsize;
405	return OK;
406}
407