xref: /freebsd/lib/libdpv/dialog_util.c (revision 1d386b48a555f61cb7325543adbbb5c3f3407a66)
1041394f3SDevin Teske /*-
2*cfeecda0SDevin Teske  * Copyright (c) 2013-2018 Devin Teske <dteske@FreeBSD.org>
3041394f3SDevin Teske  * All rights reserved.
4041394f3SDevin Teske  *
5041394f3SDevin Teske  * Redistribution and use in source and binary forms, with or without
6041394f3SDevin Teske  * modification, are permitted provided that the following conditions
7041394f3SDevin Teske  * are met:
8041394f3SDevin Teske  * 1. Redistributions of source code must retain the above copyright
9041394f3SDevin Teske  *    notice, this list of conditions and the following disclaimer.
10041394f3SDevin Teske  * 2. Redistributions in binary form must reproduce the above copyright
11041394f3SDevin Teske  *    notice, this list of conditions and the following disclaimer in the
12041394f3SDevin Teske  *    documentation and/or other materials provided with the distribution.
13041394f3SDevin Teske  *
14041394f3SDevin Teske  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15041394f3SDevin Teske  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16041394f3SDevin Teske  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17041394f3SDevin Teske  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18041394f3SDevin Teske  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19041394f3SDevin Teske  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20041394f3SDevin Teske  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21041394f3SDevin Teske  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22041394f3SDevin Teske  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23041394f3SDevin Teske  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24041394f3SDevin Teske  * SUCH DAMAGE.
25041394f3SDevin Teske  */
26041394f3SDevin Teske 
27041394f3SDevin Teske #include <sys/cdefs.h>
28041394f3SDevin Teske #include <sys/ioctl.h>
29041394f3SDevin Teske 
30041394f3SDevin Teske #include <ctype.h>
31041394f3SDevin Teske #include <err.h>
32041394f3SDevin Teske #include <fcntl.h>
33041394f3SDevin Teske #include <limits.h>
34041394f3SDevin Teske #include <spawn.h>
35041394f3SDevin Teske #include <stdio.h>
36041394f3SDevin Teske #include <stdlib.h>
37041394f3SDevin Teske #include <string.h>
38041394f3SDevin Teske #include <termios.h>
39041394f3SDevin Teske #include <unistd.h>
40041394f3SDevin Teske 
41041394f3SDevin Teske #include "dialog_util.h"
42041394f3SDevin Teske #include "dpv.h"
43041394f3SDevin Teske #include "dpv_private.h"
44041394f3SDevin Teske 
45041394f3SDevin Teske extern char **environ;
46041394f3SDevin Teske 
47041394f3SDevin Teske #define TTY_DEFAULT_ROWS	24
48041394f3SDevin Teske #define TTY_DEFAULT_COLS	80
49041394f3SDevin Teske 
50041394f3SDevin Teske /* [X]dialog(1) characteristics */
51041394f3SDevin Teske uint8_t dialog_test	= 0;
52041394f3SDevin Teske uint8_t use_dialog	= 0;
53041394f3SDevin Teske uint8_t use_libdialog	= 1;
54041394f3SDevin Teske uint8_t use_xdialog	= 0;
55041394f3SDevin Teske uint8_t use_color	= 1;
56041394f3SDevin Teske char dialog[PATH_MAX]	= DIALOG;
57041394f3SDevin Teske 
58041394f3SDevin Teske /* [X]dialog(1) functionality */
59041394f3SDevin Teske char *title	= NULL;
60041394f3SDevin Teske char *backtitle	= NULL;
61041394f3SDevin Teske int dheight	= 0;
62041394f3SDevin Teske int dwidth	= 0;
63492b8271SDevin Teske static char *dargv[64] = { NULL };
64041394f3SDevin Teske 
65041394f3SDevin Teske /* TTY/Screen characteristics */
66041394f3SDevin Teske static struct winsize *maxsize = NULL;
67041394f3SDevin Teske 
68041394f3SDevin Teske /* Function prototypes */
69041394f3SDevin Teske static void tty_maxsize_update(void);
70041394f3SDevin Teske static void x11_maxsize_update(void);
71041394f3SDevin Teske 
72041394f3SDevin Teske /*
73041394f3SDevin Teske  * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
74041394f3SDevin Teske  * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
75041394f3SDevin Teske  * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
76041394f3SDevin Teske  * maximum height and width (respectively) for a dialog(1) widget based on the
77041394f3SDevin Teske  * active TTY size.
78041394f3SDevin Teske  *
79041394f3SDevin Teske  * This function is called automatically by dialog_maxrows/cols() to reflect
80041394f3SDevin Teske  * changes in terminal size in-between calls.
81041394f3SDevin Teske  */
82041394f3SDevin Teske static void
tty_maxsize_update(void)83041394f3SDevin Teske tty_maxsize_update(void)
84041394f3SDevin Teske {
85041394f3SDevin Teske 	int fd = STDIN_FILENO;
86041394f3SDevin Teske 	struct termios t;
87041394f3SDevin Teske 
88041394f3SDevin Teske 	if (maxsize == NULL) {
89041394f3SDevin Teske 		if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
90041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
91041394f3SDevin Teske 		memset((void *)maxsize, '\0', sizeof(struct winsize));
92041394f3SDevin Teske 	}
93041394f3SDevin Teske 
94041394f3SDevin Teske 	if (!isatty(fd))
95041394f3SDevin Teske 		fd = open("/dev/tty", O_RDONLY);
96041394f3SDevin Teske 	if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) {
97041394f3SDevin Teske 		maxsize->ws_row = TTY_DEFAULT_ROWS;
98041394f3SDevin Teske 		maxsize->ws_col = TTY_DEFAULT_COLS;
99041394f3SDevin Teske 	}
100041394f3SDevin Teske }
101041394f3SDevin Teske 
102041394f3SDevin Teske /*
103041394f3SDevin Teske  * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
104041394f3SDevin Teske  * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
105041394f3SDevin Teske  * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
106041394f3SDevin Teske  * maximum height and width (respectively) for an Xdialog(1) widget based on
107041394f3SDevin Teske  * the active video resolution of the X11 environment.
108041394f3SDevin Teske  *
109041394f3SDevin Teske  * This function is called automatically by dialog_maxrows/cols() to initialize
110041394f3SDevin Teske  * `maxsize'. Since video resolution changes are less common and more obtrusive
111041394f3SDevin Teske  * than changes to terminal size, the dialog_maxrows/cols() functions only call
112041394f3SDevin Teske  * this function when `maxsize' is set to NULL.
113041394f3SDevin Teske  */
114041394f3SDevin Teske static void
x11_maxsize_update(void)115041394f3SDevin Teske x11_maxsize_update(void)
116041394f3SDevin Teske {
117041394f3SDevin Teske 	FILE *f = NULL;
118041394f3SDevin Teske 	char *cols;
119041394f3SDevin Teske 	char *cp;
120041394f3SDevin Teske 	char *rows;
121041394f3SDevin Teske 	char cmdbuf[LINE_MAX];
122041394f3SDevin Teske 	char rbuf[LINE_MAX];
123041394f3SDevin Teske 
124041394f3SDevin Teske 	if (maxsize == NULL) {
125041394f3SDevin Teske 		if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
126041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
127041394f3SDevin Teske 		memset((void *)maxsize, '\0', sizeof(struct winsize));
128041394f3SDevin Teske 	}
129041394f3SDevin Teske 
130041394f3SDevin Teske 	/* Assemble the command necessary to get X11 sizes */
131041394f3SDevin Teske 	snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog);
132041394f3SDevin Teske 
133041394f3SDevin Teske 	fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */
134041394f3SDevin Teske 
135041394f3SDevin Teske 	if ((f = popen(cmdbuf, "r")) == NULL) {
136041394f3SDevin Teske 		if (debug)
137041394f3SDevin Teske 			warnx("WARNING! Command `%s' failed", cmdbuf);
138041394f3SDevin Teske 		return;
139041394f3SDevin Teske 	}
140041394f3SDevin Teske 
141041394f3SDevin Teske 	/* Read in the line returned from Xdialog(1) */
142041394f3SDevin Teske 	if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0))
143041394f3SDevin Teske 		return;
144041394f3SDevin Teske 
145041394f3SDevin Teske 	/* Check for X11-related errors */
146041394f3SDevin Teske 	if (strncmp(rbuf, "Xdialog: Error", 14) == 0)
147041394f3SDevin Teske 		return;
148041394f3SDevin Teske 
149041394f3SDevin Teske 	/* Parse expected output: MaxSize: YY, XXX */
150041394f3SDevin Teske 	if ((rows = strchr(rbuf, ' ')) == NULL)
151041394f3SDevin Teske 		return;
152041394f3SDevin Teske 	if ((cols = strchr(rows, ',')) != NULL) {
153041394f3SDevin Teske 		/* strtonum(3) doesn't like trailing junk */
154041394f3SDevin Teske 		*(cols++) = '\0';
155041394f3SDevin Teske 		if ((cp = strchr(cols, '\n')) != NULL)
156041394f3SDevin Teske 			*cp = '\0';
157041394f3SDevin Teske 	}
158041394f3SDevin Teske 
159041394f3SDevin Teske 	/* Convert to unsigned short */
160041394f3SDevin Teske 	maxsize->ws_row = (unsigned short)strtonum(
161041394f3SDevin Teske 	    rows, 0, USHRT_MAX, (const char **)NULL);
162041394f3SDevin Teske 	maxsize->ws_col = (unsigned short)strtonum(
163041394f3SDevin Teske 	    cols, 0, USHRT_MAX, (const char **)NULL);
164041394f3SDevin Teske }
165041394f3SDevin Teske 
166041394f3SDevin Teske /*
167041394f3SDevin Teske  * Return the current maximum height (rows) for an [X]dialog(1) widget.
168041394f3SDevin Teske  */
169041394f3SDevin Teske int
dialog_maxrows(void)170041394f3SDevin Teske dialog_maxrows(void)
171041394f3SDevin Teske {
172041394f3SDevin Teske 
173041394f3SDevin Teske 	if (use_xdialog && maxsize == NULL)
174041394f3SDevin Teske 		x11_maxsize_update(); /* initialize maxsize for GUI */
175041394f3SDevin Teske 	else if (!use_xdialog)
176041394f3SDevin Teske 		tty_maxsize_update(); /* update maxsize for TTY */
177041394f3SDevin Teske 	return (maxsize->ws_row);
178041394f3SDevin Teske }
179041394f3SDevin Teske 
180041394f3SDevin Teske /*
181041394f3SDevin Teske  * Return the current maximum width (cols) for an [X]dialog(1) widget.
182041394f3SDevin Teske  */
183041394f3SDevin Teske int
dialog_maxcols(void)184041394f3SDevin Teske dialog_maxcols(void)
185041394f3SDevin Teske {
186041394f3SDevin Teske 
187041394f3SDevin Teske 	if (use_xdialog && maxsize == NULL)
188041394f3SDevin Teske 		x11_maxsize_update(); /* initialize maxsize for GUI */
189041394f3SDevin Teske 	else if (!use_xdialog)
190041394f3SDevin Teske 		tty_maxsize_update(); /* update maxsize for TTY */
191041394f3SDevin Teske 
192041394f3SDevin Teske 	if (use_dialog || use_libdialog) {
193041394f3SDevin Teske 		if (use_shadow)
194041394f3SDevin Teske 			return (maxsize->ws_col - 2);
195041394f3SDevin Teske 		else
196041394f3SDevin Teske 			return (maxsize->ws_col);
197041394f3SDevin Teske 	} else
198041394f3SDevin Teske 		return (maxsize->ws_col);
199041394f3SDevin Teske }
200041394f3SDevin Teske 
201041394f3SDevin Teske /*
202041394f3SDevin Teske  * Return the current maximum width (cols) for the terminal.
203041394f3SDevin Teske  */
204041394f3SDevin Teske int
tty_maxcols(void)205041394f3SDevin Teske tty_maxcols(void)
206041394f3SDevin Teske {
207041394f3SDevin Teske 
208041394f3SDevin Teske 	if (use_xdialog && maxsize == NULL)
209041394f3SDevin Teske 		x11_maxsize_update(); /* initialize maxsize for GUI */
210041394f3SDevin Teske 	else if (!use_xdialog)
211041394f3SDevin Teske 		tty_maxsize_update(); /* update maxsize for TTY */
212041394f3SDevin Teske 
213041394f3SDevin Teske 	return (maxsize->ws_col);
214041394f3SDevin Teske }
215041394f3SDevin Teske 
216041394f3SDevin Teske /*
217041394f3SDevin Teske  * Spawn an [X]dialog(1) `--gauge' box with a `--prompt' value of init_prompt.
218041394f3SDevin Teske  * Writes the resulting process ID to the pid_t pointed at by `pid'. Returns a
219041394f3SDevin Teske  * file descriptor (int) suitable for writing data to the [X]dialog(1) instance
220041394f3SDevin Teske  * (data written to the file descriptor is seen as standard-in by the spawned
221041394f3SDevin Teske  * [X]dialog(1) process).
222041394f3SDevin Teske  */
223041394f3SDevin Teske int
dialog_spawn_gauge(char * init_prompt,pid_t * pid)224041394f3SDevin Teske dialog_spawn_gauge(char *init_prompt, pid_t *pid)
225041394f3SDevin Teske {
226041394f3SDevin Teske 	char dummy_init[2] = "";
227041394f3SDevin Teske 	char *cp;
228041394f3SDevin Teske 	int height;
229041394f3SDevin Teske 	int width;
230041394f3SDevin Teske 	int error;
231041394f3SDevin Teske 	posix_spawn_file_actions_t action;
232041394f3SDevin Teske #if DIALOG_SPAWN_DEBUG
233041394f3SDevin Teske 	unsigned int i;
234041394f3SDevin Teske #endif
235041394f3SDevin Teske 	unsigned int n = 0;
236041394f3SDevin Teske 	int stdin_pipe[2] = { -1, -1 };
237041394f3SDevin Teske 
238041394f3SDevin Teske 	/* Override `dialog' with a path from ENV_DIALOG if provided */
239041394f3SDevin Teske 	if ((cp = getenv(ENV_DIALOG)) != NULL)
240041394f3SDevin Teske 		snprintf(dialog, PATH_MAX, "%s", cp);
241041394f3SDevin Teske 
242041394f3SDevin Teske 	/* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */
243041394f3SDevin Teske 	setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1);
244041394f3SDevin Teske 
245041394f3SDevin Teske 	/* Constrain the height/width */
246041394f3SDevin Teske 	height = dialog_maxrows();
247041394f3SDevin Teske 	if (backtitle != NULL)
248041394f3SDevin Teske 		height -= use_shadow ? 5 : 4;
249041394f3SDevin Teske 	if (dheight < height)
250041394f3SDevin Teske 		height = dheight;
251041394f3SDevin Teske 	width = dialog_maxcols();
252041394f3SDevin Teske 	if (dwidth < width)
253041394f3SDevin Teske 		width = dwidth;
254041394f3SDevin Teske 
255041394f3SDevin Teske 	/* Populate argument array */
256041394f3SDevin Teske 	dargv[n++] = dialog;
257041394f3SDevin Teske 	if (title != NULL) {
258041394f3SDevin Teske 		if ((dargv[n] = malloc(8)) == NULL)
259041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
260041394f3SDevin Teske 		sprintf(dargv[n++], "--title");
261041394f3SDevin Teske 		dargv[n++] = title;
262b888adc7SDevin Teske 	} else {
263b888adc7SDevin Teske 		if ((dargv[n] = malloc(8)) == NULL)
264b888adc7SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
265b888adc7SDevin Teske 		sprintf(dargv[n++], "--title");
266116cc28aSDevin Teske 		if ((dargv[n] = malloc(1)) == NULL)
267b888adc7SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
268116cc28aSDevin Teske 		*dargv[n++] = '\0';
269041394f3SDevin Teske 	}
270041394f3SDevin Teske 	if (backtitle != NULL) {
271041394f3SDevin Teske 		if ((dargv[n] = malloc(12)) == NULL)
272041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
273041394f3SDevin Teske 		sprintf(dargv[n++], "--backtitle");
274041394f3SDevin Teske 		dargv[n++] = backtitle;
275041394f3SDevin Teske 	}
276041394f3SDevin Teske 	if (use_color) {
277041394f3SDevin Teske 		if ((dargv[n] = malloc(11)) == NULL)
278041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
279041394f3SDevin Teske 		sprintf(dargv[n++], "--colors");
280041394f3SDevin Teske 	}
281041394f3SDevin Teske 	if (use_xdialog) {
282041394f3SDevin Teske 		if ((dargv[n] = malloc(7)) == NULL)
283041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
284041394f3SDevin Teske 		sprintf(dargv[n++], "--left");
285041394f3SDevin Teske 
286041394f3SDevin Teske 		/*
287041394f3SDevin Teske 		 * NOTE: Xdialog(1)'s `--wrap' appears to be broken for the
288041394f3SDevin Teske 		 * `--gauge' widget prompt-updates. Add it anyway (in-case it
289041394f3SDevin Teske 		 * gets fixed in some later release).
290041394f3SDevin Teske 		 */
291041394f3SDevin Teske 		if ((dargv[n] = malloc(7)) == NULL)
292041394f3SDevin Teske 			errx(EXIT_FAILURE, "Out of memory?!");
293041394f3SDevin Teske 		sprintf(dargv[n++], "--wrap");
294041394f3SDevin Teske 	}
295041394f3SDevin Teske 	if ((dargv[n] = malloc(8)) == NULL)
296041394f3SDevin Teske 		errx(EXIT_FAILURE, "Out of memory?!");
297041394f3SDevin Teske 	sprintf(dargv[n++], "--gauge");
298041394f3SDevin Teske 	dargv[n++] = use_xdialog ? dummy_init : init_prompt;
299041394f3SDevin Teske 	if ((dargv[n] = malloc(40)) == NULL)
300041394f3SDevin Teske 		errx(EXIT_FAILURE, "Out of memory?!");
301041394f3SDevin Teske 	snprintf(dargv[n++], 40, "%u", height);
302041394f3SDevin Teske 	if ((dargv[n] = malloc(40)) == NULL)
303041394f3SDevin Teske 		errx(EXIT_FAILURE, "Out of memory?!");
304041394f3SDevin Teske 	snprintf(dargv[n++], 40, "%u", width);
305041394f3SDevin Teske 	dargv[n] = NULL;
306041394f3SDevin Teske 
307041394f3SDevin Teske 	/* Open a pipe(2) to communicate with [X]dialog(1) */
308041394f3SDevin Teske 	if (pipe(stdin_pipe) < 0)
309041394f3SDevin Teske 		err(EXIT_FAILURE, "%s: pipe(2)", __func__);
310041394f3SDevin Teske 
311041394f3SDevin Teske 	/* Fork [X]dialog(1) process */
312041394f3SDevin Teske #if DIALOG_SPAWN_DEBUG
313041394f3SDevin Teske 	fprintf(stderr, "%s: spawning `", __func__);
314041394f3SDevin Teske 	for (i = 0; i < n; i++) {
315041394f3SDevin Teske 		if (i == 0)
316041394f3SDevin Teske 			fprintf(stderr, "%s", dargv[i]);
317041394f3SDevin Teske 		else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-')
318041394f3SDevin Teske 			fprintf(stderr, " %s", dargv[i]);
319041394f3SDevin Teske 		else
320041394f3SDevin Teske 			fprintf(stderr, " \"%s\"", dargv[i]);
321041394f3SDevin Teske 	}
322041394f3SDevin Teske 	fprintf(stderr, "'\n");
323041394f3SDevin Teske #endif
324041394f3SDevin Teske 	posix_spawn_file_actions_init(&action);
325041394f3SDevin Teske 	posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
326041394f3SDevin Teske 	posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
327041394f3SDevin Teske 	error = posix_spawnp(pid, dialog, &action,
328041394f3SDevin Teske 	    (const posix_spawnattr_t *)NULL, dargv, environ);
329*cfeecda0SDevin Teske 	if (error != 0) err(EXIT_FAILURE, "%s", dialog);
330041394f3SDevin Teske 
331041394f3SDevin Teske 	/* NB: Do not free(3) *dargv[], else SIGSEGV */
332041394f3SDevin Teske 
333041394f3SDevin Teske 	return (stdin_pipe[1]);
334041394f3SDevin Teske }
335041394f3SDevin Teske 
336041394f3SDevin Teske /*
337041394f3SDevin Teske  * Returns the number of lines in buffer pointed to by `prompt'. Takes both
338041394f3SDevin Teske  * newlines and escaped-newlines into account.
339041394f3SDevin Teske  */
340041394f3SDevin Teske unsigned int
dialog_prompt_numlines(const char * prompt,uint8_t nlstate)341041394f3SDevin Teske dialog_prompt_numlines(const char *prompt, uint8_t nlstate)
342041394f3SDevin Teske {
343041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
344041394f3SDevin Teske 	const char *cp = prompt;
345041394f3SDevin Teske 	unsigned int nlines = 1;
346041394f3SDevin Teske 
347041394f3SDevin Teske 	if (prompt == NULL || *prompt == '\0')
348041394f3SDevin Teske 		return (0);
349041394f3SDevin Teske 
350041394f3SDevin Teske 	while (*cp != '\0') {
351041394f3SDevin Teske 		if (use_dialog) {
352041394f3SDevin Teske 			if (strncmp(cp, "\\n", 2) == 0) {
353041394f3SDevin Teske 				cp++;
354041394f3SDevin Teske 				nlines++;
355041394f3SDevin Teske 				nls = TRUE; /* See declaration comment */
356041394f3SDevin Teske 			} else if (*cp == '\n') {
357041394f3SDevin Teske 				if (!nls)
358041394f3SDevin Teske 					nlines++;
359041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
360041394f3SDevin Teske 			}
361041394f3SDevin Teske 		} else if (use_libdialog) {
362041394f3SDevin Teske 			if (*cp == '\n')
363041394f3SDevin Teske 				nlines++;
364041394f3SDevin Teske 		} else if (strncmp(cp, "\\n", 2) == 0) {
365041394f3SDevin Teske 			cp++;
366041394f3SDevin Teske 			nlines++;
367041394f3SDevin Teske 		}
368041394f3SDevin Teske 		cp++;
369041394f3SDevin Teske 	}
370041394f3SDevin Teske 
371041394f3SDevin Teske 	return (nlines);
372041394f3SDevin Teske }
373041394f3SDevin Teske 
374041394f3SDevin Teske /*
375041394f3SDevin Teske  * Returns the length in bytes of the longest line in buffer pointed to by
376041394f3SDevin Teske  * `prompt'. Takes newlines and escaped newlines into account. Also discounts
377041394f3SDevin Teske  * dialog(1) color escape codes if enabled (via `use_color' global).
378041394f3SDevin Teske  */
379041394f3SDevin Teske unsigned int
dialog_prompt_longestline(const char * prompt,uint8_t nlstate)380041394f3SDevin Teske dialog_prompt_longestline(const char *prompt, uint8_t nlstate)
381041394f3SDevin Teske {
382041394f3SDevin Teske 	uint8_t backslash = 0;
383041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
384041394f3SDevin Teske 	const char *p = prompt;
385041394f3SDevin Teske 	int longest = 0;
386041394f3SDevin Teske 	int n = 0;
387041394f3SDevin Teske 
388041394f3SDevin Teske 	/* `prompt' parameter is required */
389041394f3SDevin Teske 	if (prompt == NULL)
390041394f3SDevin Teske 		return (0);
391041394f3SDevin Teske 	if (*prompt == '\0')
392041394f3SDevin Teske 		return (0); /* shortcut */
393041394f3SDevin Teske 
394041394f3SDevin Teske 	/* Loop until the end of the string */
395041394f3SDevin Teske 	while (*p != '\0') {
396041394f3SDevin Teske 		/* dialog(1) and dialog(3) will render literal newlines */
397041394f3SDevin Teske 		if (use_dialog || use_libdialog) {
398041394f3SDevin Teske 			if (*p == '\n') {
399041394f3SDevin Teske 				if (!use_libdialog && nls)
400041394f3SDevin Teske 					n++;
401041394f3SDevin Teske 				else {
402041394f3SDevin Teske 					if (n > longest)
403041394f3SDevin Teske 						longest = n;
404041394f3SDevin Teske 					n = 0;
405041394f3SDevin Teske 				}
406041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
407041394f3SDevin Teske 				p++;
408041394f3SDevin Teske 				continue;
409041394f3SDevin Teske 			}
410041394f3SDevin Teske 		}
411041394f3SDevin Teske 
412041394f3SDevin Teske 		/* Check for backslash character */
413041394f3SDevin Teske 		if (*p == '\\') {
414041394f3SDevin Teske 			/* If second backslash, count as a single-char */
415041394f3SDevin Teske 			if ((backslash ^= 1) == 0)
416041394f3SDevin Teske 				n++;
417041394f3SDevin Teske 		} else if (backslash) {
418041394f3SDevin Teske 			if (*p == 'n' && !use_libdialog) { /* new line */
419041394f3SDevin Teske 				/* NB: dialog(3) ignores escaped newlines */
420041394f3SDevin Teske 				nls = TRUE; /* See declaration comment */
421041394f3SDevin Teske 				if (n > longest)
422041394f3SDevin Teske 					longest = n;
423041394f3SDevin Teske 				n = 0;
424041394f3SDevin Teske 			} else if (use_color && *p == 'Z') {
425041394f3SDevin Teske 				if (*++p != '\0')
426041394f3SDevin Teske 					p++;
427041394f3SDevin Teske 				backslash = 0;
428041394f3SDevin Teske 				continue;
429041394f3SDevin Teske 			} else /* [X]dialog(1)/dialog(3) only expand those */
430041394f3SDevin Teske 				n += 2;
431041394f3SDevin Teske 
432041394f3SDevin Teske 			backslash = 0;
433041394f3SDevin Teske 		} else
434041394f3SDevin Teske 			n++;
435041394f3SDevin Teske 		p++;
436041394f3SDevin Teske 	}
437041394f3SDevin Teske 	if (n > longest)
438041394f3SDevin Teske 		longest = n;
439041394f3SDevin Teske 
440041394f3SDevin Teske 	return (longest);
441041394f3SDevin Teske }
442041394f3SDevin Teske 
443041394f3SDevin Teske /*
444041394f3SDevin Teske  * Returns a pointer to the last line in buffer pointed to by `prompt'. Takes
445041394f3SDevin Teske  * both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines
446041394f3SDevin Teske  * into account. If no newlines (escaped or otherwise) appear in the buffer,
447041394f3SDevin Teske  * `prompt' is returned. If passed a NULL pointer, returns NULL.
448041394f3SDevin Teske  */
449041394f3SDevin Teske char *
dialog_prompt_lastline(char * prompt,uint8_t nlstate)450041394f3SDevin Teske dialog_prompt_lastline(char *prompt, uint8_t nlstate)
451041394f3SDevin Teske {
452041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
453041394f3SDevin Teske 	char *lastline;
454041394f3SDevin Teske 	char *p;
455041394f3SDevin Teske 
456041394f3SDevin Teske 	if (prompt == NULL)
457041394f3SDevin Teske 		return (NULL);
458041394f3SDevin Teske 	if (*prompt == '\0')
459041394f3SDevin Teske 		return (prompt); /* shortcut */
460041394f3SDevin Teske 
461041394f3SDevin Teske 	lastline = p = prompt;
462041394f3SDevin Teske 	while (*p != '\0') {
463041394f3SDevin Teske 		/* dialog(1) and dialog(3) will render literal newlines */
464041394f3SDevin Teske 		if (use_dialog || use_libdialog) {
465041394f3SDevin Teske 			if (*p == '\n') {
466041394f3SDevin Teske 				if (use_libdialog || !nls)
467041394f3SDevin Teske 					lastline = p + 1;
468041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
469041394f3SDevin Teske 			}
470041394f3SDevin Teske 		}
471041394f3SDevin Teske 		/* dialog(3) does not expand escaped newlines */
472041394f3SDevin Teske 		if (use_libdialog) {
473041394f3SDevin Teske 			p++;
474041394f3SDevin Teske 			continue;
475041394f3SDevin Teske 		}
476041394f3SDevin Teske 		if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
477041394f3SDevin Teske 			nls = TRUE; /* See declaration comment */
478041394f3SDevin Teske 			lastline = p + 1;
479041394f3SDevin Teske 		}
480041394f3SDevin Teske 		p++;
481041394f3SDevin Teske 	}
482041394f3SDevin Teske 
483041394f3SDevin Teske 	return (lastline);
484041394f3SDevin Teske }
485041394f3SDevin Teske 
486041394f3SDevin Teske /*
487041394f3SDevin Teske  * Returns the number of extra lines generated by wrapping the text in buffer
488041394f3SDevin Teske  * pointed to by `prompt' within `ncols' columns (for prompts, this should be
489041394f3SDevin Teske  * dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via
490041394f3SDevin Teske  * `use_color' global).
491041394f3SDevin Teske  */
492041394f3SDevin Teske int
dialog_prompt_wrappedlines(char * prompt,int ncols,uint8_t nlstate)493041394f3SDevin Teske dialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate)
494041394f3SDevin Teske {
495041394f3SDevin Teske 	uint8_t backslash = 0;
496041394f3SDevin Teske 	uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
497041394f3SDevin Teske 	char *cp;
498041394f3SDevin Teske 	char *p = prompt;
499041394f3SDevin Teske 	int n = 0;
500041394f3SDevin Teske 	int wlines = 0;
501041394f3SDevin Teske 
502041394f3SDevin Teske 	/* `prompt' parameter is required */
503041394f3SDevin Teske 	if (p == NULL)
504041394f3SDevin Teske 		return (0);
505041394f3SDevin Teske 	if (*p == '\0')
506041394f3SDevin Teske 		return (0); /* shortcut */
507041394f3SDevin Teske 
508041394f3SDevin Teske 	/* Loop until the end of the string */
509041394f3SDevin Teske 	while (*p != '\0') {
510041394f3SDevin Teske 		/* dialog(1) and dialog(3) will render literal newlines */
511041394f3SDevin Teske 		if (use_dialog || use_libdialog) {
512041394f3SDevin Teske 			if (*p == '\n') {
513041394f3SDevin Teske 				if (use_dialog || !nls)
514041394f3SDevin Teske 					n = 0;
515041394f3SDevin Teske 				nls = FALSE; /* See declaration comment */
516041394f3SDevin Teske 			}
517041394f3SDevin Teske 		}
518041394f3SDevin Teske 
519041394f3SDevin Teske 		/* Check for backslash character */
520041394f3SDevin Teske 		if (*p == '\\') {
521041394f3SDevin Teske 			/* If second backslash, count as a single-char */
522041394f3SDevin Teske 			if ((backslash ^= 1) == 0)
523041394f3SDevin Teske 				n++;
524041394f3SDevin Teske 		} else if (backslash) {
525041394f3SDevin Teske 			if (*p == 'n' && !use_libdialog) { /* new line */
526041394f3SDevin Teske 				/* NB: dialog(3) ignores escaped newlines */
527041394f3SDevin Teske 				nls = TRUE; /* See declaration comment */
528041394f3SDevin Teske 				n = 0;
529041394f3SDevin Teske 			} else if (use_color && *p == 'Z') {
530041394f3SDevin Teske 				if (*++p != '\0')
531041394f3SDevin Teske 					p++;
532041394f3SDevin Teske 				backslash = 0;
533041394f3SDevin Teske 				continue;
534041394f3SDevin Teske 			} else /* [X]dialog(1)/dialog(3) only expand those */
535041394f3SDevin Teske 				n += 2;
536041394f3SDevin Teske 
537041394f3SDevin Teske 			backslash = 0;
538041394f3SDevin Teske 		} else
539041394f3SDevin Teske 			n++;
540041394f3SDevin Teske 
541041394f3SDevin Teske 		/* Did we pass the width barrier? */
542041394f3SDevin Teske 		if (n > ncols) {
543041394f3SDevin Teske 			/*
544041394f3SDevin Teske 			 * Work backward to find the first whitespace on-which
545041394f3SDevin Teske 			 * dialog(1) will wrap the line (but don't go before
546041394f3SDevin Teske 			 * the start of this line).
547041394f3SDevin Teske 			 */
548041394f3SDevin Teske 			cp = p;
549041394f3SDevin Teske 			while (n > 1 && !isspace(*cp)) {
550041394f3SDevin Teske 				cp--;
551041394f3SDevin Teske 				n--;
552041394f3SDevin Teske 			}
553041394f3SDevin Teske 			if (n > 0 && isspace(*cp))
554041394f3SDevin Teske 				p = cp;
555041394f3SDevin Teske 			wlines++;
556041394f3SDevin Teske 			n = 1;
557041394f3SDevin Teske 		}
558041394f3SDevin Teske 
559041394f3SDevin Teske 		p++;
560041394f3SDevin Teske 	}
561041394f3SDevin Teske 
562041394f3SDevin Teske 	return (wlines);
563041394f3SDevin Teske }
564041394f3SDevin Teske 
565041394f3SDevin Teske /*
566041394f3SDevin Teske  * Returns zero if the buffer pointed to by `prompt' contains an escaped
567041394f3SDevin Teske  * newline but only if appearing after any/all literal newlines. This is
568041394f3SDevin Teske  * specific to dialog(1) and does not apply to Xdialog(1).
569041394f3SDevin Teske  *
570041394f3SDevin Teske  * As an attempt to make shell scripts easier to read, dialog(1) will "eat"
571041394f3SDevin Teske  * the first literal newline after an escaped newline. This however has a bug
572041394f3SDevin Teske  * in its implementation in that rather than allowing `\\n\n' to be treated
573041394f3SDevin Teske  * similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates
574041394f3SDevin Teske  * the following literal newline (with or without characters between [!]) into
575041394f3SDevin Teske  * a single space.
576041394f3SDevin Teske  *
577041394f3SDevin Teske  * If you want to be compatible with Xdialog(1), it is suggested that you not
578041394f3SDevin Teske  * use literal newlines (they aren't supported); but if you have to use them,
579041394f3SDevin Teske  * go right ahead. But be forewarned... if you set $DIALOG in your environment
580041394f3SDevin Teske  * to something other than `cdialog' (our current dialog(1)), then it should
581041394f3SDevin Teske  * do the same thing w/respect to how to handle a literal newline after an
582041394f3SDevin Teske  * escaped newline (you could do no wrong by translating every literal newline
583041394f3SDevin Teske  * into a space but only when you've previously encountered an escaped one;
584041394f3SDevin Teske  * this is what dialog(1) is doing).
585041394f3SDevin Teske  *
586041394f3SDevin Teske  * The ``newline state'' (or nlstate for short; as I'm calling it) is helpful
587041394f3SDevin Teske  * if you plan to combine multiple strings into a single prompt text. In lead-
588041394f3SDevin Teske  * up to this procedure, a common task is to calculate and utilize the widths
589041394f3SDevin Teske  * and heights of each piece of prompt text to later be combined. However, if
590041394f3SDevin Teske  * (for example) the first string ends in a positive newline state (has an
591041394f3SDevin Teske  * escaped newline without trailing literal), the first literal newline in the
592041394f3SDevin Teske  * second string will be mangled.
593041394f3SDevin Teske  *
594041394f3SDevin Teske  * The return value of this function should be used as the `nlstate' argument
595041394f3SDevin Teske  * to dialog_*() functions that require it to allow accurate calculations in
596041394f3SDevin Teske  * the event such information is needed.
597041394f3SDevin Teske  */
598041394f3SDevin Teske uint8_t
dialog_prompt_nlstate(const char * prompt)599041394f3SDevin Teske dialog_prompt_nlstate(const char *prompt)
600041394f3SDevin Teske {
601041394f3SDevin Teske 	const char *cp;
602041394f3SDevin Teske 
603041394f3SDevin Teske 	if (prompt == NULL)
604041394f3SDevin Teske 		return 0;
605041394f3SDevin Teske 
606041394f3SDevin Teske 	/*
607041394f3SDevin Teske 	 * Work our way backward from the end of the string for efficiency.
608041394f3SDevin Teske 	 */
609041394f3SDevin Teske 	cp = prompt + strlen(prompt);
610041394f3SDevin Teske 	while (--cp >= prompt) {
611041394f3SDevin Teske 		/*
612041394f3SDevin Teske 		 * If we get to a literal newline first, this prompt ends in a
613041394f3SDevin Teske 		 * clean state for rendering with dialog(1). Otherwise, if we
614041394f3SDevin Teske 		 * get to an escaped newline first, this prompt ends in an un-
615041394f3SDevin Teske 		 * clean state (following literal will be mangled; see above).
616041394f3SDevin Teske 		 */
617041394f3SDevin Teske 		if (*cp == '\n')
618041394f3SDevin Teske 			return (0);
619041394f3SDevin Teske 		else if (*cp == 'n' && --cp > prompt && *cp == '\\')
620041394f3SDevin Teske 			return (1);
621041394f3SDevin Teske 	}
622041394f3SDevin Teske 
623041394f3SDevin Teske 	return (0); /* no newlines (escaped or otherwise) */
624041394f3SDevin Teske }
625041394f3SDevin Teske 
626041394f3SDevin Teske /*
627041394f3SDevin Teske  * Free allocated items initialized by tty_maxsize_update() and
628041394f3SDevin Teske  * x11_maxsize_update()
629041394f3SDevin Teske  */
630041394f3SDevin Teske void
dialog_maxsize_free(void)631041394f3SDevin Teske dialog_maxsize_free(void)
632041394f3SDevin Teske {
633041394f3SDevin Teske 	if (maxsize != NULL) {
634041394f3SDevin Teske 		free(maxsize);
635041394f3SDevin Teske 		maxsize = NULL;
636041394f3SDevin Teske 	}
637041394f3SDevin Teske }
638