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