xref: /freebsd/lib/libdpv/dialog_util.c (revision 041394f38a59889f0e14ace3306df5310cd5aeac)
1*041394f3SDevin Teske /*-
2*041394f3SDevin Teske  * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
3*041394f3SDevin Teske  * All rights reserved.
4*041394f3SDevin Teske  *
5*041394f3SDevin Teske  * Redistribution and use in source and binary forms, with or without
6*041394f3SDevin Teske  * modification, are permitted provided that the following conditions
7*041394f3SDevin Teske  * are met:
8*041394f3SDevin Teske  * 1. Redistributions of source code must retain the above copyright
9*041394f3SDevin Teske  *    notice, this list of conditions and the following disclaimer.
10*041394f3SDevin Teske  * 2. Redistributions in binary form must reproduce the above copyright
11*041394f3SDevin Teske  *    notice, this list of conditions and the following disclaimer in the
12*041394f3SDevin Teske  *    documentation and/or other materials provided with the distribution.
13*041394f3SDevin Teske  *
14*041394f3SDevin Teske  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15*041394f3SDevin Teske  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16*041394f3SDevin Teske  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17*041394f3SDevin Teske  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18*041394f3SDevin Teske  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19*041394f3SDevin Teske  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20*041394f3SDevin Teske  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21*041394f3SDevin Teske  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22*041394f3SDevin Teske  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23*041394f3SDevin Teske  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24*041394f3SDevin Teske  * SUCH DAMAGE.
25*041394f3SDevin Teske  */
26*041394f3SDevin Teske 
27*041394f3SDevin Teske #include <sys/cdefs.h>
28*041394f3SDevin Teske __FBSDID("$FreeBSD$");
29*041394f3SDevin Teske 
30*041394f3SDevin Teske #include <sys/ioctl.h>
31*041394f3SDevin Teske 
32*041394f3SDevin Teske #include <ctype.h>
33*041394f3SDevin Teske #include <err.h>
34*041394f3SDevin Teske #include <fcntl.h>
35*041394f3SDevin Teske #include <limits.h>
36*041394f3SDevin Teske #include <spawn.h>
37*041394f3SDevin Teske #include <stdio.h>
38*041394f3SDevin Teske #include <stdlib.h>
39*041394f3SDevin Teske #include <string.h>
40*041394f3SDevin Teske #include <termios.h>
41*041394f3SDevin Teske #include <unistd.h>
42*041394f3SDevin Teske 
43*041394f3SDevin Teske #include "dialog_util.h"
44*041394f3SDevin Teske #include "dpv.h"
45*041394f3SDevin Teske #include "dpv_private.h"
46*041394f3SDevin Teske 
47*041394f3SDevin Teske extern char **environ;
48*041394f3SDevin Teske 
49*041394f3SDevin Teske #define TTY_DEFAULT_ROWS	24
50*041394f3SDevin Teske #define TTY_DEFAULT_COLS	80
51*041394f3SDevin Teske 
52*041394f3SDevin Teske /* [X]dialog(1) characteristics */
53*041394f3SDevin Teske uint8_t dialog_test	= 0;
54*041394f3SDevin Teske uint8_t use_dialog	= 0;
55*041394f3SDevin Teske uint8_t use_libdialog	= 1;
56*041394f3SDevin Teske uint8_t use_xdialog	= 0;
57*041394f3SDevin Teske uint8_t use_color	= 1;
58*041394f3SDevin Teske char dialog[PATH_MAX]	= DIALOG;
59*041394f3SDevin Teske 
60*041394f3SDevin Teske /* [X]dialog(1) functionality */
61*041394f3SDevin Teske char *title	= NULL;
62*041394f3SDevin Teske char *backtitle	= NULL;
63*041394f3SDevin Teske int dheight	= 0;
64*041394f3SDevin Teske int dwidth	= 0;
65*041394f3SDevin Teske char *dargv[64]	= { NULL };
66*041394f3SDevin Teske 
67*041394f3SDevin Teske /* TTY/Screen characteristics */
68*041394f3SDevin Teske static struct winsize *maxsize = NULL;
69*041394f3SDevin Teske 
70*041394f3SDevin Teske /* Function prototypes */
71*041394f3SDevin Teske static void tty_maxsize_update(void);
72*041394f3SDevin Teske static void x11_maxsize_update(void);
73*041394f3SDevin Teske 
74*041394f3SDevin Teske /*
75*041394f3SDevin Teske  * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
76*041394f3SDevin Teske  * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
77*041394f3SDevin Teske  * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
78*041394f3SDevin Teske  * maximum height and width (respectively) for a dialog(1) widget based on the
79*041394f3SDevin Teske  * active TTY size.
80*041394f3SDevin Teske  *
81*041394f3SDevin Teske  * This function is called automatically by dialog_maxrows/cols() to reflect
82*041394f3SDevin Teske  * changes in terminal size in-between calls.
83*041394f3SDevin Teske  */
84*041394f3SDevin Teske static void
85*041394f3SDevin Teske tty_maxsize_update(void)
86*041394f3SDevin Teske {
87*041394f3SDevin Teske 	int fd = STDIN_FILENO;
88*041394f3SDevin Teske 	struct termios t;
89*041394f3SDevin Teske 
90*041394f3SDevin Teske 	if (maxsize == NULL) {
91*041394f3SDevin Teske 		if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
92*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
93*041394f3SDevin Teske 		memset((void *)maxsize, '\0', sizeof(struct winsize));
94*041394f3SDevin Teske 	}
95*041394f3SDevin Teske 
96*041394f3SDevin Teske 	if (!isatty(fd))
97*041394f3SDevin Teske 		fd = open("/dev/tty", O_RDONLY);
98*041394f3SDevin Teske 	if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) {
99*041394f3SDevin Teske 		maxsize->ws_row = TTY_DEFAULT_ROWS;
100*041394f3SDevin Teske 		maxsize->ws_col = TTY_DEFAULT_COLS;
101*041394f3SDevin Teske 	}
102*041394f3SDevin Teske }
103*041394f3SDevin Teske 
104*041394f3SDevin Teske /*
105*041394f3SDevin Teske  * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
106*041394f3SDevin Teske  * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
107*041394f3SDevin Teske  * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
108*041394f3SDevin Teske  * maximum height and width (respectively) for an Xdialog(1) widget based on
109*041394f3SDevin Teske  * the active video resolution of the X11 environment.
110*041394f3SDevin Teske  *
111*041394f3SDevin Teske  * This function is called automatically by dialog_maxrows/cols() to initialize
112*041394f3SDevin Teske  * `maxsize'. Since video resolution changes are less common and more obtrusive
113*041394f3SDevin Teske  * than changes to terminal size, the dialog_maxrows/cols() functions only call
114*041394f3SDevin Teske  * this function when `maxsize' is set to NULL.
115*041394f3SDevin Teske  */
116*041394f3SDevin Teske static void
117*041394f3SDevin Teske x11_maxsize_update(void)
118*041394f3SDevin Teske {
119*041394f3SDevin Teske 	FILE *f = NULL;
120*041394f3SDevin Teske 	char *cols;
121*041394f3SDevin Teske 	char *cp;
122*041394f3SDevin Teske 	char *rows;
123*041394f3SDevin Teske 	char cmdbuf[LINE_MAX];
124*041394f3SDevin Teske 	char rbuf[LINE_MAX];
125*041394f3SDevin Teske 
126*041394f3SDevin Teske 	if (maxsize == NULL) {
127*041394f3SDevin Teske 		if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
128*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
129*041394f3SDevin Teske 		memset((void *)maxsize, '\0', sizeof(struct winsize));
130*041394f3SDevin Teske 	}
131*041394f3SDevin Teske 
132*041394f3SDevin Teske 	/* Assemble the command necessary to get X11 sizes */
133*041394f3SDevin Teske 	snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog);
134*041394f3SDevin Teske 
135*041394f3SDevin Teske 	fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */
136*041394f3SDevin Teske 
137*041394f3SDevin Teske 	if ((f = popen(cmdbuf, "r")) == NULL) {
138*041394f3SDevin Teske 		if (debug)
139*041394f3SDevin Teske 			warnx("WARNING! Command `%s' failed", cmdbuf);
140*041394f3SDevin Teske 		return;
141*041394f3SDevin Teske 	}
142*041394f3SDevin Teske 
143*041394f3SDevin Teske 	/* Read in the line returned from Xdialog(1) */
144*041394f3SDevin Teske 	if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0))
145*041394f3SDevin Teske 		return;
146*041394f3SDevin Teske 
147*041394f3SDevin Teske 	/* Check for X11-related errors */
148*041394f3SDevin Teske 	if (strncmp(rbuf, "Xdialog: Error", 14) == 0)
149*041394f3SDevin Teske 		return;
150*041394f3SDevin Teske 
151*041394f3SDevin Teske 	/* Parse expected output: MaxSize: YY, XXX */
152*041394f3SDevin Teske 	if ((rows = strchr(rbuf, ' ')) == NULL)
153*041394f3SDevin Teske 		return;
154*041394f3SDevin Teske 	if ((cols = strchr(rows, ',')) != NULL) {
155*041394f3SDevin Teske 		/* strtonum(3) doesn't like trailing junk */
156*041394f3SDevin Teske 		*(cols++) = '\0';
157*041394f3SDevin Teske 		if ((cp = strchr(cols, '\n')) != NULL)
158*041394f3SDevin Teske 			*cp = '\0';
159*041394f3SDevin Teske 	}
160*041394f3SDevin Teske 
161*041394f3SDevin Teske 	/* Convert to unsigned short */
162*041394f3SDevin Teske 	maxsize->ws_row = (unsigned short)strtonum(
163*041394f3SDevin Teske 	    rows, 0, USHRT_MAX, (const char **)NULL);
164*041394f3SDevin Teske 	maxsize->ws_col = (unsigned short)strtonum(
165*041394f3SDevin Teske 	    cols, 0, USHRT_MAX, (const char **)NULL);
166*041394f3SDevin Teske }
167*041394f3SDevin Teske 
168*041394f3SDevin Teske /*
169*041394f3SDevin Teske  * Return the current maximum height (rows) for an [X]dialog(1) widget.
170*041394f3SDevin Teske  */
171*041394f3SDevin Teske int
172*041394f3SDevin Teske dialog_maxrows(void)
173*041394f3SDevin Teske {
174*041394f3SDevin Teske 
175*041394f3SDevin Teske 	if (use_xdialog && maxsize == NULL)
176*041394f3SDevin Teske 		x11_maxsize_update(); /* initialize maxsize for GUI */
177*041394f3SDevin Teske 	else if (!use_xdialog)
178*041394f3SDevin Teske 		tty_maxsize_update(); /* update maxsize for TTY */
179*041394f3SDevin Teske 	return (maxsize->ws_row);
180*041394f3SDevin Teske }
181*041394f3SDevin Teske 
182*041394f3SDevin Teske /*
183*041394f3SDevin Teske  * Return the current maximum width (cols) for an [X]dialog(1) widget.
184*041394f3SDevin Teske  */
185*041394f3SDevin Teske int
186*041394f3SDevin Teske dialog_maxcols(void)
187*041394f3SDevin Teske {
188*041394f3SDevin Teske 
189*041394f3SDevin Teske 	if (use_xdialog && maxsize == NULL)
190*041394f3SDevin Teske 		x11_maxsize_update(); /* initialize maxsize for GUI */
191*041394f3SDevin Teske 	else if (!use_xdialog)
192*041394f3SDevin Teske 		tty_maxsize_update(); /* update maxsize for TTY */
193*041394f3SDevin Teske 
194*041394f3SDevin Teske 	if (use_dialog || use_libdialog) {
195*041394f3SDevin Teske 		if (use_shadow)
196*041394f3SDevin Teske 			return (maxsize->ws_col - 2);
197*041394f3SDevin Teske 		else
198*041394f3SDevin Teske 			return (maxsize->ws_col);
199*041394f3SDevin Teske 	} else
200*041394f3SDevin Teske 		return (maxsize->ws_col);
201*041394f3SDevin Teske }
202*041394f3SDevin Teske 
203*041394f3SDevin Teske /*
204*041394f3SDevin Teske  * Return the current maximum width (cols) for the terminal.
205*041394f3SDevin Teske  */
206*041394f3SDevin Teske int
207*041394f3SDevin Teske tty_maxcols(void)
208*041394f3SDevin Teske {
209*041394f3SDevin Teske 
210*041394f3SDevin Teske 	if (use_xdialog && maxsize == NULL)
211*041394f3SDevin Teske 		x11_maxsize_update(); /* initialize maxsize for GUI */
212*041394f3SDevin Teske 	else if (!use_xdialog)
213*041394f3SDevin Teske 		tty_maxsize_update(); /* update maxsize for TTY */
214*041394f3SDevin Teske 
215*041394f3SDevin Teske 	return (maxsize->ws_col);
216*041394f3SDevin Teske }
217*041394f3SDevin Teske 
218*041394f3SDevin Teske /*
219*041394f3SDevin Teske  * Spawn an [X]dialog(1) `--gauge' box with a `--prompt' value of init_prompt.
220*041394f3SDevin Teske  * Writes the resulting process ID to the pid_t pointed at by `pid'. Returns a
221*041394f3SDevin Teske  * file descriptor (int) suitable for writing data to the [X]dialog(1) instance
222*041394f3SDevin Teske  * (data written to the file descriptor is seen as standard-in by the spawned
223*041394f3SDevin Teske  * [X]dialog(1) process).
224*041394f3SDevin Teske  */
225*041394f3SDevin Teske int
226*041394f3SDevin Teske dialog_spawn_gauge(char *init_prompt, pid_t *pid)
227*041394f3SDevin Teske {
228*041394f3SDevin Teske 	char dummy_init[2] = "";
229*041394f3SDevin Teske 	char *cp;
230*041394f3SDevin Teske 	int height;
231*041394f3SDevin Teske 	int width;
232*041394f3SDevin Teske 	int error;
233*041394f3SDevin Teske 	posix_spawn_file_actions_t action;
234*041394f3SDevin Teske #if DIALOG_SPAWN_DEBUG
235*041394f3SDevin Teske 	unsigned int i;
236*041394f3SDevin Teske #endif
237*041394f3SDevin Teske 	unsigned int n = 0;
238*041394f3SDevin Teske 	int stdin_pipe[2] = { -1, -1 };
239*041394f3SDevin Teske 
240*041394f3SDevin Teske 	/* Override `dialog' with a path from ENV_DIALOG if provided */
241*041394f3SDevin Teske 	if ((cp = getenv(ENV_DIALOG)) != NULL)
242*041394f3SDevin Teske 		snprintf(dialog, PATH_MAX, "%s", cp);
243*041394f3SDevin Teske 
244*041394f3SDevin Teske 	/* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */
245*041394f3SDevin Teske 	setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1);
246*041394f3SDevin Teske 
247*041394f3SDevin Teske 	/* Constrain the height/width */
248*041394f3SDevin Teske 	height = dialog_maxrows();
249*041394f3SDevin Teske 	if (backtitle != NULL)
250*041394f3SDevin Teske 		height -= use_shadow ? 5 : 4;
251*041394f3SDevin Teske 	if (dheight < height)
252*041394f3SDevin Teske 		height = dheight;
253*041394f3SDevin Teske 	width = dialog_maxcols();
254*041394f3SDevin Teske 	if (dwidth < width)
255*041394f3SDevin Teske 		width = dwidth;
256*041394f3SDevin Teske 
257*041394f3SDevin Teske 	/* Populate argument array */
258*041394f3SDevin Teske 	dargv[n++] = dialog;
259*041394f3SDevin Teske 	if (title != NULL) {
260*041394f3SDevin Teske 		if ((dargv[n] = malloc(8)) == NULL)
261*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
262*041394f3SDevin Teske 		sprintf(dargv[n++], "--title");
263*041394f3SDevin Teske 		dargv[n++] = title;
264*041394f3SDevin Teske 	}
265*041394f3SDevin Teske 	if (backtitle != NULL) {
266*041394f3SDevin Teske 		if ((dargv[n] = malloc(12)) == NULL)
267*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
268*041394f3SDevin Teske 		sprintf(dargv[n++], "--backtitle");
269*041394f3SDevin Teske 		dargv[n++] = backtitle;
270*041394f3SDevin Teske 	}
271*041394f3SDevin Teske 	if (use_color) {
272*041394f3SDevin Teske 		if ((dargv[n] = malloc(11)) == NULL)
273*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
274*041394f3SDevin Teske 		sprintf(dargv[n++], "--colors");
275*041394f3SDevin Teske 	}
276*041394f3SDevin Teske 	if (use_xdialog) {
277*041394f3SDevin Teske 		if ((dargv[n] = malloc(7)) == NULL)
278*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
279*041394f3SDevin Teske 		sprintf(dargv[n++], "--left");
280*041394f3SDevin Teske 
281*041394f3SDevin Teske 		/*
282*041394f3SDevin Teske 		 * NOTE: Xdialog(1)'s `--wrap' appears to be broken for the
283*041394f3SDevin Teske 		 * `--gauge' widget prompt-updates. Add it anyway (in-case it
284*041394f3SDevin Teske 		 * gets fixed in some later release).
285*041394f3SDevin Teske 		 */
286*041394f3SDevin Teske 		if ((dargv[n] = malloc(7)) == NULL)
287*041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
288*041394f3SDevin Teske 		sprintf(dargv[n++], "--wrap");
289*041394f3SDevin Teske 	}
290*041394f3SDevin Teske 	if ((dargv[n] = malloc(8)) == NULL)
291*041394f3SDevin Teske 		errx(EXIT_FAILURE, "Out of memory?!");
292*041394f3SDevin Teske 	sprintf(dargv[n++], "--gauge");
293*041394f3SDevin Teske 	dargv[n++] = use_xdialog ? dummy_init : init_prompt;
294*041394f3SDevin Teske 	if ((dargv[n] = malloc(40)) == NULL)
295*041394f3SDevin Teske 		errx(EXIT_FAILURE, "Out of memory?!");
296*041394f3SDevin Teske 	snprintf(dargv[n++], 40, "%u", height);
297*041394f3SDevin Teske 	if ((dargv[n] = malloc(40)) == NULL)
298*041394f3SDevin Teske 		errx(EXIT_FAILURE, "Out of memory?!");
299*041394f3SDevin Teske 	snprintf(dargv[n++], 40, "%u", width);
300*041394f3SDevin Teske 	dargv[n] = NULL;
301*041394f3SDevin Teske 
302*041394f3SDevin Teske 	/* Open a pipe(2) to communicate with [X]dialog(1) */
303*041394f3SDevin Teske 	if (pipe(stdin_pipe) < 0)
304*041394f3SDevin Teske 		err(EXIT_FAILURE, "%s: pipe(2)", __func__);
305*041394f3SDevin Teske 
306*041394f3SDevin Teske 	/* Fork [X]dialog(1) process */
307*041394f3SDevin Teske #if DIALOG_SPAWN_DEBUG
308*041394f3SDevin Teske 	fprintf(stderr, "%s: spawning `", __func__);
309*041394f3SDevin Teske 	for (i = 0; i < n; i++) {
310*041394f3SDevin Teske 		if (i == 0)
311*041394f3SDevin Teske 			fprintf(stderr, "%s", dargv[i]);
312*041394f3SDevin Teske 		else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-')
313*041394f3SDevin Teske 			fprintf(stderr, " %s", dargv[i]);
314*041394f3SDevin Teske 		else
315*041394f3SDevin Teske 			fprintf(stderr, " \"%s\"", dargv[i]);
316*041394f3SDevin Teske 	}
317*041394f3SDevin Teske 	fprintf(stderr, "'\n");
318*041394f3SDevin Teske #endif
319*041394f3SDevin Teske 	posix_spawn_file_actions_init(&action);
320*041394f3SDevin Teske 	posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
321*041394f3SDevin Teske 	posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
322*041394f3SDevin Teske 	error = posix_spawnp(pid, dialog, &action,
323*041394f3SDevin Teske 	    (const posix_spawnattr_t *)NULL, dargv, environ);
324*041394f3SDevin Teske 	if (error != 0)
325*041394f3SDevin Teske 		err(EXIT_FAILURE, "%s: posix_spawnp(3)", __func__);
326*041394f3SDevin Teske 
327*041394f3SDevin Teske 	/* NB: Do not free(3) *dargv[], else SIGSEGV */
328*041394f3SDevin Teske 
329*041394f3SDevin Teske 	return (stdin_pipe[1]);
330*041394f3SDevin Teske }
331*041394f3SDevin Teske 
332*041394f3SDevin Teske /*
333*041394f3SDevin Teske  * Returns the number of lines in buffer pointed to by `prompt'. Takes both
334*041394f3SDevin Teske  * newlines and escaped-newlines into account.
335*041394f3SDevin Teske  */
336*041394f3SDevin Teske unsigned int
337*041394f3SDevin Teske dialog_prompt_numlines(const char *prompt, uint8_t nlstate)
338*041394f3SDevin Teske {
339*041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
340*041394f3SDevin Teske 	const char *cp = prompt;
341*041394f3SDevin Teske 	unsigned int nlines = 1;
342*041394f3SDevin Teske 
343*041394f3SDevin Teske 	if (prompt == NULL || *prompt == '\0')
344*041394f3SDevin Teske 		return (0);
345*041394f3SDevin Teske 
346*041394f3SDevin Teske 	while (*cp != '\0') {
347*041394f3SDevin Teske 		if (use_dialog) {
348*041394f3SDevin Teske 			if (strncmp(cp, "\\n", 2) == 0) {
349*041394f3SDevin Teske 				cp++;
350*041394f3SDevin Teske 				nlines++;
351*041394f3SDevin Teske 				nls = TRUE; /* See declaration comment */
352*041394f3SDevin Teske 			} else if (*cp == '\n') {
353*041394f3SDevin Teske 				if (!nls)
354*041394f3SDevin Teske 					nlines++;
355*041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
356*041394f3SDevin Teske 			}
357*041394f3SDevin Teske 		} else if (use_libdialog) {
358*041394f3SDevin Teske 			if (*cp == '\n')
359*041394f3SDevin Teske 				nlines++;
360*041394f3SDevin Teske 		} else if (strncmp(cp, "\\n", 2) == 0) {
361*041394f3SDevin Teske 			cp++;
362*041394f3SDevin Teske 			nlines++;
363*041394f3SDevin Teske 		}
364*041394f3SDevin Teske 		cp++;
365*041394f3SDevin Teske 	}
366*041394f3SDevin Teske 
367*041394f3SDevin Teske 	return (nlines);
368*041394f3SDevin Teske }
369*041394f3SDevin Teske 
370*041394f3SDevin Teske /*
371*041394f3SDevin Teske  * Returns the length in bytes of the longest line in buffer pointed to by
372*041394f3SDevin Teske  * `prompt'. Takes newlines and escaped newlines into account. Also discounts
373*041394f3SDevin Teske  * dialog(1) color escape codes if enabled (via `use_color' global).
374*041394f3SDevin Teske  */
375*041394f3SDevin Teske unsigned int
376*041394f3SDevin Teske dialog_prompt_longestline(const char *prompt, uint8_t nlstate)
377*041394f3SDevin Teske {
378*041394f3SDevin Teske 	uint8_t backslash = 0;
379*041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
380*041394f3SDevin Teske 	const char *p = prompt;
381*041394f3SDevin Teske 	int longest = 0;
382*041394f3SDevin Teske 	int n = 0;
383*041394f3SDevin Teske 
384*041394f3SDevin Teske 	/* `prompt' parameter is required */
385*041394f3SDevin Teske 	if (prompt == NULL)
386*041394f3SDevin Teske 		return (0);
387*041394f3SDevin Teske 	if (*prompt == '\0')
388*041394f3SDevin Teske 		return (0); /* shortcut */
389*041394f3SDevin Teske 
390*041394f3SDevin Teske 	/* Loop until the end of the string */
391*041394f3SDevin Teske 	while (*p != '\0') {
392*041394f3SDevin Teske 		/* dialog(1) and dialog(3) will render literal newlines */
393*041394f3SDevin Teske 		if (use_dialog || use_libdialog) {
394*041394f3SDevin Teske 			if (*p == '\n') {
395*041394f3SDevin Teske 				if (!use_libdialog && nls)
396*041394f3SDevin Teske 					n++;
397*041394f3SDevin Teske 				else {
398*041394f3SDevin Teske 					if (n > longest)
399*041394f3SDevin Teske 						longest = n;
400*041394f3SDevin Teske 					n = 0;
401*041394f3SDevin Teske 				}
402*041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
403*041394f3SDevin Teske 				p++;
404*041394f3SDevin Teske 				continue;
405*041394f3SDevin Teske 			}
406*041394f3SDevin Teske 		}
407*041394f3SDevin Teske 
408*041394f3SDevin Teske 		/* Check for backslash character */
409*041394f3SDevin Teske 		if (*p == '\\') {
410*041394f3SDevin Teske 			/* If second backslash, count as a single-char */
411*041394f3SDevin Teske 			if ((backslash ^= 1) == 0)
412*041394f3SDevin Teske 				n++;
413*041394f3SDevin Teske 		} else if (backslash) {
414*041394f3SDevin Teske 			if (*p == 'n' && !use_libdialog) { /* new line */
415*041394f3SDevin Teske 				/* NB: dialog(3) ignores escaped newlines */
416*041394f3SDevin Teske 				nls = TRUE; /* See declaration comment */
417*041394f3SDevin Teske 				if (n > longest)
418*041394f3SDevin Teske 					longest = n;
419*041394f3SDevin Teske 				n = 0;
420*041394f3SDevin Teske 			} else if (use_color && *p == 'Z') {
421*041394f3SDevin Teske 				if (*++p != '\0')
422*041394f3SDevin Teske 					p++;
423*041394f3SDevin Teske 				backslash = 0;
424*041394f3SDevin Teske 				continue;
425*041394f3SDevin Teske 			} else /* [X]dialog(1)/dialog(3) only expand those */
426*041394f3SDevin Teske 				n += 2;
427*041394f3SDevin Teske 
428*041394f3SDevin Teske 			backslash = 0;
429*041394f3SDevin Teske 		} else
430*041394f3SDevin Teske 			n++;
431*041394f3SDevin Teske 		p++;
432*041394f3SDevin Teske 	}
433*041394f3SDevin Teske 	if (n > longest)
434*041394f3SDevin Teske 		longest = n;
435*041394f3SDevin Teske 
436*041394f3SDevin Teske 	return (longest);
437*041394f3SDevin Teske }
438*041394f3SDevin Teske 
439*041394f3SDevin Teske /*
440*041394f3SDevin Teske  * Returns a pointer to the last line in buffer pointed to by `prompt'. Takes
441*041394f3SDevin Teske  * both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines
442*041394f3SDevin Teske  * into account. If no newlines (escaped or otherwise) appear in the buffer,
443*041394f3SDevin Teske  * `prompt' is returned. If passed a NULL pointer, returns NULL.
444*041394f3SDevin Teske  */
445*041394f3SDevin Teske char *
446*041394f3SDevin Teske dialog_prompt_lastline(char *prompt, uint8_t nlstate)
447*041394f3SDevin Teske {
448*041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
449*041394f3SDevin Teske 	char *lastline;
450*041394f3SDevin Teske 	char *p;
451*041394f3SDevin Teske 
452*041394f3SDevin Teske 	if (prompt == NULL)
453*041394f3SDevin Teske 		return (NULL);
454*041394f3SDevin Teske 	if (*prompt == '\0')
455*041394f3SDevin Teske 		return (prompt); /* shortcut */
456*041394f3SDevin Teske 
457*041394f3SDevin Teske 	lastline = p = prompt;
458*041394f3SDevin Teske 	while (*p != '\0') {
459*041394f3SDevin Teske 		/* dialog(1) and dialog(3) will render literal newlines */
460*041394f3SDevin Teske 		if (use_dialog || use_libdialog) {
461*041394f3SDevin Teske 			if (*p == '\n') {
462*041394f3SDevin Teske 				if (use_libdialog || !nls)
463*041394f3SDevin Teske 					lastline = p + 1;
464*041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
465*041394f3SDevin Teske 			}
466*041394f3SDevin Teske 		}
467*041394f3SDevin Teske 		/* dialog(3) does not expand escaped newlines */
468*041394f3SDevin Teske 		if (use_libdialog) {
469*041394f3SDevin Teske 			p++;
470*041394f3SDevin Teske 			continue;
471*041394f3SDevin Teske 		}
472*041394f3SDevin Teske 		if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
473*041394f3SDevin Teske 			nls = TRUE; /* See declaration comment */
474*041394f3SDevin Teske 			lastline = p + 1;
475*041394f3SDevin Teske 		}
476*041394f3SDevin Teske 		p++;
477*041394f3SDevin Teske 	}
478*041394f3SDevin Teske 
479*041394f3SDevin Teske 	return (lastline);
480*041394f3SDevin Teske }
481*041394f3SDevin Teske 
482*041394f3SDevin Teske /*
483*041394f3SDevin Teske  * Returns the number of extra lines generated by wrapping the text in buffer
484*041394f3SDevin Teske  * pointed to by `prompt' within `ncols' columns (for prompts, this should be
485*041394f3SDevin Teske  * dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via
486*041394f3SDevin Teske  * `use_color' global).
487*041394f3SDevin Teske  */
488*041394f3SDevin Teske int
489*041394f3SDevin Teske dialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate)
490*041394f3SDevin Teske {
491*041394f3SDevin Teske 	uint8_t backslash = 0;
492*041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
493*041394f3SDevin Teske 	char *cp;
494*041394f3SDevin Teske 	char *p = prompt;
495*041394f3SDevin Teske 	int n = 0;
496*041394f3SDevin Teske 	int wlines = 0;
497*041394f3SDevin Teske 
498*041394f3SDevin Teske 	/* `prompt' parameter is required */
499*041394f3SDevin Teske 	if (p == NULL)
500*041394f3SDevin Teske 		return (0);
501*041394f3SDevin Teske 	if (*p == '\0')
502*041394f3SDevin Teske 		return (0); /* shortcut */
503*041394f3SDevin Teske 
504*041394f3SDevin Teske 	/* Loop until the end of the string */
505*041394f3SDevin Teske 	while (*p != '\0') {
506*041394f3SDevin Teske 		/* dialog(1) and dialog(3) will render literal newlines */
507*041394f3SDevin Teske 		if (use_dialog || use_libdialog) {
508*041394f3SDevin Teske 			if (*p == '\n') {
509*041394f3SDevin Teske 				if (use_dialog || !nls)
510*041394f3SDevin Teske 					n = 0;
511*041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
512*041394f3SDevin Teske 			}
513*041394f3SDevin Teske 		}
514*041394f3SDevin Teske 
515*041394f3SDevin Teske 		/* Check for backslash character */
516*041394f3SDevin Teske 		if (*p == '\\') {
517*041394f3SDevin Teske 			/* If second backslash, count as a single-char */
518*041394f3SDevin Teske 			if ((backslash ^= 1) == 0)
519*041394f3SDevin Teske 				n++;
520*041394f3SDevin Teske 		} else if (backslash) {
521*041394f3SDevin Teske 			if (*p == 'n' && !use_libdialog) { /* new line */
522*041394f3SDevin Teske 				/* NB: dialog(3) ignores escaped newlines */
523*041394f3SDevin Teske 				nls = TRUE; /* See declaration comment */
524*041394f3SDevin Teske 				n = 0;
525*041394f3SDevin Teske 			} else if (use_color && *p == 'Z') {
526*041394f3SDevin Teske 				if (*++p != '\0')
527*041394f3SDevin Teske 					p++;
528*041394f3SDevin Teske 				backslash = 0;
529*041394f3SDevin Teske 				continue;
530*041394f3SDevin Teske 			} else /* [X]dialog(1)/dialog(3) only expand those */
531*041394f3SDevin Teske 				n += 2;
532*041394f3SDevin Teske 
533*041394f3SDevin Teske 			backslash = 0;
534*041394f3SDevin Teske 		} else
535*041394f3SDevin Teske 			n++;
536*041394f3SDevin Teske 
537*041394f3SDevin Teske 		/* Did we pass the width barrier? */
538*041394f3SDevin Teske 		if (n > ncols) {
539*041394f3SDevin Teske 			/*
540*041394f3SDevin Teske 			 * Work backward to find the first whitespace on-which
541*041394f3SDevin Teske 			 * dialog(1) will wrap the line (but don't go before
542*041394f3SDevin Teske 			 * the start of this line).
543*041394f3SDevin Teske 			 */
544*041394f3SDevin Teske 			cp = p;
545*041394f3SDevin Teske 			while (n > 1 && !isspace(*cp)) {
546*041394f3SDevin Teske 				cp--;
547*041394f3SDevin Teske 				n--;
548*041394f3SDevin Teske 			}
549*041394f3SDevin Teske 			if (n > 0 && isspace(*cp))
550*041394f3SDevin Teske 				p = cp;
551*041394f3SDevin Teske 			wlines++;
552*041394f3SDevin Teske 			n = 1;
553*041394f3SDevin Teske 		}
554*041394f3SDevin Teske 
555*041394f3SDevin Teske 		p++;
556*041394f3SDevin Teske 	}
557*041394f3SDevin Teske 
558*041394f3SDevin Teske 	return (wlines);
559*041394f3SDevin Teske }
560*041394f3SDevin Teske 
561*041394f3SDevin Teske /*
562*041394f3SDevin Teske  * Returns zero if the buffer pointed to by `prompt' contains an escaped
563*041394f3SDevin Teske  * newline but only if appearing after any/all literal newlines. This is
564*041394f3SDevin Teske  * specific to dialog(1) and does not apply to Xdialog(1).
565*041394f3SDevin Teske  *
566*041394f3SDevin Teske  * As an attempt to make shell scripts easier to read, dialog(1) will "eat"
567*041394f3SDevin Teske  * the first literal newline after an escaped newline. This however has a bug
568*041394f3SDevin Teske  * in its implementation in that rather than allowing `\\n\n' to be treated
569*041394f3SDevin Teske  * similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates
570*041394f3SDevin Teske  * the following literal newline (with or without characters between [!]) into
571*041394f3SDevin Teske  * a single space.
572*041394f3SDevin Teske  *
573*041394f3SDevin Teske  * If you want to be compatible with Xdialog(1), it is suggested that you not
574*041394f3SDevin Teske  * use literal newlines (they aren't supported); but if you have to use them,
575*041394f3SDevin Teske  * go right ahead. But be forewarned... if you set $DIALOG in your environment
576*041394f3SDevin Teske  * to something other than `cdialog' (our current dialog(1)), then it should
577*041394f3SDevin Teske  * do the same thing w/respect to how to handle a literal newline after an
578*041394f3SDevin Teske  * escaped newline (you could do no wrong by translating every literal newline
579*041394f3SDevin Teske  * into a space but only when you've previously encountered an escaped one;
580*041394f3SDevin Teske  * this is what dialog(1) is doing).
581*041394f3SDevin Teske  *
582*041394f3SDevin Teske  * The ``newline state'' (or nlstate for short; as I'm calling it) is helpful
583*041394f3SDevin Teske  * if you plan to combine multiple strings into a single prompt text. In lead-
584*041394f3SDevin Teske  * up to this procedure, a common task is to calculate and utilize the widths
585*041394f3SDevin Teske  * and heights of each piece of prompt text to later be combined. However, if
586*041394f3SDevin Teske  * (for example) the first string ends in a positive newline state (has an
587*041394f3SDevin Teske  * escaped newline without trailing literal), the first literal newline in the
588*041394f3SDevin Teske  * second string will be mangled.
589*041394f3SDevin Teske  *
590*041394f3SDevin Teske  * The return value of this function should be used as the `nlstate' argument
591*041394f3SDevin Teske  * to dialog_*() functions that require it to allow accurate calculations in
592*041394f3SDevin Teske  * the event such information is needed.
593*041394f3SDevin Teske  */
594*041394f3SDevin Teske uint8_t
595*041394f3SDevin Teske dialog_prompt_nlstate(const char *prompt)
596*041394f3SDevin Teske {
597*041394f3SDevin Teske 	const char *cp;
598*041394f3SDevin Teske 
599*041394f3SDevin Teske 	if (prompt == NULL)
600*041394f3SDevin Teske 		return 0;
601*041394f3SDevin Teske 
602*041394f3SDevin Teske 	/*
603*041394f3SDevin Teske 	 * Work our way backward from the end of the string for efficiency.
604*041394f3SDevin Teske 	 */
605*041394f3SDevin Teske 	cp = prompt + strlen(prompt);
606*041394f3SDevin Teske 	while (--cp >= prompt) {
607*041394f3SDevin Teske 		/*
608*041394f3SDevin Teske 		 * If we get to a literal newline first, this prompt ends in a
609*041394f3SDevin Teske 		 * clean state for rendering with dialog(1). Otherwise, if we
610*041394f3SDevin Teske 		 * get to an escaped newline first, this prompt ends in an un-
611*041394f3SDevin Teske 		 * clean state (following literal will be mangled; see above).
612*041394f3SDevin Teske 		 */
613*041394f3SDevin Teske 		if (*cp == '\n')
614*041394f3SDevin Teske 			return (0);
615*041394f3SDevin Teske 		else if (*cp == 'n' && --cp > prompt && *cp == '\\')
616*041394f3SDevin Teske 			return (1);
617*041394f3SDevin Teske 	}
618*041394f3SDevin Teske 
619*041394f3SDevin Teske 	return (0); /* no newlines (escaped or otherwise) */
620*041394f3SDevin Teske }
621*041394f3SDevin Teske 
622*041394f3SDevin Teske /*
623*041394f3SDevin Teske  * Free allocated items initialized by tty_maxsize_update() and
624*041394f3SDevin Teske  * x11_maxsize_update()
625*041394f3SDevin Teske  */
626*041394f3SDevin Teske void
627*041394f3SDevin Teske dialog_maxsize_free(void)
628*041394f3SDevin Teske {
629*041394f3SDevin Teske 	if (maxsize != NULL) {
630*041394f3SDevin Teske 		free(maxsize);
631*041394f3SDevin Teske 		maxsize = NULL;
632*041394f3SDevin Teske 	}
633*041394f3SDevin Teske }
634