2 * Copyright (c) 2013-2018 Devin Teske <dteske@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
30 #include <sys/ioctl.h>
43 #include "dialog_util.h"
45 #include "dpv_private.h"
47 extern char **environ;
49 #define TTY_DEFAULT_ROWS 24
50 #define TTY_DEFAULT_COLS 80
52 /* [X]dialog(1) characteristics */
53 uint8_t dialog_test = 0;
54 uint8_t use_dialog = 0;
55 uint8_t use_libdialog = 1;
56 uint8_t use_xdialog = 0;
57 uint8_t use_color = 1;
58 char dialog[PATH_MAX] = DIALOG;
60 /* [X]dialog(1) functionality */
62 char *backtitle = NULL;
65 static char *dargv[64] = { NULL };
67 /* TTY/Screen characteristics */
68 static struct winsize *maxsize = NULL;
70 /* Function prototypes */
71 static void tty_maxsize_update(void);
72 static void x11_maxsize_update(void);
75 * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
76 * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
77 * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
78 * maximum height and width (respectively) for a dialog(1) widget based on the
81 * This function is called automatically by dialog_maxrows/cols() to reflect
82 * changes in terminal size in-between calls.
85 tty_maxsize_update(void)
87 int fd = STDIN_FILENO;
90 if (maxsize == NULL) {
91 if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
92 errx(EXIT_FAILURE, "Out of memory?!");
93 memset((void *)maxsize, '\0', sizeof(struct winsize));
97 fd = open("/dev/tty", O_RDONLY);
98 if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) {
99 maxsize->ws_row = TTY_DEFAULT_ROWS;
100 maxsize->ws_col = TTY_DEFAULT_COLS;
105 * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
106 * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
107 * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
108 * maximum height and width (respectively) for an Xdialog(1) widget based on
109 * the active video resolution of the X11 environment.
111 * This function is called automatically by dialog_maxrows/cols() to initialize
112 * `maxsize'. Since video resolution changes are less common and more obtrusive
113 * than changes to terminal size, the dialog_maxrows/cols() functions only call
114 * this function when `maxsize' is set to NULL.
117 x11_maxsize_update(void)
123 char cmdbuf[LINE_MAX];
126 if (maxsize == NULL) {
127 if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
128 errx(EXIT_FAILURE, "Out of memory?!");
129 memset((void *)maxsize, '\0', sizeof(struct winsize));
132 /* Assemble the command necessary to get X11 sizes */
133 snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog);
135 fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */
137 if ((f = popen(cmdbuf, "r")) == NULL) {
139 warnx("WARNING! Command `%s' failed", cmdbuf);
143 /* Read in the line returned from Xdialog(1) */
144 if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0))
147 /* Check for X11-related errors */
148 if (strncmp(rbuf, "Xdialog: Error", 14) == 0)
151 /* Parse expected output: MaxSize: YY, XXX */
152 if ((rows = strchr(rbuf, ' ')) == NULL)
154 if ((cols = strchr(rows, ',')) != NULL) {
155 /* strtonum(3) doesn't like trailing junk */
157 if ((cp = strchr(cols, '\n')) != NULL)
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);
169 * Return the current maximum height (rows) for an [X]dialog(1) widget.
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);
183 * Return the current maximum width (cols) for an [X]dialog(1) widget.
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 */
194 if (use_dialog || use_libdialog) {
196 return (maxsize->ws_col - 2);
198 return (maxsize->ws_col);
200 return (maxsize->ws_col);
204 * Return the current maximum width (cols) for the terminal.
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 */
215 return (maxsize->ws_col);
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).
226 dialog_spawn_gauge(char *init_prompt, pid_t *pid)
228 char dummy_init[2] = "";
233 posix_spawn_file_actions_t action;
234 #if DIALOG_SPAWN_DEBUG
238 int stdin_pipe[2] = { -1, -1 };
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);
244 /* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */
245 setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1);
247 /* Constrain the height/width */
248 height = dialog_maxrows();
249 if (backtitle != NULL)
250 height -= use_shadow ? 5 : 4;
251 if (dheight < height)
253 width = dialog_maxcols();
257 /* Populate argument array */
260 if ((dargv[n] = malloc(8)) == NULL)
261 errx(EXIT_FAILURE, "Out of memory?!");
262 sprintf(dargv[n++], "--title");
265 if ((dargv[n] = malloc(8)) == NULL)
266 errx(EXIT_FAILURE, "Out of memory?!");
267 sprintf(dargv[n++], "--title");
268 if ((dargv[n] = malloc(1)) == NULL)
269 errx(EXIT_FAILURE, "Out of memory?!");
272 if (backtitle != NULL) {
273 if ((dargv[n] = malloc(12)) == NULL)
274 errx(EXIT_FAILURE, "Out of memory?!");
275 sprintf(dargv[n++], "--backtitle");
276 dargv[n++] = backtitle;
279 if ((dargv[n] = malloc(11)) == NULL)
280 errx(EXIT_FAILURE, "Out of memory?!");
281 sprintf(dargv[n++], "--colors");
284 if ((dargv[n] = malloc(7)) == NULL)
285 errx(EXIT_FAILURE, "Out of memory?!");
286 sprintf(dargv[n++], "--left");
289 * NOTE: Xdialog(1)'s `--wrap' appears to be broken for the
290 * `--gauge' widget prompt-updates. Add it anyway (in-case it
291 * gets fixed in some later release).
293 if ((dargv[n] = malloc(7)) == NULL)
294 errx(EXIT_FAILURE, "Out of memory?!");
295 sprintf(dargv[n++], "--wrap");
297 if ((dargv[n] = malloc(8)) == NULL)
298 errx(EXIT_FAILURE, "Out of memory?!");
299 sprintf(dargv[n++], "--gauge");
300 dargv[n++] = use_xdialog ? dummy_init : init_prompt;
301 if ((dargv[n] = malloc(40)) == NULL)
302 errx(EXIT_FAILURE, "Out of memory?!");
303 snprintf(dargv[n++], 40, "%u", height);
304 if ((dargv[n] = malloc(40)) == NULL)
305 errx(EXIT_FAILURE, "Out of memory?!");
306 snprintf(dargv[n++], 40, "%u", width);
309 /* Open a pipe(2) to communicate with [X]dialog(1) */
310 if (pipe(stdin_pipe) < 0)
311 err(EXIT_FAILURE, "%s: pipe(2)", __func__);
313 /* Fork [X]dialog(1) process */
314 #if DIALOG_SPAWN_DEBUG
315 fprintf(stderr, "%s: spawning `", __func__);
316 for (i = 0; i < n; i++) {
318 fprintf(stderr, "%s", dargv[i]);
319 else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-')
320 fprintf(stderr, " %s", dargv[i]);
322 fprintf(stderr, " \"%s\"", dargv[i]);
324 fprintf(stderr, "'\n");
326 posix_spawn_file_actions_init(&action);
327 posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
328 posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
329 error = posix_spawnp(pid, dialog, &action,
330 (const posix_spawnattr_t *)NULL, dargv, environ);
331 if (error != 0) err(EXIT_FAILURE, "%s", dialog);
333 /* NB: Do not free(3) *dargv[], else SIGSEGV */
335 return (stdin_pipe[1]);
339 * Returns the number of lines in buffer pointed to by `prompt'. Takes both
340 * newlines and escaped-newlines into account.
343 dialog_prompt_numlines(const char *prompt, uint8_t nlstate)
345 uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
346 const char *cp = prompt;
347 unsigned int nlines = 1;
349 if (prompt == NULL || *prompt == '\0')
352 while (*cp != '\0') {
354 if (strncmp(cp, "\\n", 2) == 0) {
357 nls = TRUE; /* See declaration comment */
358 } else if (*cp == '\n') {
361 nls = FALSE; /* See declaration comment */
363 } else if (use_libdialog) {
366 } else if (strncmp(cp, "\\n", 2) == 0) {
377 * Returns the length in bytes of the longest line in buffer pointed to by
378 * `prompt'. Takes newlines and escaped newlines into account. Also discounts
379 * dialog(1) color escape codes if enabled (via `use_color' global).
382 dialog_prompt_longestline(const char *prompt, uint8_t nlstate)
384 uint8_t backslash = 0;
385 uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
386 const char *p = prompt;
390 /* `prompt' parameter is required */
394 return (0); /* shortcut */
396 /* Loop until the end of the string */
398 /* dialog(1) and dialog(3) will render literal newlines */
399 if (use_dialog || use_libdialog) {
401 if (!use_libdialog && nls)
408 nls = FALSE; /* See declaration comment */
414 /* Check for backslash character */
416 /* If second backslash, count as a single-char */
417 if ((backslash ^= 1) == 0)
419 } else if (backslash) {
420 if (*p == 'n' && !use_libdialog) { /* new line */
421 /* NB: dialog(3) ignores escaped newlines */
422 nls = TRUE; /* See declaration comment */
426 } else if (use_color && *p == 'Z') {
431 } else /* [X]dialog(1)/dialog(3) only expand those */
446 * Returns a pointer to the last line in buffer pointed to by `prompt'. Takes
447 * both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines
448 * into account. If no newlines (escaped or otherwise) appear in the buffer,
449 * `prompt' is returned. If passed a NULL pointer, returns NULL.
452 dialog_prompt_lastline(char *prompt, uint8_t nlstate)
454 uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
461 return (prompt); /* shortcut */
463 lastline = p = prompt;
465 /* dialog(1) and dialog(3) will render literal newlines */
466 if (use_dialog || use_libdialog) {
468 if (use_libdialog || !nls)
470 nls = FALSE; /* See declaration comment */
473 /* dialog(3) does not expand escaped newlines */
478 if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
479 nls = TRUE; /* See declaration comment */
489 * Returns the number of extra lines generated by wrapping the text in buffer
490 * pointed to by `prompt' within `ncols' columns (for prompts, this should be
491 * dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via
492 * `use_color' global).
495 dialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate)
497 uint8_t backslash = 0;
498 uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
504 /* `prompt' parameter is required */
508 return (0); /* shortcut */
510 /* Loop until the end of the string */
512 /* dialog(1) and dialog(3) will render literal newlines */
513 if (use_dialog || use_libdialog) {
515 if (use_dialog || !nls)
517 nls = FALSE; /* See declaration comment */
521 /* Check for backslash character */
523 /* If second backslash, count as a single-char */
524 if ((backslash ^= 1) == 0)
526 } else if (backslash) {
527 if (*p == 'n' && !use_libdialog) { /* new line */
528 /* NB: dialog(3) ignores escaped newlines */
529 nls = TRUE; /* See declaration comment */
531 } else if (use_color && *p == 'Z') {
536 } else /* [X]dialog(1)/dialog(3) only expand those */
543 /* Did we pass the width barrier? */
546 * Work backward to find the first whitespace on-which
547 * dialog(1) will wrap the line (but don't go before
548 * the start of this line).
551 while (n > 1 && !isspace(*cp)) {
555 if (n > 0 && isspace(*cp))
568 * Returns zero if the buffer pointed to by `prompt' contains an escaped
569 * newline but only if appearing after any/all literal newlines. This is
570 * specific to dialog(1) and does not apply to Xdialog(1).
572 * As an attempt to make shell scripts easier to read, dialog(1) will "eat"
573 * the first literal newline after an escaped newline. This however has a bug
574 * in its implementation in that rather than allowing `\\n\n' to be treated
575 * similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates
576 * the following literal newline (with or without characters between [!]) into
579 * If you want to be compatible with Xdialog(1), it is suggested that you not
580 * use literal newlines (they aren't supported); but if you have to use them,
581 * go right ahead. But be forewarned... if you set $DIALOG in your environment
582 * to something other than `cdialog' (our current dialog(1)), then it should
583 * do the same thing w/respect to how to handle a literal newline after an
584 * escaped newline (you could do no wrong by translating every literal newline
585 * into a space but only when you've previously encountered an escaped one;
586 * this is what dialog(1) is doing).
588 * The ``newline state'' (or nlstate for short; as I'm calling it) is helpful
589 * if you plan to combine multiple strings into a single prompt text. In lead-
590 * up to this procedure, a common task is to calculate and utilize the widths
591 * and heights of each piece of prompt text to later be combined. However, if
592 * (for example) the first string ends in a positive newline state (has an
593 * escaped newline without trailing literal), the first literal newline in the
594 * second string will be mangled.
596 * The return value of this function should be used as the `nlstate' argument
597 * to dialog_*() functions that require it to allow accurate calculations in
598 * the event such information is needed.
601 dialog_prompt_nlstate(const char *prompt)
609 * Work our way backward from the end of the string for efficiency.
611 cp = prompt + strlen(prompt);
612 while (--cp >= prompt) {
614 * If we get to a literal newline first, this prompt ends in a
615 * clean state for rendering with dialog(1). Otherwise, if we
616 * get to an escaped newline first, this prompt ends in an un-
617 * clean state (following literal will be mangled; see above).
621 else if (*cp == 'n' && --cp > prompt && *cp == '\\')
625 return (0); /* no newlines (escaped or otherwise) */
629 * Free allocated items initialized by tty_maxsize_update() and
630 * x11_maxsize_update()
633 dialog_maxsize_free(void)
635 if (maxsize != NULL) {