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