1274116Sdteske/*- 2335406Sdteske * Copyright (c) 2013-2018 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: stable/11/lib/libdpv/dialog_util.c 335406 2018-06-20 05:45:41Z 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"); 268294922Sdteske if ((dargv[n] = malloc(1)) == NULL) 269294893Sdteske errx(EXIT_FAILURE, "Out of memory?!"); 270294922Sdteske *dargv[n++] = '\0'; 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); 331335406Sdteske if (error != 0) err(EXIT_FAILURE, "%s", dialog); 332274116Sdteske 333274116Sdteske /* NB: Do not free(3) *dargv[], else SIGSEGV */ 334274116Sdteske 335274116Sdteske return (stdin_pipe[1]); 336274116Sdteske} 337274116Sdteske 338274116Sdteske/* 339274116Sdteske * Returns the number of lines in buffer pointed to by `prompt'. Takes both 340274116Sdteske * newlines and escaped-newlines into account. 341274116Sdteske */ 342274116Sdteskeunsigned int 343274116Sdteskedialog_prompt_numlines(const char *prompt, uint8_t nlstate) 344274116Sdteske{ 345274116Sdteske uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ 346274116Sdteske const char *cp = prompt; 347274116Sdteske unsigned int nlines = 1; 348274116Sdteske 349274116Sdteske if (prompt == NULL || *prompt == '\0') 350274116Sdteske return (0); 351274116Sdteske 352274116Sdteske while (*cp != '\0') { 353274116Sdteske if (use_dialog) { 354274116Sdteske if (strncmp(cp, "\\n", 2) == 0) { 355274116Sdteske cp++; 356274116Sdteske nlines++; 357274116Sdteske nls = TRUE; /* See declaration comment */ 358274116Sdteske } else if (*cp == '\n') { 359274116Sdteske if (!nls) 360274116Sdteske nlines++; 361274116Sdteske nls = FALSE; /* See declaration comment */ 362274116Sdteske } 363274116Sdteske } else if (use_libdialog) { 364274116Sdteske if (*cp == '\n') 365274116Sdteske nlines++; 366274116Sdteske } else if (strncmp(cp, "\\n", 2) == 0) { 367274116Sdteske cp++; 368274116Sdteske nlines++; 369274116Sdteske } 370274116Sdteske cp++; 371274116Sdteske } 372274116Sdteske 373274116Sdteske return (nlines); 374274116Sdteske} 375274116Sdteske 376274116Sdteske/* 377274116Sdteske * Returns the length in bytes of the longest line in buffer pointed to by 378274116Sdteske * `prompt'. Takes newlines and escaped newlines into account. Also discounts 379274116Sdteske * dialog(1) color escape codes if enabled (via `use_color' global). 380274116Sdteske */ 381274116Sdteskeunsigned int 382274116Sdteskedialog_prompt_longestline(const char *prompt, uint8_t nlstate) 383274116Sdteske{ 384274116Sdteske uint8_t backslash = 0; 385274116Sdteske uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ 386274116Sdteske const char *p = prompt; 387274116Sdteske int longest = 0; 388274116Sdteske int n = 0; 389274116Sdteske 390274116Sdteske /* `prompt' parameter is required */ 391274116Sdteske if (prompt == NULL) 392274116Sdteske return (0); 393274116Sdteske if (*prompt == '\0') 394274116Sdteske return (0); /* shortcut */ 395274116Sdteske 396274116Sdteske /* Loop until the end of the string */ 397274116Sdteske while (*p != '\0') { 398274116Sdteske /* dialog(1) and dialog(3) will render literal newlines */ 399274116Sdteske if (use_dialog || use_libdialog) { 400274116Sdteske if (*p == '\n') { 401274116Sdteske if (!use_libdialog && nls) 402274116Sdteske n++; 403274116Sdteske else { 404274116Sdteske if (n > longest) 405274116Sdteske longest = n; 406274116Sdteske n = 0; 407274116Sdteske } 408274116Sdteske nls = FALSE; /* See declaration comment */ 409274116Sdteske p++; 410274116Sdteske continue; 411274116Sdteske } 412274116Sdteske } 413274116Sdteske 414274116Sdteske /* Check for backslash character */ 415274116Sdteske if (*p == '\\') { 416274116Sdteske /* If second backslash, count as a single-char */ 417274116Sdteske if ((backslash ^= 1) == 0) 418274116Sdteske n++; 419274116Sdteske } else if (backslash) { 420274116Sdteske if (*p == 'n' && !use_libdialog) { /* new line */ 421274116Sdteske /* NB: dialog(3) ignores escaped newlines */ 422274116Sdteske nls = TRUE; /* See declaration comment */ 423274116Sdteske if (n > longest) 424274116Sdteske longest = n; 425274116Sdteske n = 0; 426274116Sdteske } else if (use_color && *p == 'Z') { 427274116Sdteske if (*++p != '\0') 428274116Sdteske p++; 429274116Sdteske backslash = 0; 430274116Sdteske continue; 431274116Sdteske } else /* [X]dialog(1)/dialog(3) only expand those */ 432274116Sdteske n += 2; 433274116Sdteske 434274116Sdteske backslash = 0; 435274116Sdteske } else 436274116Sdteske n++; 437274116Sdteske p++; 438274116Sdteske } 439274116Sdteske if (n > longest) 440274116Sdteske longest = n; 441274116Sdteske 442274116Sdteske return (longest); 443274116Sdteske} 444274116Sdteske 445274116Sdteske/* 446274116Sdteske * Returns a pointer to the last line in buffer pointed to by `prompt'. Takes 447274116Sdteske * both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines 448274116Sdteske * into account. If no newlines (escaped or otherwise) appear in the buffer, 449274116Sdteske * `prompt' is returned. If passed a NULL pointer, returns NULL. 450274116Sdteske */ 451274116Sdteskechar * 452274116Sdteskedialog_prompt_lastline(char *prompt, uint8_t nlstate) 453274116Sdteske{ 454274116Sdteske uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ 455274116Sdteske char *lastline; 456274116Sdteske char *p; 457274116Sdteske 458274116Sdteske if (prompt == NULL) 459274116Sdteske return (NULL); 460274116Sdteske if (*prompt == '\0') 461274116Sdteske return (prompt); /* shortcut */ 462274116Sdteske 463274116Sdteske lastline = p = prompt; 464274116Sdteske while (*p != '\0') { 465274116Sdteske /* dialog(1) and dialog(3) will render literal newlines */ 466274116Sdteske if (use_dialog || use_libdialog) { 467274116Sdteske if (*p == '\n') { 468274116Sdteske if (use_libdialog || !nls) 469274116Sdteske lastline = p + 1; 470274116Sdteske nls = FALSE; /* See declaration comment */ 471274116Sdteske } 472274116Sdteske } 473274116Sdteske /* dialog(3) does not expand escaped newlines */ 474274116Sdteske if (use_libdialog) { 475274116Sdteske p++; 476274116Sdteske continue; 477274116Sdteske } 478274116Sdteske if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') { 479274116Sdteske nls = TRUE; /* See declaration comment */ 480274116Sdteske lastline = p + 1; 481274116Sdteske } 482274116Sdteske p++; 483274116Sdteske } 484274116Sdteske 485274116Sdteske return (lastline); 486274116Sdteske} 487274116Sdteske 488274116Sdteske/* 489274116Sdteske * Returns the number of extra lines generated by wrapping the text in buffer 490274116Sdteske * pointed to by `prompt' within `ncols' columns (for prompts, this should be 491274116Sdteske * dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via 492274116Sdteske * `use_color' global). 493274116Sdteske */ 494274116Sdteskeint 495274116Sdteskedialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate) 496274116Sdteske{ 497274116Sdteske uint8_t backslash = 0; 498274116Sdteske uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ 499274116Sdteske char *cp; 500274116Sdteske char *p = prompt; 501274116Sdteske int n = 0; 502274116Sdteske int wlines = 0; 503274116Sdteske 504274116Sdteske /* `prompt' parameter is required */ 505274116Sdteske if (p == NULL) 506274116Sdteske return (0); 507274116Sdteske if (*p == '\0') 508274116Sdteske return (0); /* shortcut */ 509274116Sdteske 510274116Sdteske /* Loop until the end of the string */ 511274116Sdteske while (*p != '\0') { 512274116Sdteske /* dialog(1) and dialog(3) will render literal newlines */ 513274116Sdteske if (use_dialog || use_libdialog) { 514274116Sdteske if (*p == '\n') { 515274116Sdteske if (use_dialog || !nls) 516274116Sdteske n = 0; 517274116Sdteske nls = FALSE; /* See declaration comment */ 518274116Sdteske } 519274116Sdteske } 520274116Sdteske 521274116Sdteske /* Check for backslash character */ 522274116Sdteske if (*p == '\\') { 523274116Sdteske /* If second backslash, count as a single-char */ 524274116Sdteske if ((backslash ^= 1) == 0) 525274116Sdteske n++; 526274116Sdteske } else if (backslash) { 527274116Sdteske if (*p == 'n' && !use_libdialog) { /* new line */ 528274116Sdteske /* NB: dialog(3) ignores escaped newlines */ 529274116Sdteske nls = TRUE; /* See declaration comment */ 530274116Sdteske n = 0; 531274116Sdteske } else if (use_color && *p == 'Z') { 532274116Sdteske if (*++p != '\0') 533274116Sdteske p++; 534274116Sdteske backslash = 0; 535274116Sdteske continue; 536274116Sdteske } else /* [X]dialog(1)/dialog(3) only expand those */ 537274116Sdteske n += 2; 538274116Sdteske 539274116Sdteske backslash = 0; 540274116Sdteske } else 541274116Sdteske n++; 542274116Sdteske 543274116Sdteske /* Did we pass the width barrier? */ 544274116Sdteske if (n > ncols) { 545274116Sdteske /* 546274116Sdteske * Work backward to find the first whitespace on-which 547274116Sdteske * dialog(1) will wrap the line (but don't go before 548274116Sdteske * the start of this line). 549274116Sdteske */ 550274116Sdteske cp = p; 551274116Sdteske while (n > 1 && !isspace(*cp)) { 552274116Sdteske cp--; 553274116Sdteske n--; 554274116Sdteske } 555274116Sdteske if (n > 0 && isspace(*cp)) 556274116Sdteske p = cp; 557274116Sdteske wlines++; 558274116Sdteske n = 1; 559274116Sdteske } 560274116Sdteske 561274116Sdteske p++; 562274116Sdteske } 563274116Sdteske 564274116Sdteske return (wlines); 565274116Sdteske} 566274116Sdteske 567274116Sdteske/* 568274116Sdteske * Returns zero if the buffer pointed to by `prompt' contains an escaped 569274116Sdteske * newline but only if appearing after any/all literal newlines. This is 570274116Sdteske * specific to dialog(1) and does not apply to Xdialog(1). 571274116Sdteske * 572274116Sdteske * As an attempt to make shell scripts easier to read, dialog(1) will "eat" 573274116Sdteske * the first literal newline after an escaped newline. This however has a bug 574274116Sdteske * in its implementation in that rather than allowing `\\n\n' to be treated 575274116Sdteske * similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates 576274116Sdteske * the following literal newline (with or without characters between [!]) into 577274116Sdteske * a single space. 578274116Sdteske * 579274116Sdteske * If you want to be compatible with Xdialog(1), it is suggested that you not 580274116Sdteske * use literal newlines (they aren't supported); but if you have to use them, 581274116Sdteske * go right ahead. But be forewarned... if you set $DIALOG in your environment 582274116Sdteske * to something other than `cdialog' (our current dialog(1)), then it should 583274116Sdteske * do the same thing w/respect to how to handle a literal newline after an 584274116Sdteske * escaped newline (you could do no wrong by translating every literal newline 585274116Sdteske * into a space but only when you've previously encountered an escaped one; 586274116Sdteske * this is what dialog(1) is doing). 587274116Sdteske * 588274116Sdteske * The ``newline state'' (or nlstate for short; as I'm calling it) is helpful 589274116Sdteske * if you plan to combine multiple strings into a single prompt text. In lead- 590274116Sdteske * up to this procedure, a common task is to calculate and utilize the widths 591274116Sdteske * and heights of each piece of prompt text to later be combined. However, if 592274116Sdteske * (for example) the first string ends in a positive newline state (has an 593274116Sdteske * escaped newline without trailing literal), the first literal newline in the 594274116Sdteske * second string will be mangled. 595274116Sdteske * 596274116Sdteske * The return value of this function should be used as the `nlstate' argument 597274116Sdteske * to dialog_*() functions that require it to allow accurate calculations in 598274116Sdteske * the event such information is needed. 599274116Sdteske */ 600274116Sdteskeuint8_t 601274116Sdteskedialog_prompt_nlstate(const char *prompt) 602274116Sdteske{ 603274116Sdteske const char *cp; 604274116Sdteske 605274116Sdteske if (prompt == NULL) 606274116Sdteske return 0; 607274116Sdteske 608274116Sdteske /* 609274116Sdteske * Work our way backward from the end of the string for efficiency. 610274116Sdteske */ 611274116Sdteske cp = prompt + strlen(prompt); 612274116Sdteske while (--cp >= prompt) { 613274116Sdteske /* 614274116Sdteske * If we get to a literal newline first, this prompt ends in a 615274116Sdteske * clean state for rendering with dialog(1). Otherwise, if we 616274116Sdteske * get to an escaped newline first, this prompt ends in an un- 617274116Sdteske * clean state (following literal will be mangled; see above). 618274116Sdteske */ 619274116Sdteske if (*cp == '\n') 620274116Sdteske return (0); 621274116Sdteske else if (*cp == 'n' && --cp > prompt && *cp == '\\') 622274116Sdteske return (1); 623274116Sdteske } 624274116Sdteske 625274116Sdteske return (0); /* no newlines (escaped or otherwise) */ 626274116Sdteske} 627274116Sdteske 628274116Sdteske/* 629274116Sdteske * Free allocated items initialized by tty_maxsize_update() and 630274116Sdteske * x11_maxsize_update() 631274116Sdteske */ 632274116Sdteskevoid 633274116Sdteskedialog_maxsize_free(void) 634274116Sdteske{ 635274116Sdteske if (maxsize != NULL) { 636274116Sdteske free(maxsize); 637274116Sdteske maxsize = NULL; 638274116Sdteske } 639274116Sdteske} 640