dialog_util.c revision 294893
1274116Sdteske/*-
2274116Sdteske * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
3274116Sdteske * All rights reserved.
4274116Sdteske *
5274116Sdteske * Redistribution and use in source and binary forms, with or without
6274116Sdteske * modification, are permitted provided that the following conditions
7274116Sdteske * are met:
8274116Sdteske * 1. Redistributions of source code must retain the above copyright
9274116Sdteske *    notice, this list of conditions and the following disclaimer.
10274116Sdteske * 2. Redistributions in binary form must reproduce the above copyright
11274116Sdteske *    notice, this list of conditions and the following disclaimer in the
12274116Sdteske *    documentation and/or other materials provided with the distribution.
13274116Sdteske *
14274116Sdteske * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15274116Sdteske * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16274116Sdteske * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17274116Sdteske * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18274116Sdteske * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19274116Sdteske * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20274116Sdteske * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21274116Sdteske * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22274116Sdteske * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23274116Sdteske * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24274116Sdteske * SUCH DAMAGE.
25274116Sdteske */
26274116Sdteske
27274116Sdteske#include <sys/cdefs.h>
28274116Sdteske__FBSDID("$FreeBSD: head/lib/libdpv/dialog_util.c 294893 2016-01-27 06:21:35Z dteske $");
29274116Sdteske
30274116Sdteske#include <sys/ioctl.h>
31274116Sdteske
32274116Sdteske#include <ctype.h>
33274116Sdteske#include <err.h>
34274116Sdteske#include <fcntl.h>
35274116Sdteske#include <limits.h>
36274116Sdteske#include <spawn.h>
37274116Sdteske#include <stdio.h>
38274116Sdteske#include <stdlib.h>
39274116Sdteske#include <string.h>
40274116Sdteske#include <termios.h>
41274116Sdteske#include <unistd.h>
42274116Sdteske
43274116Sdteske#include "dialog_util.h"
44274116Sdteske#include "dpv.h"
45274116Sdteske#include "dpv_private.h"
46274116Sdteske
47274116Sdteskeextern char **environ;
48274116Sdteske
49274116Sdteske#define TTY_DEFAULT_ROWS	24
50274116Sdteske#define TTY_DEFAULT_COLS	80
51274116Sdteske
52274116Sdteske/* [X]dialog(1) characteristics */
53274116Sdteskeuint8_t dialog_test	= 0;
54274116Sdteskeuint8_t use_dialog	= 0;
55274116Sdteskeuint8_t use_libdialog	= 1;
56274116Sdteskeuint8_t use_xdialog	= 0;
57274116Sdteskeuint8_t use_color	= 1;
58274116Sdteskechar dialog[PATH_MAX]	= DIALOG;
59274116Sdteske
60274116Sdteske/* [X]dialog(1) functionality */
61274116Sdteskechar *title	= NULL;
62274116Sdteskechar *backtitle	= NULL;
63274116Sdteskeint dheight	= 0;
64274116Sdteskeint dwidth	= 0;
65274121Sdteskestatic char *dargv[64] = { NULL };
66274116Sdteske
67274116Sdteske/* TTY/Screen characteristics */
68274116Sdteskestatic struct winsize *maxsize = NULL;
69274116Sdteske
70274116Sdteske/* Function prototypes */
71274116Sdteskestatic void tty_maxsize_update(void);
72274116Sdteskestatic void x11_maxsize_update(void);
73274116Sdteske
74274116Sdteske/*
75274116Sdteske * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
76274116Sdteske * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
77274116Sdteske * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
78274116Sdteske * maximum height and width (respectively) for a dialog(1) widget based on the
79274116Sdteske * active TTY size.
80274116Sdteske *
81274116Sdteske * This function is called automatically by dialog_maxrows/cols() to reflect
82274116Sdteske * changes in terminal size in-between calls.
83274116Sdteske */
84274116Sdteskestatic void
85274116Sdtesketty_maxsize_update(void)
86274116Sdteske{
87274116Sdteske	int fd = STDIN_FILENO;
88274116Sdteske	struct termios t;
89274116Sdteske
90274116Sdteske	if (maxsize == NULL) {
91274116Sdteske		if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
92274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
93274116Sdteske		memset((void *)maxsize, '\0', sizeof(struct winsize));
94274116Sdteske	}
95274116Sdteske
96274116Sdteske	if (!isatty(fd))
97274116Sdteske		fd = open("/dev/tty", O_RDONLY);
98274116Sdteske	if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) {
99274116Sdteske		maxsize->ws_row = TTY_DEFAULT_ROWS;
100274116Sdteske		maxsize->ws_col = TTY_DEFAULT_COLS;
101274116Sdteske	}
102274116Sdteske}
103274116Sdteske
104274116Sdteske/*
105274116Sdteske * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
106274116Sdteske * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
107274116Sdteske * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
108274116Sdteske * maximum height and width (respectively) for an Xdialog(1) widget based on
109274116Sdteske * the active video resolution of the X11 environment.
110274116Sdteske *
111274116Sdteske * This function is called automatically by dialog_maxrows/cols() to initialize
112274116Sdteske * `maxsize'. Since video resolution changes are less common and more obtrusive
113274116Sdteske * than changes to terminal size, the dialog_maxrows/cols() functions only call
114274116Sdteske * this function when `maxsize' is set to NULL.
115274116Sdteske */
116274116Sdteskestatic void
117274116Sdteskex11_maxsize_update(void)
118274116Sdteske{
119274116Sdteske	FILE *f = NULL;
120274116Sdteske	char *cols;
121274116Sdteske	char *cp;
122274116Sdteske	char *rows;
123274116Sdteske	char cmdbuf[LINE_MAX];
124274116Sdteske	char rbuf[LINE_MAX];
125274116Sdteske
126274116Sdteske	if (maxsize == NULL) {
127274116Sdteske		if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
128274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
129274116Sdteske		memset((void *)maxsize, '\0', sizeof(struct winsize));
130274116Sdteske	}
131274116Sdteske
132274116Sdteske	/* Assemble the command necessary to get X11 sizes */
133274116Sdteske	snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog);
134274116Sdteske
135274116Sdteske	fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */
136274116Sdteske
137274116Sdteske	if ((f = popen(cmdbuf, "r")) == NULL) {
138274116Sdteske		if (debug)
139274116Sdteske			warnx("WARNING! Command `%s' failed", cmdbuf);
140274116Sdteske		return;
141274116Sdteske	}
142274116Sdteske
143274116Sdteske	/* Read in the line returned from Xdialog(1) */
144274116Sdteske	if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0))
145274116Sdteske		return;
146274116Sdteske
147274116Sdteske	/* Check for X11-related errors */
148274116Sdteske	if (strncmp(rbuf, "Xdialog: Error", 14) == 0)
149274116Sdteske		return;
150274116Sdteske
151274116Sdteske	/* Parse expected output: MaxSize: YY, XXX */
152274116Sdteske	if ((rows = strchr(rbuf, ' ')) == NULL)
153274116Sdteske		return;
154274116Sdteske	if ((cols = strchr(rows, ',')) != NULL) {
155274116Sdteske		/* strtonum(3) doesn't like trailing junk */
156274116Sdteske		*(cols++) = '\0';
157274116Sdteske		if ((cp = strchr(cols, '\n')) != NULL)
158274116Sdteske			*cp = '\0';
159274116Sdteske	}
160274116Sdteske
161274116Sdteske	/* Convert to unsigned short */
162274116Sdteske	maxsize->ws_row = (unsigned short)strtonum(
163274116Sdteske	    rows, 0, USHRT_MAX, (const char **)NULL);
164274116Sdteske	maxsize->ws_col = (unsigned short)strtonum(
165274116Sdteske	    cols, 0, USHRT_MAX, (const char **)NULL);
166274116Sdteske}
167274116Sdteske
168274116Sdteske/*
169274116Sdteske * Return the current maximum height (rows) for an [X]dialog(1) widget.
170274116Sdteske */
171274116Sdteskeint
172274116Sdteskedialog_maxrows(void)
173274116Sdteske{
174274116Sdteske
175274116Sdteske	if (use_xdialog && maxsize == NULL)
176274116Sdteske		x11_maxsize_update(); /* initialize maxsize for GUI */
177274116Sdteske	else if (!use_xdialog)
178274116Sdteske		tty_maxsize_update(); /* update maxsize for TTY */
179274116Sdteske	return (maxsize->ws_row);
180274116Sdteske}
181274116Sdteske
182274116Sdteske/*
183274116Sdteske * Return the current maximum width (cols) for an [X]dialog(1) widget.
184274116Sdteske */
185274116Sdteskeint
186274116Sdteskedialog_maxcols(void)
187274116Sdteske{
188274116Sdteske
189274116Sdteske	if (use_xdialog && maxsize == NULL)
190274116Sdteske		x11_maxsize_update(); /* initialize maxsize for GUI */
191274116Sdteske	else if (!use_xdialog)
192274116Sdteske		tty_maxsize_update(); /* update maxsize for TTY */
193274116Sdteske
194274116Sdteske	if (use_dialog || use_libdialog) {
195274116Sdteske		if (use_shadow)
196274116Sdteske			return (maxsize->ws_col - 2);
197274116Sdteske		else
198274116Sdteske			return (maxsize->ws_col);
199274116Sdteske	} else
200274116Sdteske		return (maxsize->ws_col);
201274116Sdteske}
202274116Sdteske
203274116Sdteske/*
204274116Sdteske * Return the current maximum width (cols) for the terminal.
205274116Sdteske */
206274116Sdteskeint
207274116Sdtesketty_maxcols(void)
208274116Sdteske{
209274116Sdteske
210274116Sdteske	if (use_xdialog && maxsize == NULL)
211274116Sdteske		x11_maxsize_update(); /* initialize maxsize for GUI */
212274116Sdteske	else if (!use_xdialog)
213274116Sdteske		tty_maxsize_update(); /* update maxsize for TTY */
214274116Sdteske
215274116Sdteske	return (maxsize->ws_col);
216274116Sdteske}
217274116Sdteske
218274116Sdteske/*
219274116Sdteske * Spawn an [X]dialog(1) `--gauge' box with a `--prompt' value of init_prompt.
220274116Sdteske * Writes the resulting process ID to the pid_t pointed at by `pid'. Returns a
221274116Sdteske * file descriptor (int) suitable for writing data to the [X]dialog(1) instance
222274116Sdteske * (data written to the file descriptor is seen as standard-in by the spawned
223274116Sdteske * [X]dialog(1) process).
224274116Sdteske */
225274116Sdteskeint
226274116Sdteskedialog_spawn_gauge(char *init_prompt, pid_t *pid)
227274116Sdteske{
228274116Sdteske	char dummy_init[2] = "";
229274116Sdteske	char *cp;
230274116Sdteske	int height;
231274116Sdteske	int width;
232274116Sdteske	int error;
233274116Sdteske	posix_spawn_file_actions_t action;
234274116Sdteske#if DIALOG_SPAWN_DEBUG
235274116Sdteske	unsigned int i;
236274116Sdteske#endif
237274116Sdteske	unsigned int n = 0;
238274116Sdteske	int stdin_pipe[2] = { -1, -1 };
239274116Sdteske
240274116Sdteske	/* Override `dialog' with a path from ENV_DIALOG if provided */
241274116Sdteske	if ((cp = getenv(ENV_DIALOG)) != NULL)
242274116Sdteske		snprintf(dialog, PATH_MAX, "%s", cp);
243274116Sdteske
244274116Sdteske	/* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */
245274116Sdteske	setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1);
246274116Sdteske
247274116Sdteske	/* Constrain the height/width */
248274116Sdteske	height = dialog_maxrows();
249274116Sdteske	if (backtitle != NULL)
250274116Sdteske		height -= use_shadow ? 5 : 4;
251274116Sdteske	if (dheight < height)
252274116Sdteske		height = dheight;
253274116Sdteske	width = dialog_maxcols();
254274116Sdteske	if (dwidth < width)
255274116Sdteske		width = dwidth;
256274116Sdteske
257274116Sdteske	/* Populate argument array */
258274116Sdteske	dargv[n++] = dialog;
259274116Sdteske	if (title != NULL) {
260274116Sdteske		if ((dargv[n] = malloc(8)) == NULL)
261274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
262274116Sdteske		sprintf(dargv[n++], "--title");
263274116Sdteske		dargv[n++] = title;
264294893Sdteske	} else {
265294893Sdteske		if ((dargv[n] = malloc(8)) == NULL)
266294893Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
267294893Sdteske		sprintf(dargv[n++], "--title");
268294893Sdteske		if ((dargv[n] = malloc(8)) == NULL)
269294893Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
270294893Sdteske		sprintf(dargv[n++], "");
271274116Sdteske	}
272274116Sdteske	if (backtitle != NULL) {
273274116Sdteske		if ((dargv[n] = malloc(12)) == NULL)
274274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
275274116Sdteske		sprintf(dargv[n++], "--backtitle");
276274116Sdteske		dargv[n++] = backtitle;
277274116Sdteske	}
278274116Sdteske	if (use_color) {
279274116Sdteske		if ((dargv[n] = malloc(11)) == NULL)
280274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
281274116Sdteske		sprintf(dargv[n++], "--colors");
282274116Sdteske	}
283274116Sdteske	if (use_xdialog) {
284274116Sdteske		if ((dargv[n] = malloc(7)) == NULL)
285274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
286274116Sdteske		sprintf(dargv[n++], "--left");
287274116Sdteske
288274116Sdteske		/*
289274116Sdteske		 * NOTE: Xdialog(1)'s `--wrap' appears to be broken for the
290274116Sdteske		 * `--gauge' widget prompt-updates. Add it anyway (in-case it
291274116Sdteske		 * gets fixed in some later release).
292274116Sdteske		 */
293274116Sdteske		if ((dargv[n] = malloc(7)) == NULL)
294274116Sdteske			errx(EXIT_FAILURE, "Out of memory?!");
295274116Sdteske		sprintf(dargv[n++], "--wrap");
296274116Sdteske	}
297274116Sdteske	if ((dargv[n] = malloc(8)) == NULL)
298274116Sdteske		errx(EXIT_FAILURE, "Out of memory?!");
299274116Sdteske	sprintf(dargv[n++], "--gauge");
300274116Sdteske	dargv[n++] = use_xdialog ? dummy_init : init_prompt;
301274116Sdteske	if ((dargv[n] = malloc(40)) == NULL)
302274116Sdteske		errx(EXIT_FAILURE, "Out of memory?!");
303274116Sdteske	snprintf(dargv[n++], 40, "%u", height);
304274116Sdteske	if ((dargv[n] = malloc(40)) == NULL)
305274116Sdteske		errx(EXIT_FAILURE, "Out of memory?!");
306274116Sdteske	snprintf(dargv[n++], 40, "%u", width);
307274116Sdteske	dargv[n] = NULL;
308274116Sdteske
309274116Sdteske	/* Open a pipe(2) to communicate with [X]dialog(1) */
310274116Sdteske	if (pipe(stdin_pipe) < 0)
311274116Sdteske		err(EXIT_FAILURE, "%s: pipe(2)", __func__);
312274116Sdteske
313274116Sdteske	/* Fork [X]dialog(1) process */
314274116Sdteske#if DIALOG_SPAWN_DEBUG
315274116Sdteske	fprintf(stderr, "%s: spawning `", __func__);
316274116Sdteske	for (i = 0; i < n; i++) {
317274116Sdteske		if (i == 0)
318274116Sdteske			fprintf(stderr, "%s", dargv[i]);
319274116Sdteske		else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-')
320274116Sdteske			fprintf(stderr, " %s", dargv[i]);
321274116Sdteske		else
322274116Sdteske			fprintf(stderr, " \"%s\"", dargv[i]);
323274116Sdteske	}
324274116Sdteske	fprintf(stderr, "'\n");
325274116Sdteske#endif
326274116Sdteske	posix_spawn_file_actions_init(&action);
327274116Sdteske	posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
328274116Sdteske	posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
329274116Sdteske	error = posix_spawnp(pid, dialog, &action,
330274116Sdteske	    (const posix_spawnattr_t *)NULL, dargv, environ);
331274116Sdteske	if (error != 0)
332274116Sdteske		err(EXIT_FAILURE, "%s: posix_spawnp(3)", __func__);
333274116Sdteske
334274116Sdteske	/* NB: Do not free(3) *dargv[], else SIGSEGV */
335274116Sdteske
336274116Sdteske	return (stdin_pipe[1]);
337274116Sdteske}
338274116Sdteske
339274116Sdteske/*
340274116Sdteske * Returns the number of lines in buffer pointed to by `prompt'. Takes both
341274116Sdteske * newlines and escaped-newlines into account.
342274116Sdteske */
343274116Sdteskeunsigned int
344274116Sdteskedialog_prompt_numlines(const char *prompt, uint8_t nlstate)
345274116Sdteske{
346274116Sdteske	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
347274116Sdteske	const char *cp = prompt;
348274116Sdteske	unsigned int nlines = 1;
349274116Sdteske
350274116Sdteske	if (prompt == NULL || *prompt == '\0')
351274116Sdteske		return (0);
352274116Sdteske
353274116Sdteske	while (*cp != '\0') {
354274116Sdteske		if (use_dialog) {
355274116Sdteske			if (strncmp(cp, "\\n", 2) == 0) {
356274116Sdteske				cp++;
357274116Sdteske				nlines++;
358274116Sdteske				nls = TRUE; /* See declaration comment */
359274116Sdteske			} else if (*cp == '\n') {
360274116Sdteske				if (!nls)
361274116Sdteske					nlines++;
362274116Sdteske				nls = FALSE; /* See declaration comment */
363274116Sdteske			}
364274116Sdteske		} else if (use_libdialog) {
365274116Sdteske			if (*cp == '\n')
366274116Sdteske				nlines++;
367274116Sdteske		} else if (strncmp(cp, "\\n", 2) == 0) {
368274116Sdteske			cp++;
369274116Sdteske			nlines++;
370274116Sdteske		}
371274116Sdteske		cp++;
372274116Sdteske	}
373274116Sdteske
374274116Sdteske	return (nlines);
375274116Sdteske}
376274116Sdteske
377274116Sdteske/*
378274116Sdteske * Returns the length in bytes of the longest line in buffer pointed to by
379274116Sdteske * `prompt'. Takes newlines and escaped newlines into account. Also discounts
380274116Sdteske * dialog(1) color escape codes if enabled (via `use_color' global).
381274116Sdteske */
382274116Sdteskeunsigned int
383274116Sdteskedialog_prompt_longestline(const char *prompt, uint8_t nlstate)
384274116Sdteske{
385274116Sdteske	uint8_t backslash = 0;
386274116Sdteske	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
387274116Sdteske	const char *p = prompt;
388274116Sdteske	int longest = 0;
389274116Sdteske	int n = 0;
390274116Sdteske
391274116Sdteske	/* `prompt' parameter is required */
392274116Sdteske	if (prompt == NULL)
393274116Sdteske		return (0);
394274116Sdteske	if (*prompt == '\0')
395274116Sdteske		return (0); /* shortcut */
396274116Sdteske
397274116Sdteske	/* Loop until the end of the string */
398274116Sdteske	while (*p != '\0') {
399274116Sdteske		/* dialog(1) and dialog(3) will render literal newlines */
400274116Sdteske		if (use_dialog || use_libdialog) {
401274116Sdteske			if (*p == '\n') {
402274116Sdteske				if (!use_libdialog && nls)
403274116Sdteske					n++;
404274116Sdteske				else {
405274116Sdteske					if (n > longest)
406274116Sdteske						longest = n;
407274116Sdteske					n = 0;
408274116Sdteske				}
409274116Sdteske				nls = FALSE; /* See declaration comment */
410274116Sdteske				p++;
411274116Sdteske				continue;
412274116Sdteske			}
413274116Sdteske		}
414274116Sdteske
415274116Sdteske		/* Check for backslash character */
416274116Sdteske		if (*p == '\\') {
417274116Sdteske			/* If second backslash, count as a single-char */
418274116Sdteske			if ((backslash ^= 1) == 0)
419274116Sdteske				n++;
420274116Sdteske		} else if (backslash) {
421274116Sdteske			if (*p == 'n' && !use_libdialog) { /* new line */
422274116Sdteske				/* NB: dialog(3) ignores escaped newlines */
423274116Sdteske				nls = TRUE; /* See declaration comment */
424274116Sdteske				if (n > longest)
425274116Sdteske					longest = n;
426274116Sdteske				n = 0;
427274116Sdteske			} else if (use_color && *p == 'Z') {
428274116Sdteske				if (*++p != '\0')
429274116Sdteske					p++;
430274116Sdteske				backslash = 0;
431274116Sdteske				continue;
432274116Sdteske			} else /* [X]dialog(1)/dialog(3) only expand those */
433274116Sdteske				n += 2;
434274116Sdteske
435274116Sdteske			backslash = 0;
436274116Sdteske		} else
437274116Sdteske			n++;
438274116Sdteske		p++;
439274116Sdteske	}
440274116Sdteske	if (n > longest)
441274116Sdteske		longest = n;
442274116Sdteske
443274116Sdteske	return (longest);
444274116Sdteske}
445274116Sdteske
446274116Sdteske/*
447274116Sdteske * Returns a pointer to the last line in buffer pointed to by `prompt'. Takes
448274116Sdteske * both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines
449274116Sdteske * into account. If no newlines (escaped or otherwise) appear in the buffer,
450274116Sdteske * `prompt' is returned. If passed a NULL pointer, returns NULL.
451274116Sdteske */
452274116Sdteskechar *
453274116Sdteskedialog_prompt_lastline(char *prompt, uint8_t nlstate)
454274116Sdteske{
455274116Sdteske	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
456274116Sdteske	char *lastline;
457274116Sdteske	char *p;
458274116Sdteske
459274116Sdteske	if (prompt == NULL)
460274116Sdteske		return (NULL);
461274116Sdteske	if (*prompt == '\0')
462274116Sdteske		return (prompt); /* shortcut */
463274116Sdteske
464274116Sdteske	lastline = p = prompt;
465274116Sdteske	while (*p != '\0') {
466274116Sdteske		/* dialog(1) and dialog(3) will render literal newlines */
467274116Sdteske		if (use_dialog || use_libdialog) {
468274116Sdteske			if (*p == '\n') {
469274116Sdteske				if (use_libdialog || !nls)
470274116Sdteske					lastline = p + 1;
471274116Sdteske				nls = FALSE; /* See declaration comment */
472274116Sdteske			}
473274116Sdteske		}
474274116Sdteske		/* dialog(3) does not expand escaped newlines */
475274116Sdteske		if (use_libdialog) {
476274116Sdteske			p++;
477274116Sdteske			continue;
478274116Sdteske		}
479274116Sdteske		if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
480274116Sdteske			nls = TRUE; /* See declaration comment */
481274116Sdteske			lastline = p + 1;
482274116Sdteske		}
483274116Sdteske		p++;
484274116Sdteske	}
485274116Sdteske
486274116Sdteske	return (lastline);
487274116Sdteske}
488274116Sdteske
489274116Sdteske/*
490274116Sdteske * Returns the number of extra lines generated by wrapping the text in buffer
491274116Sdteske * pointed to by `prompt' within `ncols' columns (for prompts, this should be
492274116Sdteske * dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via
493274116Sdteske * `use_color' global).
494274116Sdteske */
495274116Sdteskeint
496274116Sdteskedialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate)
497274116Sdteske{
498274116Sdteske	uint8_t backslash = 0;
499274116Sdteske	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
500274116Sdteske	char *cp;
501274116Sdteske	char *p = prompt;
502274116Sdteske	int n = 0;
503274116Sdteske	int wlines = 0;
504274116Sdteske
505274116Sdteske	/* `prompt' parameter is required */
506274116Sdteske	if (p == NULL)
507274116Sdteske		return (0);
508274116Sdteske	if (*p == '\0')
509274116Sdteske		return (0); /* shortcut */
510274116Sdteske
511274116Sdteske	/* Loop until the end of the string */
512274116Sdteske	while (*p != '\0') {
513274116Sdteske		/* dialog(1) and dialog(3) will render literal newlines */
514274116Sdteske		if (use_dialog || use_libdialog) {
515274116Sdteske			if (*p == '\n') {
516274116Sdteske				if (use_dialog || !nls)
517274116Sdteske					n = 0;
518274116Sdteske				nls = FALSE; /* See declaration comment */
519274116Sdteske			}
520274116Sdteske		}
521274116Sdteske
522274116Sdteske		/* Check for backslash character */
523274116Sdteske		if (*p == '\\') {
524274116Sdteske			/* If second backslash, count as a single-char */
525274116Sdteske			if ((backslash ^= 1) == 0)
526274116Sdteske				n++;
527274116Sdteske		} else if (backslash) {
528274116Sdteske			if (*p == 'n' && !use_libdialog) { /* new line */
529274116Sdteske				/* NB: dialog(3) ignores escaped newlines */
530274116Sdteske				nls = TRUE; /* See declaration comment */
531274116Sdteske				n = 0;
532274116Sdteske			} else if (use_color && *p == 'Z') {
533274116Sdteske				if (*++p != '\0')
534274116Sdteske					p++;
535274116Sdteske				backslash = 0;
536274116Sdteske				continue;
537274116Sdteske			} else /* [X]dialog(1)/dialog(3) only expand those */
538274116Sdteske				n += 2;
539274116Sdteske
540274116Sdteske			backslash = 0;
541274116Sdteske		} else
542274116Sdteske			n++;
543274116Sdteske
544274116Sdteske		/* Did we pass the width barrier? */
545274116Sdteske		if (n > ncols) {
546274116Sdteske			/*
547274116Sdteske			 * Work backward to find the first whitespace on-which
548274116Sdteske			 * dialog(1) will wrap the line (but don't go before
549274116Sdteske			 * the start of this line).
550274116Sdteske			 */
551274116Sdteske			cp = p;
552274116Sdteske			while (n > 1 && !isspace(*cp)) {
553274116Sdteske				cp--;
554274116Sdteske				n--;
555274116Sdteske			}
556274116Sdteske			if (n > 0 && isspace(*cp))
557274116Sdteske				p = cp;
558274116Sdteske			wlines++;
559274116Sdteske			n = 1;
560274116Sdteske		}
561274116Sdteske
562274116Sdteske		p++;
563274116Sdteske	}
564274116Sdteske
565274116Sdteske	return (wlines);
566274116Sdteske}
567274116Sdteske
568274116Sdteske/*
569274116Sdteske * Returns zero if the buffer pointed to by `prompt' contains an escaped
570274116Sdteske * newline but only if appearing after any/all literal newlines. This is
571274116Sdteske * specific to dialog(1) and does not apply to Xdialog(1).
572274116Sdteske *
573274116Sdteske * As an attempt to make shell scripts easier to read, dialog(1) will "eat"
574274116Sdteske * the first literal newline after an escaped newline. This however has a bug
575274116Sdteske * in its implementation in that rather than allowing `\\n\n' to be treated
576274116Sdteske * similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates
577274116Sdteske * the following literal newline (with or without characters between [!]) into
578274116Sdteske * a single space.
579274116Sdteske *
580274116Sdteske * If you want to be compatible with Xdialog(1), it is suggested that you not
581274116Sdteske * use literal newlines (they aren't supported); but if you have to use them,
582274116Sdteske * go right ahead. But be forewarned... if you set $DIALOG in your environment
583274116Sdteske * to something other than `cdialog' (our current dialog(1)), then it should
584274116Sdteske * do the same thing w/respect to how to handle a literal newline after an
585274116Sdteske * escaped newline (you could do no wrong by translating every literal newline
586274116Sdteske * into a space but only when you've previously encountered an escaped one;
587274116Sdteske * this is what dialog(1) is doing).
588274116Sdteske *
589274116Sdteske * The ``newline state'' (or nlstate for short; as I'm calling it) is helpful
590274116Sdteske * if you plan to combine multiple strings into a single prompt text. In lead-
591274116Sdteske * up to this procedure, a common task is to calculate and utilize the widths
592274116Sdteske * and heights of each piece of prompt text to later be combined. However, if
593274116Sdteske * (for example) the first string ends in a positive newline state (has an
594274116Sdteske * escaped newline without trailing literal), the first literal newline in the
595274116Sdteske * second string will be mangled.
596274116Sdteske *
597274116Sdteske * The return value of this function should be used as the `nlstate' argument
598274116Sdteske * to dialog_*() functions that require it to allow accurate calculations in
599274116Sdteske * the event such information is needed.
600274116Sdteske */
601274116Sdteskeuint8_t
602274116Sdteskedialog_prompt_nlstate(const char *prompt)
603274116Sdteske{
604274116Sdteske	const char *cp;
605274116Sdteske
606274116Sdteske	if (prompt == NULL)
607274116Sdteske		return 0;
608274116Sdteske
609274116Sdteske	/*
610274116Sdteske	 * Work our way backward from the end of the string for efficiency.
611274116Sdteske	 */
612274116Sdteske	cp = prompt + strlen(prompt);
613274116Sdteske	while (--cp >= prompt) {
614274116Sdteske		/*
615274116Sdteske		 * If we get to a literal newline first, this prompt ends in a
616274116Sdteske		 * clean state for rendering with dialog(1). Otherwise, if we
617274116Sdteske		 * get to an escaped newline first, this prompt ends in an un-
618274116Sdteske		 * clean state (following literal will be mangled; see above).
619274116Sdteske		 */
620274116Sdteske		if (*cp == '\n')
621274116Sdteske			return (0);
622274116Sdteske		else if (*cp == 'n' && --cp > prompt && *cp == '\\')
623274116Sdteske			return (1);
624274116Sdteske	}
625274116Sdteske
626274116Sdteske	return (0); /* no newlines (escaped or otherwise) */
627274116Sdteske}
628274116Sdteske
629274116Sdteske/*
630274116Sdteske * Free allocated items initialized by tty_maxsize_update() and
631274116Sdteske * x11_maxsize_update()
632274116Sdteske */
633274116Sdteskevoid
634274116Sdteskedialog_maxsize_free(void)
635274116Sdteske{
636274116Sdteske	if (maxsize != NULL) {
637274116Sdteske		free(maxsize);
638274116Sdteske		maxsize = NULL;
639274116Sdteske	}
640274116Sdteske}
641