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