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